Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DM-38339: Refactor mobu state management #225

Merged
merged 1 commit into from
Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 0 additions & 26 deletions src/mobu/autostart.py

This file was deleted.

86 changes: 86 additions & 0 deletions src/mobu/dependencies/context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"""Request context dependency for FastAPI.

This dependency gathers a variety of information into a single object for the
convenience of writing request handlers. It also provides a place to store a
`structlog.BoundLogger` that can gather additional context during processing,
including from dependencies.
"""

from dataclasses import dataclass
from typing import Optional

from fastapi import Depends, Request
from safir.dependencies.gafaelfawr import auth_logger_dependency
from structlog.stdlib import BoundLogger

from ..factory import ProcessContext
from ..services.manager import FlockManager

__all__ = [
"ContextDependency",
"RequestContext",
"context_dependency",
]


@dataclass(slots=True)
class RequestContext:
"""Holds the incoming request and its surrounding context."""

request: Request
"""Incoming request."""

logger: BoundLogger
"""Request loger, rebound with discovered context."""

manager: FlockManager
"""Global singleton flock manager."""


class ContextDependency:
"""Provide a per-request context as a FastAPI dependency.

Each request gets a `RequestContext`. To save overhead, the portions of
the context that are shared by all requests are collected into the single
process-global `~mobu.factory.ProcessContext` and reused with each
request.
"""

def __init__(self) -> None:
self._process_context: Optional[ProcessContext] = None

async def __call__(
self,
request: Request,
logger: BoundLogger = Depends(auth_logger_dependency),
) -> RequestContext:
"""Create a per-request context."""
if not self._process_context:
raise RuntimeError("ContextDependency not initialized")
return RequestContext(
request=request,
logger=logger,
manager=self._process_context.manager,
)

@property
def process_context(self) -> ProcessContext:
if not self._process_context:
raise RuntimeError("ContextDependency not initialized")
return self._process_context

async def initialize(self) -> None:
"""Initialize the process-wide shared context."""
if self._process_context:
await self._process_context.aclose()
self._process_context = ProcessContext()

async def aclose(self) -> None:
"""Clean up the per-process configuration."""
if self._process_context:
await self._process_context.aclose()
self._process_context = None


context_dependency = ContextDependency()
"""The dependency that will return the per-request context."""
81 changes: 0 additions & 81 deletions src/mobu/dependencies/manager.py

This file was deleted.

74 changes: 74 additions & 0 deletions src/mobu/factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Component factory and process-wide status for mobu."""

from __future__ import annotations

from typing import Optional

import structlog
from aiohttp import ClientSession
from safir.slack.webhook import SlackWebhookClient
from structlog import BoundLogger

from .config import config
from .services.manager import FlockManager

__all__ = ["Factory", "ProcessContext"]


class ProcessContext:
"""Per-process application context.

This object caches all of the per-process singletons that can be reused
for every request.

Attributes
----------
manager
Manager for all running flocks.
session
Shared HTTP client session.
"""

def __init__(self) -> None:
self.session = ClientSession()
self.manager = FlockManager(self.session, structlog.get_logger("mobu"))

async def aclose(self) -> None:
"""Clean up a process context.

Called before shutdown to free resources.
"""
await self.manager.aclose()
await self.session.close()


class Factory:
"""Component factory for mobu.

Uses the contents of a `ProcessContext` to construct the components of an
application on demand.

Parameters
----------
context
Shared process context.
"""

def __init__(
self, context: ProcessContext, logger: Optional[BoundLogger] = None
) -> None:
self._context = context
self._logger = logger if logger else structlog.get_logger("mobu")

def create_slack_webhook_client(self) -> SlackWebhookClient | None:
"""Create a Slack webhook client if configured for Slack alerting.

Returns
-------
SlackWebhookClient or None
Newly-created Slack client, or `None` if Slack alerting is not
configured.
"""
if not config.alert_hook or config.alert_hook == "None":
return None
return SlackWebhookClient(config.alert_hook, "Mobu", self._logger)
Loading