diff --git a/DESIGN.md b/DESIGN.md
index cc37723..91218f9 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -39,7 +39,7 @@ The architecture separates metadata capture (import-time) from document generati
```mermaid
flowchart TD
subgraph Registration ["Import-Time Registration"]
- DEC["@openapi(...) decorator"] --> REG["Thread-safe Registry
_openapi_registry"]
+ DEC["@openapi(...) decorator"] --> REG["Thread-safe OpenAPIRegistry
registry.py"]
end
subgraph Consumers ["On-Demand Consumers"]
diff --git a/README.md b/README.md
index 5eaead5..fde91df 100644
--- a/README.md
+++ b/README.md
@@ -102,12 +102,17 @@ parsing, status codes, or response bodies match unless a validation layer such a
```mermaid
flowchart LR
- DEC["@openapi decorator"] --> REG["_openapi_registry"]
+ DEC["@openapi decorator"] --> REG["OpenAPIRegistry"]
REG --> GEN["spec.generate_openapi_spec()"]
GEN --> EP["GET /api/openapi.json | .yaml"]
UI["render_swagger_ui()
GET /api/docs"] -.->|browser fetches spec| EP
```
+`registry.py` owns `OpenAPIRegistry` and its process-wide `registry` instance.
+Decorators register metadata there; selected-app generation seeds a separate
+registry with only that app's handlers. Swagger UI fetches the generated document
+and does not read registry state directly.
+
## FastAPI comparison
**FastAPI also runs on Azure Functions.** The choice is the HTTP programming
diff --git a/docs/architecture.md b/docs/architecture.md
index 45c9159..b7d7f3d 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -17,7 +17,7 @@ The architecture operates in two phases: import-time registration and on-demand
1. Python imports function modules.
2. `@openapi(...)` decorator executes and registers operation metadata.
-3. Metadata is stored in the thread-safe `_openapi_registry`.
+3. Metadata is stored in the thread-safe `registry` instance of `OpenAPIRegistry`, owned by `registry.py`.
### Phase 2: On-Demand Consumption
@@ -26,7 +26,7 @@ sequenceDiagram
participant Client
participant Endpoint as Function Endpoint
participant Gen as generate_openapi_spec()
- participant Reg as _openapi_registry
+ participant Reg as OpenAPIRegistry
participant UI as render_swagger_ui()
rect rgb(240, 248, 255)
@@ -62,7 +62,7 @@ sequenceDiagram
participant Host as Azure Functions Host
participant Worker as Python Worker
participant Handler as Function Handler
- participant Reg as _openapi_registry
+ participant Reg as OpenAPIRegistry
rect rgb(240, 248, 255)
note over Worker,Reg: Import Time (startup)
@@ -100,7 +100,11 @@ Decorator order is a valid degree of freedom. When `@openapi` is applied *below*
### `--isolate-app` fails closed
-By default discovery seeds into the process-wide global registry, matching the common single-app layout. When several apps are imported in one process, `generate --isolate-app` scans the `--app` target into a fresh, app-scoped registry so each spec is limited to its own routes. Isolation **fails closed**: an explicit `--isolate-app` that cannot be honored (no `--app`, or an `--app` without a resolvable `:variable`) prints an error and exits non-zero rather than silently emitting a non-isolated spec with a success code (`src/azure_functions_openapi/cli.py:171-179,246-253`).
+Direct `scan_endpoint_metadata(app)` calls default to the process-wide registry.
+Python generation with `app=` and CLI `--app module:variable` instead seed a fresh
+app-scoped `OpenAPIRegistry` by canonical handler identity. The compatibility flag
+`--isolate-app` still **fails closed** if no resolvable `module:variable` is supplied.
+Python callers injecting `registry=` retain caller-owned additive behavior.
### Mismatch consequences
@@ -111,7 +115,8 @@ Because route and method are binding-first, `@openapi(route=...)` / `@openapi(me
```mermaid
flowchart TD
INIT["__init__.py
Public API exports"]
- DEC["decorator.py
@openapi + registry"]
+ DEC["decorator.py
@openapi metadata"]
+ REG["registry.py
OpenAPIRegistry + registry"]
OAI["spec.py / generate_openapi_spec()
Spec compiler"]
UTL["utils.py
Schema extraction + validation"]
SUI["swagger_ui.py
Swagger UI rendering"]
@@ -124,7 +129,8 @@ flowchart TD
INIT --> EXC
DEC --> UTL
DEC --> EXC
- OAI --> DEC
+ DEC --> REG
+ OAI --> REG
OAI --> UTL
OAI --> EXC
UTL --> EXC
@@ -136,7 +142,7 @@ flowchart TD
- Provides `@openapi(...)` decorator.
- Validates and sanitizes decorator inputs.
-- Stores operation metadata in `_openapi_registry` (protected by `threading.RLock`).
+- Registers operation metadata in `registry.py`'s shared `registry` instance.
- Exposes `get_openapi_registry()` snapshot accessor.
- Tags default to `['default']` when not provided; invalid route path or operation ID raises `ValueError`.
@@ -147,6 +153,12 @@ flowchart TD
- Supports OpenAPI 3.0.0, 3.1.0, and 3.2.0 output (3.1/3.2 convert `nullable` to union types, `example` to `examples`; 3.2.0 is a backward-compatible superset of 3.1.0).
- Resolves routes, methods, request/response schemas, Pydantic model components, and security schemes.
+### `registry.py`
+
+- Owns the thread-safe `OpenAPIRegistry` container and default `registry` instance.
+- Guards metadata access with an `RLock` and provides detached snapshots.
+- Supports isolated app-scoped generation without another registry implementation.
+
### `utils.py`
- Pydantic v2 schema extraction (`model_to_schema`).
@@ -235,7 +247,7 @@ Every ceiling is more than three times the measured baseline. A separate, noise-
### Thread-Safe Registration
-The `_openapi_registry` is protected by `threading.RLock`, ensuring safe concurrent decorator execution during module import.
+`OpenAPIRegistry` protects its entries with `threading.RLock`, ensuring safe concurrent decorator execution during module import.
### Extension Points
@@ -247,7 +259,7 @@ The `_openapi_registry` is protected by `threading.RLock`, ensuring safe concurr
`register_openapi_metadata()` in `decorator.py` allows external packages to register route metadata without using the `@openapi` decorator. This is the integration contract for ecosystem packages that define their own HTTP endpoints and want OpenAPI documentation generated by this package.
-The function accepts a similar core metadata shape to `@openapi(...)` (path, method, operation_id, summary, request/response schemas, etc.) and writes directly to the shared `_openapi_registry`. Once registered, routes appear in the generated spec alongside decorator-registered routes.
+The function accepts a similar core metadata shape to `@openapi(...)` (path, method, operation_id, summary, request/response schemas, etc.) and writes to the shared `registry` unless one is injected. Unbound programmatic entries appear in registry-based generation, but are not seeded into selected-app generation.
**Reference consumer:** [`azure-functions-langgraph`](https://github.com/yeongseon/azure-functions-langgraph-python) uses its bridge module (`azure_functions_langgraph.openapi.register_with_openapi`) to read route metadata from its `get_app_metadata()` API and forward it to `register_openapi_metadata()`. This pattern demonstrates how any Azure Functions package can contribute routes to the OpenAPI spec without depending on the `@openapi` decorator.
@@ -264,7 +276,7 @@ The architecture intentionally keeps bridge implementation in the *consumer* pac
- OpenAPI spec generation from decorated handlers and programmatic metadata
- Swagger UI rendering with security defaults
- CLI spec generation for CI pipelines
-- The `_openapi_registry` as the single source of truth for operation metadata
+- `OpenAPIRegistry` as the operation-metadata container, with a shared default and isolated app-scoped instances
- `register_openapi_metadata()` as the integration contract for ecosystem packages
## What this package does not own