Plugins

The plugin mixin and the OpenAPI plugin (router integration and translator).

Plugin capability: router plugins as a mixin over the base server (D16).

The base server knows nothing about router plugins. This mixin adds them as a capability, composed BEFORE the server class (class MyServer(PluginMixin, BaseServer)), mirroring MiddlewareMixin: its cooperative __init__ peels plugins= (the {name: bool | dict} switches — a dict value enables the plugin and becomes its plug options) and plugin_registry= (extra {name: class} entries merged over default_plugin_registry()), and exposes arm_router(router).

Unlike the middleware chain — assembled once around the base dispatch — plugins are armed onto the ROUTER of each routed application: arm_router is called by RoutedApplication on first route access. Per enabled plugin it ensures the plugin class is registered with genro-routes (idempotent), then batch-plugs the enabled set with a single router.plug([...]) call (genro-routes 0.28.0 accepts a list of plugin dicts); bundled genro-routes plugins (auth/channel/env/logging/pydantic) carry no class in the registry and are plugged by name alone. A composition WITHOUT this mixin exposes no arm_router and arms nothing — RoutedApplication degrades silently.

default_plugin_registry() returns a FRESH dict per call ({"openapi": OpenAPIPlugin} as of Phase 5) — a function, so no module-level mutable registry exists; and importing this module never registers a plugin against genro-routes (no import side effect).

class genro_asgi.plugin_mixin.PluginMixin(**kwargs)[source]

Bases: object

Router-plugin capability mixin, composed BEFORE a server class.

Every server arms the FIXED_PLUGINS (pydantic/openapi) unconditionally — the fixed structure of a routed core. Constructor kwargs peeled here: plugins — the {name: bool | dict} switches tuning the base and adding extras (a dict value becomes the plug options; False drops an EXTRA but is a config error on a fixed plugin); plugin_registry — extra {name: class} entries merged over default_plugin_registry().

Parameters:

kwargs (Any)

property plugins: dict[str, dict[str, Any]]

The materialized {name: options} config of the enabled plugins.

property plugin_registry: dict[str, type[BasePlugin]]

The effective {name: class} registry (defaults + extras).

arm_router(router)[source]

Register and plug every enabled plugin onto router (idempotent).

A plugin carrying a class in the registry is registered with genro-routes first (guarded — never re-registered); the not-yet-attached plugins are then armed with a single batch router.plug([...]) call, so arming twice is safe (already-attached plugins are skipped). Unknown plugin names surface as router.plug errors, not silent no-ops.

Return type:

None

Parameters:

router (Router)

genro_asgi.plugin_mixin.default_plugin_registry()[source]

A fresh {name: class} mapping of the plugins this core arms itself.

Only the transport-dialect plugins that live in this package need a class here (so arm_router can register them). genro-routes’ own bundled plugins are plugged by name without a class.

Return type:

dict[str, type[BasePlugin]]

OpenAPIPlugin — per-handler OpenAPI configuration.

Provides explicit control over OpenAPI schema generation for handlers. Use this plugin to override automatically guessed HTTP methods or add OpenAPI-specific metadata like tags, summary, description, and security.

Accepted config keys (router-level or per-handler):
  • enabled: gate the plugin entirely (default True)

  • method: HTTP method override (e.g. “get”, “post”, “delete”)

  • tags: OpenAPI tags (string or list of strings)

  • summary: summary override for the operation

  • description: description override for the operation

  • deprecated: mark the operation as deprecated (default False)

  • security_scheme: security scheme name (default “BearerAuth”)

  • security: explicit security override (list, or [] for public)

Cross-plugin integration (read by OpenAPITranslator):
  • When the auth plugin is active, security is auto-derived from auth_rule.

  • When the env plugin is active, x-requires is auto-derived from env_requires.

Unlike the old genro-asgi module, this file does NOT register itself against genro-routes at import time (the no-global-state / no-import-side-effect rule): registration is an explicit arming act performed by PluginMixin.arm_router.

