Source code for dmr.openapi.core.context

from dataclasses import dataclass
from typing import TYPE_CHECKING, ClassVar

from dmr.openapi.core.merger import ConfigMerger
from dmr.openapi.core.registry import (
    OperationIdRegistry,
    SchemaRegistry,
    SecuritySchemeRegistry,
)
from dmr.openapi.generators import (
    ComponentParserGenerator,
    OperationIdGenerator,
    ParameterGenerator,
    ResponseGenerator,
    SchemaGenerator,
    SecuritySchemeGenerator,
)
from dmr.openapi.mappers.example import seed_example_factory
from dmr.openapi.objects import Components

if TYPE_CHECKING:
    from dmr.openapi.config import OpenAPIConfig


@dataclass(slots=True, frozen=True)
class RegistryContainer:
    """Container for registries."""

    operation_id: OperationIdRegistry
    schema: SchemaRegistry
    security_scheme: SecuritySchemeRegistry


@dataclass(slots=True, frozen=True)
class GeneratorContainer:
    """Container for generators."""

    operation_id: OperationIdGenerator
    schema: SchemaGenerator
    component_parsers: ComponentParserGenerator
    response: ResponseGenerator
    security_scheme: SecuritySchemeGenerator
    parameter: ParameterGenerator


[docs] class OpenAPIContext: """ Context for OpenAPI specification generation. Maintains shared state and generators used across the OpenAPI generation process. Provides access to different generators. Subclass this context and override the ``*_cls`` attributes to customize schema generation. Each class is instantiated with the current context. .. versionchanged:: 0.16.0 Added class-level overrides for generators and the configuration merger. .. versionchanged:: 0.16.0 Added the :meth:`seed_examples` hook. Now all used classes can be customized via subclassing and overriding class-level variables. """ __slots__ = ( 'config', 'config_merger', 'generators', 'registries', ) #: Merges configuration with generated paths and components. config_merger_cls: ClassVar[type[ConfigMerger]] = ConfigMerger #: Generates and registers operation IDs. operation_id_cls: ClassVar[type[OperationIdGenerator]] = ( OperationIdGenerator ) #: Resolves annotations into schemas and references. schema_cls: ClassVar[type[SchemaGenerator]] = SchemaGenerator #: Generates request bodies and parameters from endpoint components. component_parsers_cls: ClassVar[type[ComponentParserGenerator]] = ( ComponentParserGenerator ) #: Generates endpoint responses. response_cls: ClassVar[type[ResponseGenerator]] = ResponseGenerator #: Generates security requirements and registers security schemes. security_scheme_cls: ClassVar[type[SecuritySchemeGenerator]] = ( SecuritySchemeGenerator ) #: Generates parameters from models. parameter_cls: ClassVar[type[ParameterGenerator]] = ParameterGenerator #: Operation ID registry. operation_id_registry_cls: ClassVar[type[OperationIdRegistry]] = ( OperationIdRegistry ) #: Schema registry. schema_registry_cls: ClassVar[type[SchemaRegistry]] = SchemaRegistry #: Security schemes registry. security_scheme_registry_cls: ClassVar[type[SecuritySchemeRegistry]] = ( SecuritySchemeRegistry ) def __init__( self, config: 'OpenAPIConfig | None' = None, ) -> None: """Initialize the OpenAPI context.""" from dmr.openapi.config import default_config # noqa: PLC0415 self.config = config or default_config() self.config_merger = self.config_merger_cls(self) # Initialize registries: self.registries = RegistryContainer( operation_id=self.operation_id_registry_cls(), schema=self.schema_registry_cls(), security_scheme=self.security_scheme_registry_cls(), ) # Initialize generators: self.generators = GeneratorContainer( operation_id=self.operation_id_cls(self), schema=self.schema_cls(self), component_parsers=self.component_parsers_cls(self), response=self.response_cls(self), security_scheme=self.security_scheme_cls(self), parameter=self.parameter_cls(self), ) # Last, so that overrides of this method see a ready context: self.seed_examples()
[docs] def seed_examples(self) -> None: """ Seed the generation of examples for this schema. One context builds one schema, so this runs exactly once per schema. All its examples then come from a single random stream, which is what makes them differ from each other. Override it to seed from something other than :data:`~dmr.settings.Settings.openapi_examples_seed`, or to leave the factory alone entirely. .. versionadded:: 0.16.0 """ seed_example_factory()
[docs] def get_components(self) -> Components: """ Resolve all components from own and external schemas. .. versionadded:: 0.13.0 """ return Components( # TODO: support other components, not just `schema`: schemas=self.registries.schema.schemas or None, security_schemes=self.registries.security_scheme.schemes or None, )