Skip to content

Commit

Permalink
Merge pull request #225 from lsst-sqre/tickets/DM-38339
Browse files Browse the repository at this point in the history
DM-38339: Refactor mobu state management
  • Loading branch information
rra committed Mar 21, 2023
2 parents 35efcca + ad0fd2f commit 5480573
Show file tree
Hide file tree
Showing 12 changed files with 353 additions and 157 deletions.
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

0 comments on commit 5480573

Please sign in to comment.