class genro_asgi.plugins.openapi.plugin.OpenAPIPlugin(router, **config)[source]

Bases: BasePlugin

OpenAPI plugin for explicit schema control.

By default HTTP methods are guessed from the signature (GET for scalar params, POST for complex types). Use this plugin to override the guessed method or add OpenAPI-specific metadata.

Parameters:
  • router (Any)

  • config (Any)

configure(enabled=True, method=None, tags=None, summary=None, description=None, deprecated=False, security_scheme='BearerAuth', security=None)[source]

Configure OpenAPI plugin options (storage handled by the wrapper).

Return type:

None

Parameters:
  • enabled (bool)

  • method (str | None)

  • tags (str | list[str] | None)

  • summary (str | None)

  • description (str | None)

  • deprecated (bool)

  • security_scheme (str)

  • security (list | None)

entry_metadata(router, entry)[source]

Provide OpenAPI-specific metadata for a handler.

Return type:

dict[str, Any]

Parameters:
  • router (Any)

  • entry (MethodEntry)

OpenAPITranslator — turn genro-routes nodes() output into OpenAPI.

Reads the dialect-neutral description produced by router.nodes() and NEVER re-derives anything from the callables. genro-routes’ pydantic plugin computes and caches, once at decoration time, both the input and the output description of every handler; this translator only reads those neutral blocks:

  • input — entry_info["params"]: schema is the aggregate request JSON schema (request_schema) and fields is the per-parameter list ({name, schema, required, default, kind}). The HTTP method is guessed from the per-parameter schemas (all scalar → GET, else POST), and the query parameters / request body are built from the cached schema — no pydantic model is ever built here;

  • output — entry_info["result"]: {schema, media_type} (the return-type schema the pydantic plugin produced via TypeAdapter);

  • security / x-requires come from the per-entry auth / env plugin config.

Consequently this module imports NO pydantic (and does not inspect callables): pydantic is a concern of genro-routes’ pydantic plugin, which owns the schema derivation. The cached schema blocks keep $defs inline (self-contained per entry); this translator lifts them into a document-level pool, as OpenAPI expects — copying each block before popping $defs so the plugin’s cache is never mutated.

The translator methods are staticmethods by design (the granted builder-style exception, coding rule 4): pure data-logic functions over the neutral node description, holding no instance state.

class genro_asgi.plugins.openapi.translator.OpenAPITranslator[source]

Bases: object

Translate router nodes() output to OpenAPI format.

Modes:
  • openapi: flat format, all paths merged into a single paths dict.

  • h_openapi: hierarchical format preserving the router tree.

static translate_openapi(nodes_data, lazy=False, path_prefix='')[source]

Translate nodes() output to flat OpenAPI format.

Return type:

dict[str, Any]

Parameters:
static translate_h_openapi(nodes_data, lazy=False, path_prefix='')[source]

Translate nodes() output to hierarchical OpenAPI format.

Return type:

dict[str, Any]

Parameters:
static entry_info_to_openapi(name, entry_info)[source]

Convert an entry info dict to an OpenAPI path item.

HTTP method priority: explicit openapi plugin config, else guessed from the per-parameter schemas. Input (query params / request body) is built from the cached params block; the response schema is read from the neutral result block. Nothing is derived from the callable.

Return type:

tuple[dict[str, Any], dict[str, Any]]

Parameters:
static guess_http_method(fields)[source]

Guess the HTTP method from the neutral parameter fields.

GET when every typed parameter carries a scalar JSON schema, else POST. Reads only the cached fields (never the callable); an untyped parameter — schema is None (unannotated, or *args/**kwargs) — does not force POST, matching “no typed params → GET”.

Return type:

str

Parameters:

fields (list[dict[str, Any]])

static schema_to_parameters(schema)[source]

Convert an aggregate request JSON schema to OpenAPI query parameters.

Return type:

list[dict[str, Any]]

Parameters:

schema (dict[str, Any])