From fced40af598a847b479f35080f13b6e796aa7697 Mon Sep 17 00:00:00 2001 From: Vishesh 'ironeagle' Bangotra Date: Tue, 13 Oct 2026 03:26:51 +0530 Subject: [PATCH] feat(standards): codify codegen, ship generic Tier-B infra, add get_app_template + prompts; fix docs:// regressions P0: fix doc:// -> docs:// across standards src/server/registry and regenerate stale mongo-ops bundle. P1: build recipe + spec-first routing now document the openapi-first codegen fast path (models/routes with --inject-request/--package). P4: CRUD template documents the model-vs-document id rule (BaseDocument owns id). P5: authless guidance (omit security keys). P6: template gains generic infra (http_cache, keyed SSE bus, KV port+in-memory+Redis), interfaces/openapi.py + .dockerignore + generic build test registered as components, and a get_app_template tool; genericity matrix marks LLM pool + human-in-loop as reference-app patterns. P7: intake/build/start_app/trial prompts. P8: template ships tests/integration/test_openapi_app_builds.py. Rebuilt standards + openapi-first bundles and re-collected. --- build.py | 18 +- mcp_server.py | 6 +- .../mcp/modules/mongo_ops.cache.backend.json | 81 +- .../mcp/modules/mongo_ops.cache.config.json | 34 +- .../modules/mongo_ops.cache.in_memory.json | 154 +- mongo-ops/mcp/modules/mongo_ops.cache.json | 670 +++---- .../mongo_ops.cache.redis_backend.json | 123 +- .../modules/mongo_ops.cache.repository.json | 160 +- .../mcp/modules/mongo_ops.connection.json | 48 +- mongo-ops/mcp/modules/mongo_ops.json | 1592 ++++++----------- mongo-ops/mcp/modules/mongo_ops.models.json | 72 +- .../modules/mongo_ops.populate.engine.json | 120 +- mongo-ops/mcp/modules/mongo_ops.populate.json | 195 +- .../mcp/modules/mongo_ops.populate.rules.json | 41 +- mongo-ops/mcp/modules/mongo_ops.registry.json | 141 +- .../mcp/modules/mongo_ops.repository.json | 208 +-- .../mcp/modules/mongo_ops.transactions.json | 74 +- mongo-ops/mcp/nav.json | 30 +- openapi-first/lib/app/index.html | 8 +- openapi-first/lib/client/index.html | 6 +- openapi-first/lib/codegen/index.html | 46 +- openapi-first/lib/codegen_routes/index.html | 80 +- openapi-first/lib/index.html | 12 +- .../lib/openapi_first/app/index.html | 8 +- .../lib/openapi_first/client/index.html | 6 +- .../lib/openapi_first/codegen/index.html | 46 +- .../openapi_first/codegen_routes/index.html | 80 +- openapi-first/lib/openapi_first/index.html | 8 +- .../templates/crud_app/index.html | 12 +- .../templates/health_app/index.html | 4 +- .../templates/model_app/index.html | 12 +- .../templates/vet_app/index.html | 10 +- openapi-first/lib/search/search_index.json | 2 +- openapi-first/lib/sitemap.xml.gz | Bin 127 -> 127 bytes .../mcp/modules/openapi_first.app.json | 47 +- .../mcp/modules/openapi_first.binder.json | 34 +- .../mcp/modules/openapi_first.cli.json | 34 +- .../mcp/modules/openapi_first.client.json | 38 +- .../mcp/modules/openapi_first.codegen.json | 34 +- .../modules/openapi_first.codegen_routes.json | 22 +- .../mcp/modules/openapi_first.errors.json | 6 +- openapi-first/mcp/modules/openapi_first.json | 1102 +++--------- .../mcp/modules/openapi_first.loader.json | 41 +- .../mcp/modules/openapi_first.security.json | 74 +- ...openapi_first.templates.crud_app.data.json | 17 +- .../openapi_first.templates.crud_app.json | 98 +- ...openapi_first.templates.crud_app.main.json | 13 +- ...enapi_first.templates.crud_app.routes.json | 28 +- ...irst.templates.crud_app.test_crud_app.json | 38 +- .../openapi_first.templates.health_app.json | 17 +- ...enapi_first.templates.health_app.main.json | 13 +- ...api_first.templates.health_app.routes.json | 2 +- .../mcp/modules/openapi_first.templates.json | 772 ++------ ...penapi_first.templates.model_app.data.json | 31 +- .../openapi_first.templates.model_app.json | 132 +- ...penapi_first.templates.model_app.main.json | 13 +- ...napi_first.templates.model_app.models.json | 13 +- ...napi_first.templates.model_app.routes.json | 35 +- ...st.templates.model_app.test_model_app.json | 38 +- .../openapi_first.templates.vet_app.data.json | 190 +- .../openapi_first.templates.vet_app.json | 525 ++---- .../openapi_first.templates.vet_app.main.json | 29 +- ...penapi_first.templates.vet_app.models.json | 90 +- ...penapi_first.templates.vet_app.routes.json | 135 +- .../openapi_first.templates.vet_app.sse.json | 25 +- ..._first.templates.vet_app.test_vet_app.json | 54 +- openapi-first/wiki/01_overview/index.html | 14 +- openapi-first/wiki/02_components/index.html | 12 +- .../03_use_cases/01_quickstart/index.html | 16 +- .../wiki/03_use_cases/02_templates/index.html | 2 +- .../wiki/03_use_cases/03_client/index.html | 18 +- .../wiki/03_use_cases/04_codegen/index.html | 87 +- openapi-first/wiki/05_security/index.html | 12 +- .../wiki/06_error_handling/index.html | 10 +- openapi-first/wiki/07_testing/index.html | 8 +- openapi-first/wiki/search/search_index.json | 2 +- openapi-first/wiki/sitemap.xml.gz | Bin 127 -> 127 bytes registry.py | 194 +- standards/lib/01_build_recipe/index.html | 225 ++- .../lib/04_spec_first_routing/index.html | 47 +- standards/lib/05_crud_template/index.html | 87 +- standards/lib/12_genericity_matrix/index.html | 84 +- standards/lib/13_library_registry/index.html | 6 +- standards/lib/search/search_index.json | 2 +- standards/mcp/components.json | 48 + standards/mcp/modules/build_recipe.json | 2 +- standards/mcp/modules/crud_template.json | 2 +- standards/mcp/modules/genericity_matrix.json | 2 +- standards/mcp/modules/spec_first_routing.json | 2 +- .../mcp/templates/app/infra/adapters/kv.py | 147 ++ .../mcp/templates/app/infra/adapters/sse.py | 54 + .../mcp/templates/app/infra/http_cache.py | 229 +++ standards/mcp/templates/app/pyproject.toml | 1 + .../integration/test_openapi_app_builds.py | 44 + standards/src/lib/01_build_recipe.md | 110 +- standards/src/lib/04_spec_first_routing.md | 32 +- standards/src/lib/05_crud_template.md | 32 +- standards/src/lib/12_genericity_matrix.md | 48 +- standards/src/lib/13_library_registry.md | 6 +- .../src/templates/app/infra/adapters/kv.py | 147 ++ .../src/templates/app/infra/adapters/sse.py | 54 + .../src/templates/app/infra/http_cache.py | 229 +++ standards/src/templates/app/pyproject.toml | 1 + .../integration/test_openapi_app_builds.py | 44 + standards/src/wiki/03_start_an_app.md | 25 +- standards/src/wiki/index.md | 4 +- standards/wiki/03_start_an_app/index.html | 25 +- standards/wiki/index.html | 4 +- standards/wiki/search/search_index.json | 2 +- 109 files changed, 4082 insertions(+), 5885 deletions(-) create mode 100644 standards/mcp/templates/app/infra/adapters/kv.py create mode 100644 standards/mcp/templates/app/infra/adapters/sse.py create mode 100644 standards/mcp/templates/app/infra/http_cache.py create mode 100644 standards/mcp/templates/app/tests/integration/test_openapi_app_builds.py create mode 100644 standards/src/templates/app/infra/adapters/kv.py create mode 100644 standards/src/templates/app/infra/adapters/sse.py create mode 100644 standards/src/templates/app/infra/http_cache.py create mode 100644 standards/src/templates/app/tests/integration/test_openapi_app_builds.py diff --git a/build.py b/build.py index 91a83f1..ff2b40f 100644 --- a/build.py +++ b/build.py @@ -145,7 +145,7 @@ def build_bundle(folder: Path, dry_run: bool = False) -> None: write_module(module, path.read_text(encoding="utf-8")) nav.append({ "module": module, - "resource": f"doc://modules/{module}", + "resource": f"docs://modules/{module}", "title": _read_title(path), }) @@ -185,10 +185,16 @@ _TEMPLATE_COMPONENTS: list[dict[str, str]] = [ "description": "Optional pure converters between domain types."}, {"kind": "routes", "path": "interfaces/item/routes.py", "placeholder": "resource", "description": "Thin HTTP handlers bound to operationIds."}, + {"kind": "openapi-routes", "path": "interfaces/openapi.py", "placeholder": "", + "description": "Route registry: flat re-export of every handler namespace."}, {"kind": "test-model", "path": "tests/unit/test_item_models.py", "placeholder": "resource", "description": "Unit test for the resource model."}, {"kind": "test-routes", "path": "tests/integration/test_item_routes.py", "placeholder": "resource", "description": "Integration test at the HTTP edge."}, + {"kind": "test-openapi-build", "path": "tests/integration/test_openapi_app_builds.py", "placeholder": "", + "description": "Generic test: every spec operationId resolves and the app builds."}, + {"kind": "test-pipeline", "path": "tests/unit/test_item_sync.py", "placeholder": "", + "description": "Unit test for the smallest pipeline form."}, # Per-family pipeline scaffold (seed: example/Example). {"kind": "pipeline-abc", "path": "pipelines/example/abc.py", "placeholder": "family", "description": "Port contracts for a pipeline family."}, @@ -233,6 +239,8 @@ _TEMPLATE_COMPONENTS: list[dict[str, str]] = [ "description": "Packaging + dependency pins."}, {"kind": "dockerfile", "path": "Dockerfile", "placeholder": "", "description": "API + worker image with private-index wiring."}, + {"kind": "dockerignore", "path": ".dockerignore", "placeholder": "", + "description": "Docker build-context exclusions."}, {"kind": "compose", "path": "docker-compose.yaml", "placeholder": "", "description": "api + worker + mongo compose file."}, {"kind": "env-example", "path": ".env.example", "placeholder": "", @@ -243,6 +251,14 @@ _TEMPLATE_COMPONENTS: list[dict[str, str]] = [ "description": "Repository factory (base/populating/cached)."}, {"kind": "service-adapter", "path": "infra/adapters/service.py", "placeholder": "", "description": "Generic CRUD service adaptor."}, + {"kind": "http-cache", "path": "infra/http_cache.py", "placeholder": "", + "description": "Response cache decorator + Redis store (generation invalidation)."}, + {"kind": "progress-bus", "path": "infra/adapters/sse.py", "placeholder": "", + "description": "In-memory SSE progress bus (keyed by an event attribute)."}, + {"kind": "kv-store", "path": "infra/adapters/kv.py", "placeholder": "", + "description": "KV port + in-memory fallback + optional Redis adaptor."}, + {"kind": "package-init", "path": "domain/__init__.py", "placeholder": "", + "description": "Empty package marker; add an __init__.py to every package dir."}, {"kind": "script-seed", "path": "scripts/seed.py", "placeholder": "", "description": "Operator seed script."}, {"kind": "script-drop", "path": "scripts/drop.py", "placeholder": "", diff --git a/mcp_server.py b/mcp_server.py index 2195348..8a0f231 100644 --- a/mcp_server.py +++ b/mcp_server.py @@ -33,15 +33,15 @@ class MCPServer: # ------------------------------------------------------------------ def _register_resources(self) -> None: - @self.app.resource("doc://index") + @self.app.resource("docs://index") def index(): return self._read_json(self.mcp_root / "index.json") - @self.app.resource("doc://nav") + @self.app.resource("docs://nav") def nav(): return self._read_json(self.mcp_root / "nav.json") - @self.app.resource("doc://modules/{module}") + @self.app.resource("docs://modules/{module}") def module(module: str): return self._read_json(self.mcp_root / "modules" / f"{module}.json") diff --git a/mongo-ops/mcp/modules/mongo_ops.cache.backend.json b/mongo-ops/mcp/modules/mongo_ops.cache.backend.json index 0365e80..972d9fe 100644 --- a/mongo-ops/mcp/modules/mongo_ops.cache.backend.json +++ b/mongo-ops/mcp/modules/mongo_ops.cache.backend.json @@ -2,49 +2,14 @@ "module": "mongo_ops.cache.backend", "content": { "path": "mongo_ops.cache.backend", - "docstring": "Cache backend abstraction.", + "docstring": "# Summary\n\nCache backend abstraction.", "objects": { - "ABC": { - "name": "ABC", - "kind": "alias", - "path": "mongo_ops.cache.backend.ABC", - "signature": "", - "docstring": null - }, - "abstractmethod": { - "name": "abstractmethod", - "kind": "alias", - "path": "mongo_ops.cache.backend.abstractmethod", - "signature": "", - "docstring": null - }, - "dataclass": { - "name": "dataclass", - "kind": "alias", - "path": "mongo_ops.cache.backend.dataclass", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.cache.backend.Optional", - "signature": "", - "docstring": null - }, - "ObjectId": { - "name": "ObjectId", - "kind": "alias", - "path": "mongo_ops.cache.backend.ObjectId", - "signature": "", - "docstring": null - }, "CacheStats": { "name": "CacheStats", "kind": "class", "path": "mongo_ops.cache.backend.CacheStats", - "signature": "", - "docstring": null, + "signature": "CacheStats(hits: int = 0, misses: int = 0, sets: int = 0, deletes: int = 0, current_size: int = 0, max_size: int = 0)", + "docstring": "Snapshot of cache usage and activity counters.\n\nAttributes:\n hits (int):\n Number of get() calls that found a value.\n misses (int):\n Number of get() calls that returned None.\n sets (int):\n Number of values written to the cache.\n deletes (int):\n Number of keys removed.\n current_size (int):\n Number of entries currently held.\n max_size (int):\n Maximum number of entries the cache allows (0 = unbounded).", "members": { "hits": { "name": "hits", @@ -94,8 +59,8 @@ "name": "CircularReferenceError", "kind": "class", "path": "mongo_ops.cache.backend.CircularReferenceError", - "signature": "", - "docstring": null, + "signature": "CircularReferenceError(collection: str, doc_id: ObjectId, path: list[str])", + "docstring": "Raised when population detects a cycle in the reference graph.\n\nAttributes:\n collection (str):\n Collection where the cycle was detected.\n doc_id (ObjectId):\n Document ID where the cycle was detected.\n path (list[str]):\n Ordered labels describing the visited reference path.", "members": { "collection": { "name": "collection", @@ -124,64 +89,64 @@ "name": "CacheBackend", "kind": "class", "path": "mongo_ops.cache.backend.CacheBackend", - "signature": "", - "docstring": null, + "signature": null, + "docstring": "Abstract interface for cache backends.\n\nImplementations store byte-encoded values keyed by string, track usage\nstatistics, and manage their own lifecycle. The in-memory and Redis\nbackends both implement this contract.", "members": { "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str) -> bytes | None", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None) -> None", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds. When None, the backend default\n applies." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str) -> None", + "docstring": "Remove a key from the cache.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str) -> bool", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str) -> None", + "docstring": "Remove all keys matching a glob pattern.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats() -> CacheStats", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: A copy of the current stats counters." }, "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize() -> None", + "docstring": "Start background resources owned by the backend.\n\nShould be called once during application startup, after the\nrepositories are connected." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown() -> None", + "docstring": "Stop and release background resources.\n\nShould be called once during application shutdown." } } } diff --git a/mongo-ops/mcp/modules/mongo_ops.cache.config.json b/mongo-ops/mcp/modules/mongo_ops.cache.config.json index 638800f..3133291 100644 --- a/mongo-ops/mcp/modules/mongo_ops.cache.config.json +++ b/mongo-ops/mcp/modules/mongo_ops.cache.config.json @@ -2,42 +2,14 @@ "module": "mongo_ops.cache.config", "content": { "path": "mongo_ops.cache.config", - "docstring": "Cache configuration.", + "docstring": "# Summary\n\nCache configuration.", "objects": { - "dataclass": { - "name": "dataclass", - "kind": "alias", - "path": "mongo_ops.cache.config.dataclass", - "signature": "", - "docstring": null - }, - "Literal": { - "name": "Literal", - "kind": "alias", - "path": "mongo_ops.cache.config.Literal", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.cache.config.Optional", - "signature": "", - "docstring": null - }, - "Redis": { - "name": "Redis", - "kind": "alias", - "path": "mongo_ops.cache.config.Redis", - "signature": "", - "docstring": null - }, "CacheConfig": { "name": "CacheConfig", "kind": "class", "path": "mongo_ops.cache.config.CacheConfig", - "signature": "", - "docstring": null, + "signature": "CacheConfig(enabled: bool = True, backend: Literal['memory', 'redis'] = 'memory', redis_client: Redis | None = None, default_ttl: int = 300, max_entries: int = 10000, key_prefix: str = '', cleanup_interval: int = 60)", + "docstring": "Configuration for the cached repository layer.\n\nAttributes:\n enabled (bool):\n Whether caching is active for the repository.\n backend (Literal[\"memory\", \"redis\"]):\n Which backend to use. Defaults to \"memory\".\n redis_client (Optional[Redis]):\n Redis client required when backend is \"redis\".\n default_ttl (int):\n Default time-to-live for cached entries, in seconds.\n max_entries (int):\n Maximum entries for the in-memory backend.\n key_prefix (str):\n Prefix applied to cache keys; defaults to the collection name\n when empty.\n cleanup_interval (int):\n Interval (seconds) for the in-memory expiry sweep.", "members": { "enabled": { "name": "enabled", diff --git a/mongo-ops/mcp/modules/mongo_ops.cache.in_memory.json b/mongo-ops/mcp/modules/mongo_ops.cache.in_memory.json index 7650a6e..3ba9c01 100644 --- a/mongo-ops/mcp/modules/mongo_ops.cache.in_memory.json +++ b/mongo-ops/mcp/modules/mongo_ops.cache.in_memory.json @@ -2,126 +2,70 @@ "module": "mongo_ops.cache.in_memory", "content": { "path": "mongo_ops.cache.in_memory", - "docstring": null, + "docstring": "# Summary\n\nIn-memory cache backend with TTL-based eviction and encode/decode helpers.", "objects": { - "asyncio": { - "name": "asyncio", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.asyncio", - "signature": "", - "docstring": null - }, - "heapq": { - "name": "heapq", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.heapq", - "signature": "", - "docstring": null - }, - "json": { - "name": "json", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.json", - "signature": "", - "docstring": null - }, - "OrderedDict": { - "name": "OrderedDict", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.OrderedDict", - "signature": "", - "docstring": null - }, - "datetime": { - "name": "datetime", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.datetime", - "signature": "", - "docstring": null - }, - "timedelta": { - "name": "timedelta", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.timedelta", - "signature": "", - "docstring": null - }, - "timezone": { - "name": "timezone", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.timezone", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.Optional", - "signature": "", - "docstring": null - }, "CacheBackend": { "name": "CacheBackend", "kind": "class", "path": "mongo_ops.cache.in_memory.CacheBackend", - "signature": "", - "docstring": null, + "signature": null, + "docstring": "Abstract interface for cache backends.\n\nImplementations store byte-encoded values keyed by string, track usage\nstatistics, and manage their own lifecycle. The in-memory and Redis\nbackends both implement this contract.", "members": { "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str)", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None)", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds. When None, the backend default\n applies." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str)", + "docstring": "Remove a key from the cache.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str)", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str)", + "docstring": "Remove all keys matching a glob pattern.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats()", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: A copy of the current stats counters." }, "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize()", + "docstring": "Start background resources owned by the backend.\n\nShould be called once during application startup, after the\nrepositories are connected." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown()", + "docstring": "Stop and release background resources.\n\nShould be called once during application shutdown." } } }, @@ -129,49 +73,49 @@ "name": "CacheStats", "kind": "class", "path": "mongo_ops.cache.in_memory.CacheStats", - "signature": "", - "docstring": null, + "signature": "CacheStats(hits: int = 0, misses: int = 0, sets: int = 0, deletes: int = 0, current_size: int = 0, max_size: int = 0)", + "docstring": "Snapshot of cache usage and activity counters.\n\nAttributes:\n hits (int):\n Number of get() calls that found a value.\n misses (int):\n Number of get() calls that returned None.\n sets (int):\n Number of values written to the cache.\n deletes (int):\n Number of keys removed.\n current_size (int):\n Number of entries currently held.\n max_size (int):\n Maximum number of entries the cache allows (0 = unbounded).", "members": { "hits": { "name": "hits", "kind": "attribute", "path": "mongo_ops.cache.in_memory.CacheStats.hits", - "signature": "", + "signature": null, "docstring": null }, "misses": { "name": "misses", "kind": "attribute", "path": "mongo_ops.cache.in_memory.CacheStats.misses", - "signature": "", + "signature": null, "docstring": null }, "sets": { "name": "sets", "kind": "attribute", "path": "mongo_ops.cache.in_memory.CacheStats.sets", - "signature": "", + "signature": null, "docstring": null }, "deletes": { "name": "deletes", "kind": "attribute", "path": "mongo_ops.cache.in_memory.CacheStats.deletes", - "signature": "", + "signature": null, "docstring": null }, "current_size": { "name": "current_size", "kind": "attribute", "path": "mongo_ops.cache.in_memory.CacheStats.current_size", - "signature": "", + "signature": null, "docstring": null }, "max_size": { "name": "max_size", "kind": "attribute", "path": "mongo_ops.cache.in_memory.CacheStats.max_size", - "signature": "", + "signature": null, "docstring": null } } @@ -180,64 +124,64 @@ "name": "InMemoryCacheBackend", "kind": "class", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend", - "signature": "", - "docstring": null, + "signature": "InMemoryCacheBackend(max_entries: int = 10000, default_ttl: int = 300, cleanup_interval: int = 60)", + "docstring": "Cache backend backed by an in-memory dict with TTL expiry.\n\nEntries are stored in an OrderedDict for LRU-compatible eviction and a\nmin-heap of expiry timestamps drives periodic removal of stale entries.\n\nNotes:\n **Thread safety:**\n\n All operations take an asyncio lock; the backend is safe for\n concurrent use within a single event loop.", "members": { "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize() -> None", + "docstring": "Start the periodic expired-entry cleanup task." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown() -> None", + "docstring": "Cancel and await the cleanup task." }, "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str) -> bytes | None", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None) -> None", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds; defaults to the backend default." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str) -> None", + "docstring": "Remove a key from the cache.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str) -> bool", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str) -> None", + "docstring": "Remove all keys matching a glob pattern.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats() -> CacheStats", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: A copy of the current stats counters." } } }, @@ -245,15 +189,15 @@ "name": "encode_value", "kind": "function", "path": "mongo_ops.cache.in_memory.encode_value", - "signature": "", - "docstring": null + "signature": "encode_value(value: dict) -> bytes", + "docstring": "Encode a dict into cache-ready bytes.\n\nNon-serializable values (e.g., ObjectId) are coerced with str().\n\nArgs:\n value (dict):\n The dictionary to encode.\n\nReturns:\n bytes: UTF-8 JSON bytes." }, "decode_value": { "name": "decode_value", "kind": "function", "path": "mongo_ops.cache.in_memory.decode_value", - "signature": "", - "docstring": null + "signature": "decode_value(data: bytes) -> dict", + "docstring": "Decode cache bytes back into a dict.\n\nArgs:\n data (bytes):\n UTF-8 JSON bytes produced by encode_value().\n\nReturns:\n dict: The decoded dictionary." } } } diff --git a/mongo-ops/mcp/modules/mongo_ops.cache.json b/mongo-ops/mcp/modules/mongo_ops.cache.json index 66908f5..e0e5579 100644 --- a/mongo-ops/mcp/modules/mongo_ops.cache.json +++ b/mongo-ops/mcp/modules/mongo_ops.cache.json @@ -2,70 +2,70 @@ "module": "mongo_ops.cache", "content": { "path": "mongo_ops.cache", - "docstring": null, + "docstring": "# Summary\n\nCache backends and configuration for mongo-ops.", "objects": { "CacheBackend": { "name": "CacheBackend", "kind": "class", "path": "mongo_ops.cache.CacheBackend", - "signature": "", - "docstring": null, + "signature": null, + "docstring": "Abstract interface for cache backends.\n\nImplementations store byte-encoded values keyed by string, track usage\nstatistics, and manage their own lifecycle. The in-memory and Redis\nbackends both implement this contract.", "members": { "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.CacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str)", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.CacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None)", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds. When None, the backend default\n applies." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.CacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str)", + "docstring": "Remove a key from the cache.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.CacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str)", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.CacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str)", + "docstring": "Remove all keys matching a glob pattern.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.CacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats()", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: A copy of the current stats counters." }, "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.CacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize()", + "docstring": "Start background resources owned by the backend.\n\nShould be called once during application startup, after the\nrepositories are connected." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.CacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown()", + "docstring": "Stop and release background resources.\n\nShould be called once during application shutdown." } } }, @@ -73,49 +73,49 @@ "name": "CacheStats", "kind": "class", "path": "mongo_ops.cache.CacheStats", - "signature": "", - "docstring": null, + "signature": "CacheStats(hits: int = 0, misses: int = 0, sets: int = 0, deletes: int = 0, current_size: int = 0, max_size: int = 0)", + "docstring": "Snapshot of cache usage and activity counters.\n\nAttributes:\n hits (int):\n Number of get() calls that found a value.\n misses (int):\n Number of get() calls that returned None.\n sets (int):\n Number of values written to the cache.\n deletes (int):\n Number of keys removed.\n current_size (int):\n Number of entries currently held.\n max_size (int):\n Maximum number of entries the cache allows (0 = unbounded).", "members": { "hits": { "name": "hits", "kind": "attribute", "path": "mongo_ops.cache.CacheStats.hits", - "signature": "", + "signature": null, "docstring": null }, "misses": { "name": "misses", "kind": "attribute", "path": "mongo_ops.cache.CacheStats.misses", - "signature": "", + "signature": null, "docstring": null }, "sets": { "name": "sets", "kind": "attribute", "path": "mongo_ops.cache.CacheStats.sets", - "signature": "", + "signature": null, "docstring": null }, "deletes": { "name": "deletes", "kind": "attribute", "path": "mongo_ops.cache.CacheStats.deletes", - "signature": "", + "signature": null, "docstring": null }, "current_size": { "name": "current_size", "kind": "attribute", "path": "mongo_ops.cache.CacheStats.current_size", - "signature": "", + "signature": null, "docstring": null }, "max_size": { "name": "max_size", "kind": "attribute", "path": "mongo_ops.cache.CacheStats.max_size", - "signature": "", + "signature": null, "docstring": null } } @@ -124,28 +124,28 @@ "name": "CircularReferenceError", "kind": "class", "path": "mongo_ops.cache.CircularReferenceError", - "signature": "", - "docstring": null, + "signature": "CircularReferenceError(collection: str, doc_id: ObjectId, path: list[str])", + "docstring": "Raised when population detects a cycle in the reference graph.\n\nAttributes:\n collection (str):\n Collection where the cycle was detected.\n doc_id (ObjectId):\n Document ID where the cycle was detected.\n path (list[str]):\n Ordered labels describing the visited reference path.", "members": { "collection": { "name": "collection", "kind": "attribute", "path": "mongo_ops.cache.CircularReferenceError.collection", - "signature": "", + "signature": null, "docstring": null }, "doc_id": { "name": "doc_id", "kind": "attribute", "path": "mongo_ops.cache.CircularReferenceError.doc_id", - "signature": "", + "signature": null, "docstring": null }, "path": { "name": "path", "kind": "attribute", "path": "mongo_ops.cache.CircularReferenceError.path", - "signature": "", + "signature": null, "docstring": null } } @@ -154,56 +154,56 @@ "name": "CacheConfig", "kind": "class", "path": "mongo_ops.cache.CacheConfig", - "signature": "", - "docstring": null, + "signature": "CacheConfig(enabled: bool = True, backend: Literal['memory', 'redis'] = 'memory', redis_client: Redis | None = None, default_ttl: int = 300, max_entries: int = 10000, key_prefix: str = '', cleanup_interval: int = 60)", + "docstring": "Configuration for the cached repository layer.\n\nAttributes:\n enabled (bool):\n Whether caching is active for the repository.\n backend (Literal[\"memory\", \"redis\"]):\n Which backend to use. Defaults to \"memory\".\n redis_client (Optional[Redis]):\n Redis client required when backend is \"redis\".\n default_ttl (int):\n Default time-to-live for cached entries, in seconds.\n max_entries (int):\n Maximum entries for the in-memory backend.\n key_prefix (str):\n Prefix applied to cache keys; defaults to the collection name\n when empty.\n cleanup_interval (int):\n Interval (seconds) for the in-memory expiry sweep.", "members": { "enabled": { "name": "enabled", "kind": "attribute", "path": "mongo_ops.cache.CacheConfig.enabled", - "signature": "", + "signature": null, "docstring": null }, "backend": { "name": "backend", "kind": "attribute", "path": "mongo_ops.cache.CacheConfig.backend", - "signature": "", + "signature": null, "docstring": null }, "redis_client": { "name": "redis_client", "kind": "attribute", "path": "mongo_ops.cache.CacheConfig.redis_client", - "signature": "", + "signature": null, "docstring": null }, "default_ttl": { "name": "default_ttl", "kind": "attribute", "path": "mongo_ops.cache.CacheConfig.default_ttl", - "signature": "", + "signature": null, "docstring": null }, "max_entries": { "name": "max_entries", "kind": "attribute", "path": "mongo_ops.cache.CacheConfig.max_entries", - "signature": "", + "signature": null, "docstring": null }, "key_prefix": { "name": "key_prefix", "kind": "attribute", "path": "mongo_ops.cache.CacheConfig.key_prefix", - "signature": "", + "signature": null, "docstring": null }, "cleanup_interval": { "name": "cleanup_interval", "kind": "attribute", "path": "mongo_ops.cache.CacheConfig.cleanup_interval", - "signature": "", + "signature": null, "docstring": null } } @@ -212,64 +212,64 @@ "name": "InMemoryCacheBackend", "kind": "class", "path": "mongo_ops.cache.InMemoryCacheBackend", - "signature": "", - "docstring": null, + "signature": "InMemoryCacheBackend(max_entries: int = 10000, default_ttl: int = 300, cleanup_interval: int = 60)", + "docstring": "Cache backend backed by an in-memory dict with TTL expiry.\n\nEntries are stored in an OrderedDict for LRU-compatible eviction and a\nmin-heap of expiry timestamps drives periodic removal of stale entries.\n\nNotes:\n **Thread safety:**\n\n All operations take an asyncio lock; the backend is safe for\n concurrent use within a single event loop.", "members": { "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.InMemoryCacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize()", + "docstring": "Start the periodic expired-entry cleanup task." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.InMemoryCacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown()", + "docstring": "Cancel and await the cleanup task." }, "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.InMemoryCacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str)", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.InMemoryCacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None)", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds; defaults to the backend default." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.InMemoryCacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str)", + "docstring": "Remove a key from the cache.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.InMemoryCacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str)", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.InMemoryCacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str)", + "docstring": "Remove all keys matching a glob pattern.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.InMemoryCacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats()", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: A copy of the current stats counters." } } }, @@ -278,49 +278,14 @@ "kind": "module", "path": "mongo_ops.cache.backend", "signature": null, - "docstring": "Cache backend abstraction.", + "docstring": "# Summary\n\nCache backend abstraction.", "members": { - "ABC": { - "name": "ABC", - "kind": "alias", - "path": "mongo_ops.cache.backend.ABC", - "signature": "", - "docstring": null - }, - "abstractmethod": { - "name": "abstractmethod", - "kind": "alias", - "path": "mongo_ops.cache.backend.abstractmethod", - "signature": "", - "docstring": null - }, - "dataclass": { - "name": "dataclass", - "kind": "alias", - "path": "mongo_ops.cache.backend.dataclass", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.cache.backend.Optional", - "signature": "", - "docstring": null - }, - "ObjectId": { - "name": "ObjectId", - "kind": "alias", - "path": "mongo_ops.cache.backend.ObjectId", - "signature": "", - "docstring": null - }, "CacheStats": { "name": "CacheStats", "kind": "class", "path": "mongo_ops.cache.backend.CacheStats", - "signature": "", - "docstring": null, + "signature": "CacheStats(hits: int = 0, misses: int = 0, sets: int = 0, deletes: int = 0, current_size: int = 0, max_size: int = 0)", + "docstring": "Snapshot of cache usage and activity counters.\n\nAttributes:\n hits (int):\n Number of get() calls that found a value.\n misses (int):\n Number of get() calls that returned None.\n sets (int):\n Number of values written to the cache.\n deletes (int):\n Number of keys removed.\n current_size (int):\n Number of entries currently held.\n max_size (int):\n Maximum number of entries the cache allows (0 = unbounded).", "members": { "hits": { "name": "hits", @@ -370,8 +335,8 @@ "name": "CircularReferenceError", "kind": "class", "path": "mongo_ops.cache.backend.CircularReferenceError", - "signature": "", - "docstring": null, + "signature": "CircularReferenceError(collection: str, doc_id: ObjectId, path: list[str])", + "docstring": "Raised when population detects a cycle in the reference graph.\n\nAttributes:\n collection (str):\n Collection where the cycle was detected.\n doc_id (ObjectId):\n Document ID where the cycle was detected.\n path (list[str]):\n Ordered labels describing the visited reference path.", "members": { "collection": { "name": "collection", @@ -400,64 +365,64 @@ "name": "CacheBackend", "kind": "class", "path": "mongo_ops.cache.backend.CacheBackend", - "signature": "", - "docstring": null, + "signature": null, + "docstring": "Abstract interface for cache backends.\n\nImplementations store byte-encoded values keyed by string, track usage\nstatistics, and manage their own lifecycle. The in-memory and Redis\nbackends both implement this contract.", "members": { "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str) -> bytes | None", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None) -> None", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds. When None, the backend default\n applies." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str) -> None", + "docstring": "Remove a key from the cache.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str) -> bool", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str) -> None", + "docstring": "Remove all keys matching a glob pattern.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats() -> CacheStats", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: A copy of the current stats counters." }, "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize() -> None", + "docstring": "Start background resources owned by the backend.\n\nShould be called once during application startup, after the\nrepositories are connected." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown() -> None", + "docstring": "Stop and release background resources.\n\nShould be called once during application shutdown." } } } @@ -468,42 +433,14 @@ "kind": "module", "path": "mongo_ops.cache.config", "signature": null, - "docstring": "Cache configuration.", + "docstring": "# Summary\n\nCache configuration.", "members": { - "dataclass": { - "name": "dataclass", - "kind": "alias", - "path": "mongo_ops.cache.config.dataclass", - "signature": "", - "docstring": null - }, - "Literal": { - "name": "Literal", - "kind": "alias", - "path": "mongo_ops.cache.config.Literal", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.cache.config.Optional", - "signature": "", - "docstring": null - }, - "Redis": { - "name": "Redis", - "kind": "alias", - "path": "mongo_ops.cache.config.Redis", - "signature": "", - "docstring": null - }, "CacheConfig": { "name": "CacheConfig", "kind": "class", "path": "mongo_ops.cache.config.CacheConfig", - "signature": "", - "docstring": null, + "signature": "CacheConfig(enabled: bool = True, backend: Literal['memory', 'redis'] = 'memory', redis_client: Redis | None = None, default_ttl: int = 300, max_entries: int = 10000, key_prefix: str = '', cleanup_interval: int = 60)", + "docstring": "Configuration for the cached repository layer.\n\nAttributes:\n enabled (bool):\n Whether caching is active for the repository.\n backend (Literal[\"memory\", \"redis\"]):\n Which backend to use. Defaults to \"memory\".\n redis_client (Optional[Redis]):\n Redis client required when backend is \"redis\".\n default_ttl (int):\n Default time-to-live for cached entries, in seconds.\n max_entries (int):\n Maximum entries for the in-memory backend.\n key_prefix (str):\n Prefix applied to cache keys; defaults to the collection name\n when empty.\n cleanup_interval (int):\n Interval (seconds) for the in-memory expiry sweep.", "members": { "enabled": { "name": "enabled", @@ -563,126 +500,70 @@ "kind": "module", "path": "mongo_ops.cache.in_memory", "signature": null, - "docstring": null, + "docstring": "# Summary\n\nIn-memory cache backend with TTL-based eviction and encode/decode helpers.", "members": { - "asyncio": { - "name": "asyncio", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.asyncio", - "signature": "", - "docstring": null - }, - "heapq": { - "name": "heapq", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.heapq", - "signature": "", - "docstring": null - }, - "json": { - "name": "json", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.json", - "signature": "", - "docstring": null - }, - "OrderedDict": { - "name": "OrderedDict", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.OrderedDict", - "signature": "", - "docstring": null - }, - "datetime": { - "name": "datetime", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.datetime", - "signature": "", - "docstring": null - }, - "timedelta": { - "name": "timedelta", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.timedelta", - "signature": "", - "docstring": null - }, - "timezone": { - "name": "timezone", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.timezone", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.Optional", - "signature": "", - "docstring": null - }, "CacheBackend": { "name": "CacheBackend", "kind": "class", "path": "mongo_ops.cache.in_memory.CacheBackend", - "signature": "", - "docstring": null, + "signature": null, + "docstring": "Abstract interface for cache backends.\n\nImplementations store byte-encoded values keyed by string, track usage\nstatistics, and manage their own lifecycle. The in-memory and Redis\nbackends both implement this contract.", "members": { "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str)", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None)", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds. When None, the backend default\n applies." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str)", + "docstring": "Remove a key from the cache.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str)", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str)", + "docstring": "Remove all keys matching a glob pattern.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats()", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: A copy of the current stats counters." }, "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize()", + "docstring": "Start background resources owned by the backend.\n\nShould be called once during application startup, after the\nrepositories are connected." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown()", + "docstring": "Stop and release background resources.\n\nShould be called once during application shutdown." } } }, @@ -690,49 +571,49 @@ "name": "CacheStats", "kind": "class", "path": "mongo_ops.cache.in_memory.CacheStats", - "signature": "", - "docstring": null, + "signature": "CacheStats(hits: int = 0, misses: int = 0, sets: int = 0, deletes: int = 0, current_size: int = 0, max_size: int = 0)", + "docstring": "Snapshot of cache usage and activity counters.\n\nAttributes:\n hits (int):\n Number of get() calls that found a value.\n misses (int):\n Number of get() calls that returned None.\n sets (int):\n Number of values written to the cache.\n deletes (int):\n Number of keys removed.\n current_size (int):\n Number of entries currently held.\n max_size (int):\n Maximum number of entries the cache allows (0 = unbounded).", "members": { "hits": { "name": "hits", "kind": "attribute", "path": "mongo_ops.cache.in_memory.CacheStats.hits", - "signature": "", + "signature": null, "docstring": null }, "misses": { "name": "misses", "kind": "attribute", "path": "mongo_ops.cache.in_memory.CacheStats.misses", - "signature": "", + "signature": null, "docstring": null }, "sets": { "name": "sets", "kind": "attribute", "path": "mongo_ops.cache.in_memory.CacheStats.sets", - "signature": "", + "signature": null, "docstring": null }, "deletes": { "name": "deletes", "kind": "attribute", "path": "mongo_ops.cache.in_memory.CacheStats.deletes", - "signature": "", + "signature": null, "docstring": null }, "current_size": { "name": "current_size", "kind": "attribute", "path": "mongo_ops.cache.in_memory.CacheStats.current_size", - "signature": "", + "signature": null, "docstring": null }, "max_size": { "name": "max_size", "kind": "attribute", "path": "mongo_ops.cache.in_memory.CacheStats.max_size", - "signature": "", + "signature": null, "docstring": null } } @@ -741,64 +622,64 @@ "name": "InMemoryCacheBackend", "kind": "class", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend", - "signature": "", - "docstring": null, + "signature": "InMemoryCacheBackend(max_entries: int = 10000, default_ttl: int = 300, cleanup_interval: int = 60)", + "docstring": "Cache backend backed by an in-memory dict with TTL expiry.\n\nEntries are stored in an OrderedDict for LRU-compatible eviction and a\nmin-heap of expiry timestamps drives periodic removal of stale entries.\n\nNotes:\n **Thread safety:**\n\n All operations take an asyncio lock; the backend is safe for\n concurrent use within a single event loop.", "members": { "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize() -> None", + "docstring": "Start the periodic expired-entry cleanup task." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown() -> None", + "docstring": "Cancel and await the cleanup task." }, "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str) -> bytes | None", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None) -> None", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds; defaults to the backend default." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str) -> None", + "docstring": "Remove a key from the cache.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str) -> bool", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str) -> None", + "docstring": "Remove all keys matching a glob pattern.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats() -> CacheStats", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: A copy of the current stats counters." } } }, @@ -806,15 +687,15 @@ "name": "encode_value", "kind": "function", "path": "mongo_ops.cache.in_memory.encode_value", - "signature": "", - "docstring": null + "signature": "encode_value(value: dict) -> bytes", + "docstring": "Encode a dict into cache-ready bytes.\n\nNon-serializable values (e.g., ObjectId) are coerced with str().\n\nArgs:\n value (dict):\n The dictionary to encode.\n\nReturns:\n bytes: UTF-8 JSON bytes." }, "decode_value": { "name": "decode_value", "kind": "function", "path": "mongo_ops.cache.in_memory.decode_value", - "signature": "", - "docstring": null + "signature": "decode_value(data: bytes) -> dict", + "docstring": "Decode cache bytes back into a dict.\n\nArgs:\n data (bytes):\n UTF-8 JSON bytes produced by encode_value().\n\nReturns:\n dict: The decoded dictionary." } } }, @@ -823,84 +704,70 @@ "kind": "module", "path": "mongo_ops.cache.redis_backend", "signature": null, - "docstring": null, + "docstring": "# Summary\n\nRedis cache backend with key prefixing and pub/sub invalidation.", "members": { - "json": { - "name": "json", - "kind": "alias", - "path": "mongo_ops.cache.redis_backend.json", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.cache.redis_backend.Optional", - "signature": "", - "docstring": null - }, "CacheBackend": { "name": "CacheBackend", "kind": "class", "path": "mongo_ops.cache.redis_backend.CacheBackend", - "signature": "", - "docstring": null, + "signature": null, + "docstring": "Abstract interface for cache backends.\n\nImplementations store byte-encoded values keyed by string, track usage\nstatistics, and manage their own lifecycle. The in-memory and Redis\nbackends both implement this contract.", "members": { "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str)", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None)", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds. When None, the backend default\n applies." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str)", + "docstring": "Remove a key from the cache.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str)", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str)", + "docstring": "Remove all keys matching a glob pattern.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats()", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: A copy of the current stats counters." }, "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize()", + "docstring": "Start background resources owned by the backend.\n\nShould be called once during application startup, after the\nrepositories are connected." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown()", + "docstring": "Stop and release background resources.\n\nShould be called once during application shutdown." } } }, @@ -908,129 +775,122 @@ "name": "CacheStats", "kind": "class", "path": "mongo_ops.cache.redis_backend.CacheStats", - "signature": "", - "docstring": null, + "signature": "CacheStats(hits: int = 0, misses: int = 0, sets: int = 0, deletes: int = 0, current_size: int = 0, max_size: int = 0)", + "docstring": "Snapshot of cache usage and activity counters.\n\nAttributes:\n hits (int):\n Number of get() calls that found a value.\n misses (int):\n Number of get() calls that returned None.\n sets (int):\n Number of values written to the cache.\n deletes (int):\n Number of keys removed.\n current_size (int):\n Number of entries currently held.\n max_size (int):\n Maximum number of entries the cache allows (0 = unbounded).", "members": { "hits": { "name": "hits", "kind": "attribute", "path": "mongo_ops.cache.redis_backend.CacheStats.hits", - "signature": "", + "signature": null, "docstring": null }, "misses": { "name": "misses", "kind": "attribute", "path": "mongo_ops.cache.redis_backend.CacheStats.misses", - "signature": "", + "signature": null, "docstring": null }, "sets": { "name": "sets", "kind": "attribute", "path": "mongo_ops.cache.redis_backend.CacheStats.sets", - "signature": "", + "signature": null, "docstring": null }, "deletes": { "name": "deletes", "kind": "attribute", "path": "mongo_ops.cache.redis_backend.CacheStats.deletes", - "signature": "", + "signature": null, "docstring": null }, "current_size": { "name": "current_size", "kind": "attribute", "path": "mongo_ops.cache.redis_backend.CacheStats.current_size", - "signature": "", + "signature": null, "docstring": null }, "max_size": { "name": "max_size", "kind": "attribute", "path": "mongo_ops.cache.redis_backend.CacheStats.max_size", - "signature": "", + "signature": null, "docstring": null } } }, - "Redis": { - "name": "Redis", - "kind": "alias", - "path": "mongo_ops.cache.redis_backend.Redis", - "signature": "", - "docstring": null - }, "RedisCacheBackend": { "name": "RedisCacheBackend", "kind": "class", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend", - "signature": "", - "docstring": null, + "signature": "RedisCacheBackend(redis_client: Redis, key_prefix: str = '', default_ttl: int = 300)", + "docstring": "Cache backend backed by Redis with key prefixing.\n\nValues are stored with a configurable key prefix, and deletions publish\non a shared invalidation channel so other processes can react.\n\nNotes:\n **Lifecycle:**\n\n Requires ``pip install mongo-ops[redis]`` and a live Redis\n connection supplied by the caller.", "members": { "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize() -> None", + "docstring": "Open the pub/sub subscription used for invalidation." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown() -> None", + "docstring": "Close the pub/sub subscription." }, "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str) -> bytes | None", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None) -> None", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds; defaults to the backend default." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str) -> None", + "docstring": "Remove a key and publish an invalidation notice.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str) -> bool", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str) -> None", + "docstring": "Remove all keys matching a glob pattern via SCAN/DEL.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats() -> CacheStats", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: Stats with current_size taken from Redis dbsize." }, "publish_invalidate": { "name": "publish_invalidate", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.publish_invalidate", - "signature": "", - "docstring": null + "signature": "publish_invalidate(key: str) -> None", + "docstring": "Publish an invalidation notice for a key.\n\nArgs:\n key (str):\n The cache key to broadcast." } } }, @@ -1038,15 +898,15 @@ "name": "encode_value", "kind": "function", "path": "mongo_ops.cache.redis_backend.encode_value", - "signature": "", - "docstring": null + "signature": "encode_value(value: dict) -> bytes", + "docstring": "Encode a dict into cache-ready bytes.\n\nNon-serializable values (e.g., ObjectId) are coerced with str().\n\nArgs:\n value (dict):\n The dictionary to encode.\n\nReturns:\n bytes: UTF-8 JSON bytes." }, "decode_value": { "name": "decode_value", "kind": "function", "path": "mongo_ops.cache.redis_backend.decode_value", - "signature": "", - "docstring": null + "signature": "decode_value(data: bytes) -> dict", + "docstring": "Decode cache bytes back into a dict.\n\nArgs:\n data (bytes):\n UTF-8 JSON bytes produced by encode_value().\n\nReturns:\n dict: The decoded dictionary." } } }, @@ -1055,111 +915,69 @@ "kind": "module", "path": "mongo_ops.cache.repository", "signature": null, - "docstring": null, + "docstring": "# Summary\n\nCached repository layer that combines a repository with a cache backend.", "members": { - "Generic": { - "name": "Generic", - "kind": "alias", - "path": "mongo_ops.cache.repository.Generic", - "signature": "", - "docstring": null - }, - "List": { - "name": "List", - "kind": "alias", - "path": "mongo_ops.cache.repository.List", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.cache.repository.Optional", - "signature": "", - "docstring": null - }, - "TypeVar": { - "name": "TypeVar", - "kind": "alias", - "path": "mongo_ops.cache.repository.TypeVar", - "signature": "", - "docstring": null - }, - "Union": { - "name": "Union", - "kind": "alias", - "path": "mongo_ops.cache.repository.Union", - "signature": "", - "docstring": null - }, - "ObjectId": { - "name": "ObjectId", - "kind": "alias", - "path": "mongo_ops.cache.repository.ObjectId", - "signature": "", - "docstring": null - }, "BaseDocument": { "name": "BaseDocument", "kind": "class", "path": "mongo_ops.cache.repository.BaseDocument", - "signature": "", - "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent \nMongoDB documents. It includes automatic handling of the `_id` field \nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", + "signature": null, + "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent\nMongoDB documents. It includes automatic handling of the `_id` field\nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", "members": { "id": { "name": "id", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseDocument.id", - "signature": "", + "signature": null, "docstring": null }, "created_at": { "name": "created_at", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseDocument.created_at", - "signature": "", + "signature": null, "docstring": null }, "updated_at": { "name": "updated_at", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseDocument.updated_at", - "signature": "", + "signature": null, "docstring": null }, "Config": { "name": "Config", "kind": "class", "path": "mongo_ops.cache.repository.BaseDocument.Config", - "signature": "", + "signature": null, "docstring": null, "members": { "populate_by_name": { "name": "populate_by_name", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseDocument.Config.populate_by_name", - "signature": "", + "signature": null, "docstring": null }, "arbitrary_types_allowed": { "name": "arbitrary_types_allowed", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseDocument.Config.arbitrary_types_allowed", - "signature": "", + "signature": null, "docstring": null }, "json_encoders": { "name": "json_encoders", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseDocument.Config.json_encoders", - "signature": "", + "signature": null, "docstring": null }, "json_schema_extra": { "name": "json_schema_extra", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseDocument.Config.json_schema_extra", - "signature": "", + "signature": null, "docstring": null } } @@ -1170,14 +988,14 @@ "name": "BaseRepository", "kind": "class", "path": "mongo_ops.cache.repository.BaseRepository", - "signature": "", - "docstring": "Base repository class combining CRUD operations and collection management.\n\nThis class simplifies repository creation by automatically obtaining the \ndatabase connection and collection instance.\n\nAttributes:\n collection_name: The name of the collection managed by this repository.", + "signature": "BaseRepository(collection_name: str, model: type[T])", + "docstring": "Base repository class combining CRUD operations and collection management.\n\nThis class simplifies repository creation by automatically obtaining the\ndatabase connection and collection instance.\n\nAttributes:\n collection_name: The name of the collection managed by this repository.", "members": { "collection_name": { "name": "collection_name", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseRepository.collection_name", - "signature": "", + "signature": null, "docstring": null } } @@ -1186,64 +1004,64 @@ "name": "CacheBackend", "kind": "class", "path": "mongo_ops.cache.repository.CacheBackend", - "signature": "", - "docstring": null, + "signature": null, + "docstring": "Abstract interface for cache backends.\n\nImplementations store byte-encoded values keyed by string, track usage\nstatistics, and manage their own lifecycle. The in-memory and Redis\nbackends both implement this contract.", "members": { "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str)", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None)", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds. When None, the backend default\n applies." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str)", + "docstring": "Remove a key from the cache.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str)", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str)", + "docstring": "Remove all keys matching a glob pattern.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats()", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: A copy of the current stats counters." }, "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize()", + "docstring": "Start background resources owned by the backend.\n\nShould be called once during application startup, after the\nrepositories are connected." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown()", + "docstring": "Stop and release background resources.\n\nShould be called once during application shutdown." } } }, @@ -1251,56 +1069,56 @@ "name": "CacheConfig", "kind": "class", "path": "mongo_ops.cache.repository.CacheConfig", - "signature": "", - "docstring": null, + "signature": "CacheConfig(enabled: bool = True, backend: Literal['memory', 'redis'] = 'memory', redis_client: Redis | None = None, default_ttl: int = 300, max_entries: int = 10000, key_prefix: str = '', cleanup_interval: int = 60)", + "docstring": "Configuration for the cached repository layer.\n\nAttributes:\n enabled (bool):\n Whether caching is active for the repository.\n backend (Literal[\"memory\", \"redis\"]):\n Which backend to use. Defaults to \"memory\".\n redis_client (Optional[Redis]):\n Redis client required when backend is \"redis\".\n default_ttl (int):\n Default time-to-live for cached entries, in seconds.\n max_entries (int):\n Maximum entries for the in-memory backend.\n key_prefix (str):\n Prefix applied to cache keys; defaults to the collection name\n when empty.\n cleanup_interval (int):\n Interval (seconds) for the in-memory expiry sweep.", "members": { "enabled": { "name": "enabled", "kind": "attribute", "path": "mongo_ops.cache.repository.CacheConfig.enabled", - "signature": "", + "signature": null, "docstring": null }, "backend": { "name": "backend", "kind": "attribute", "path": "mongo_ops.cache.repository.CacheConfig.backend", - "signature": "", + "signature": null, "docstring": null }, "redis_client": { "name": "redis_client", "kind": "attribute", "path": "mongo_ops.cache.repository.CacheConfig.redis_client", - "signature": "", + "signature": null, "docstring": null }, "default_ttl": { "name": "default_ttl", "kind": "attribute", "path": "mongo_ops.cache.repository.CacheConfig.default_ttl", - "signature": "", + "signature": null, "docstring": null }, "max_entries": { "name": "max_entries", "kind": "attribute", "path": "mongo_ops.cache.repository.CacheConfig.max_entries", - "signature": "", + "signature": null, "docstring": null }, "key_prefix": { "name": "key_prefix", "kind": "attribute", "path": "mongo_ops.cache.repository.CacheConfig.key_prefix", - "signature": "", + "signature": null, "docstring": null }, "cleanup_interval": { "name": "cleanup_interval", "kind": "attribute", "path": "mongo_ops.cache.repository.CacheConfig.cleanup_interval", - "signature": "", + "signature": null, "docstring": null } } @@ -1309,15 +1127,15 @@ "name": "decode_value", "kind": "function", "path": "mongo_ops.cache.repository.decode_value", - "signature": "", - "docstring": null + "signature": "decode_value(data: bytes)", + "docstring": "Decode cache bytes back into a dict.\n\nArgs:\n data (bytes):\n UTF-8 JSON bytes produced by encode_value().\n\nReturns:\n dict: The decoded dictionary." }, "encode_value": { "name": "encode_value", "kind": "function", "path": "mongo_ops.cache.repository.encode_value", - "signature": "", - "docstring": null + "signature": "encode_value(value: dict)", + "docstring": "Encode a dict into cache-ready bytes.\n\nNon-serializable values (e.g., ObjectId) are coerced with str().\n\nArgs:\n value (dict):\n The dictionary to encode.\n\nReturns:\n bytes: UTF-8 JSON bytes." }, "T": { "name": "T", @@ -1330,50 +1148,50 @@ "name": "CachedBaseRepository", "kind": "class", "path": "mongo_ops.cache.repository.CachedBaseRepository", - "signature": "", - "docstring": null, + "signature": "CachedBaseRepository(collection_name: str, model: type[T], cache_backend: CacheBackend, config: CacheConfig | None = None)", + "docstring": "Repository that reads and writes through a cache backend.\n\nWraps an existing BaseRepository with an ID-keyed cache. Reads consult\nthe backend first and fall through to MongoDB on a miss, populating the\ncache on success. Writes invalidate or refresh the affected key.\n\nNotes:\n **Guarantees:**\n\n - The cache holds the raw document shape (``model_dump``), so FK\n references round-trip as hex strings, not populated models.\n - When ``config.enabled`` is False the repository behaves exactly\n like its parent with no cache access.", "members": { "get_by_id": { "name": "get_by_id", "kind": "function", "path": "mongo_ops.cache.repository.CachedBaseRepository.get_by_id", - "signature": "", - "docstring": null + "signature": "get_by_id(id: str | ObjectId) -> T | None", + "docstring": "Fetch a document, reading through the cache when enabled.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID.\n\nReturns:\n Optional[T]: The model instance, or None when not found." }, "create": { "name": "create", "kind": "function", "path": "mongo_ops.cache.repository.CachedBaseRepository.create", - "signature": "", - "docstring": null + "signature": "create(data: T) -> T", + "docstring": "Insert a document and cache the raw snapshot.\n\nArgs:\n data (T):\n The model instance to insert.\n\nReturns:\n T: The created model instance, including its ID." }, "update": { "name": "update", "kind": "function", "path": "mongo_ops.cache.repository.CachedBaseRepository.update", - "signature": "", - "docstring": null + "signature": "update(id: str | ObjectId, data: dict) -> T | None", + "docstring": "Update a document and refresh its cache entry.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID.\n data (dict):\n Fields to set via $set.\n\nReturns:\n Optional[T]: The updated model instance, or None when not found." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.repository.CachedBaseRepository.delete", - "signature": "", - "docstring": null + "signature": "delete(id: str | ObjectId) -> bool", + "docstring": "Delete a document and remove its cache entry.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID.\n\nReturns:\n bool: True if a document was deleted, False otherwise." }, "warm_cache": { "name": "warm_cache", "kind": "function", "path": "mongo_ops.cache.repository.CachedBaseRepository.warm_cache", - "signature": "", - "docstring": null + "signature": "warm_cache(ids: list[str | ObjectId]) -> int", + "docstring": "Pre-populate the cache for a set of document IDs.\n\nDocs already present in the cache are skipped.\n\nArgs:\n ids (list[Union[str, ObjectId]]):\n Document IDs to warm.\n\nReturns:\n int: Number of entries added to the cache." }, "invalidate_cache": { "name": "invalidate_cache", "kind": "function", "path": "mongo_ops.cache.repository.CachedBaseRepository.invalidate_cache", - "signature": "", - "docstring": null + "signature": "invalidate_cache(id: str | ObjectId) -> None", + "docstring": "Remove a single document's cache entry.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID to invalidate." } } } diff --git a/mongo-ops/mcp/modules/mongo_ops.cache.redis_backend.json b/mongo-ops/mcp/modules/mongo_ops.cache.redis_backend.json index 0bcef2b..b85f172 100644 --- a/mongo-ops/mcp/modules/mongo_ops.cache.redis_backend.json +++ b/mongo-ops/mcp/modules/mongo_ops.cache.redis_backend.json @@ -2,84 +2,70 @@ "module": "mongo_ops.cache.redis_backend", "content": { "path": "mongo_ops.cache.redis_backend", - "docstring": null, + "docstring": "# Summary\n\nRedis cache backend with key prefixing and pub/sub invalidation.", "objects": { - "json": { - "name": "json", - "kind": "alias", - "path": "mongo_ops.cache.redis_backend.json", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.cache.redis_backend.Optional", - "signature": "", - "docstring": null - }, "CacheBackend": { "name": "CacheBackend", "kind": "class", "path": "mongo_ops.cache.redis_backend.CacheBackend", - "signature": "", - "docstring": null, + "signature": null, + "docstring": "Abstract interface for cache backends.\n\nImplementations store byte-encoded values keyed by string, track usage\nstatistics, and manage their own lifecycle. The in-memory and Redis\nbackends both implement this contract.", "members": { "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str)", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None)", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds. When None, the backend default\n applies." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str)", + "docstring": "Remove a key from the cache.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str)", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str)", + "docstring": "Remove all keys matching a glob pattern.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats()", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: A copy of the current stats counters." }, "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize()", + "docstring": "Start background resources owned by the backend.\n\nShould be called once during application startup, after the\nrepositories are connected." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown()", + "docstring": "Stop and release background resources.\n\nShould be called once during application shutdown." } } }, @@ -87,129 +73,122 @@ "name": "CacheStats", "kind": "class", "path": "mongo_ops.cache.redis_backend.CacheStats", - "signature": "", - "docstring": null, + "signature": "CacheStats(hits: int = 0, misses: int = 0, sets: int = 0, deletes: int = 0, current_size: int = 0, max_size: int = 0)", + "docstring": "Snapshot of cache usage and activity counters.\n\nAttributes:\n hits (int):\n Number of get() calls that found a value.\n misses (int):\n Number of get() calls that returned None.\n sets (int):\n Number of values written to the cache.\n deletes (int):\n Number of keys removed.\n current_size (int):\n Number of entries currently held.\n max_size (int):\n Maximum number of entries the cache allows (0 = unbounded).", "members": { "hits": { "name": "hits", "kind": "attribute", "path": "mongo_ops.cache.redis_backend.CacheStats.hits", - "signature": "", + "signature": null, "docstring": null }, "misses": { "name": "misses", "kind": "attribute", "path": "mongo_ops.cache.redis_backend.CacheStats.misses", - "signature": "", + "signature": null, "docstring": null }, "sets": { "name": "sets", "kind": "attribute", "path": "mongo_ops.cache.redis_backend.CacheStats.sets", - "signature": "", + "signature": null, "docstring": null }, "deletes": { "name": "deletes", "kind": "attribute", "path": "mongo_ops.cache.redis_backend.CacheStats.deletes", - "signature": "", + "signature": null, "docstring": null }, "current_size": { "name": "current_size", "kind": "attribute", "path": "mongo_ops.cache.redis_backend.CacheStats.current_size", - "signature": "", + "signature": null, "docstring": null }, "max_size": { "name": "max_size", "kind": "attribute", "path": "mongo_ops.cache.redis_backend.CacheStats.max_size", - "signature": "", + "signature": null, "docstring": null } } }, - "Redis": { - "name": "Redis", - "kind": "alias", - "path": "mongo_ops.cache.redis_backend.Redis", - "signature": "", - "docstring": null - }, "RedisCacheBackend": { "name": "RedisCacheBackend", "kind": "class", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend", - "signature": "", - "docstring": null, + "signature": "RedisCacheBackend(redis_client: Redis, key_prefix: str = '', default_ttl: int = 300)", + "docstring": "Cache backend backed by Redis with key prefixing.\n\nValues are stored with a configurable key prefix, and deletions publish\non a shared invalidation channel so other processes can react.\n\nNotes:\n **Lifecycle:**\n\n Requires ``pip install mongo-ops[redis]`` and a live Redis\n connection supplied by the caller.", "members": { "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize() -> None", + "docstring": "Open the pub/sub subscription used for invalidation." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown() -> None", + "docstring": "Close the pub/sub subscription." }, "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str) -> bytes | None", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None) -> None", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds; defaults to the backend default." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str) -> None", + "docstring": "Remove a key and publish an invalidation notice.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str) -> bool", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str) -> None", + "docstring": "Remove all keys matching a glob pattern via SCAN/DEL.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats() -> CacheStats", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: Stats with current_size taken from Redis dbsize." }, "publish_invalidate": { "name": "publish_invalidate", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.publish_invalidate", - "signature": "", - "docstring": null + "signature": "publish_invalidate(key: str) -> None", + "docstring": "Publish an invalidation notice for a key.\n\nArgs:\n key (str):\n The cache key to broadcast." } } }, @@ -217,15 +196,15 @@ "name": "encode_value", "kind": "function", "path": "mongo_ops.cache.redis_backend.encode_value", - "signature": "", - "docstring": null + "signature": "encode_value(value: dict) -> bytes", + "docstring": "Encode a dict into cache-ready bytes.\n\nNon-serializable values (e.g., ObjectId) are coerced with str().\n\nArgs:\n value (dict):\n The dictionary to encode.\n\nReturns:\n bytes: UTF-8 JSON bytes." }, "decode_value": { "name": "decode_value", "kind": "function", "path": "mongo_ops.cache.redis_backend.decode_value", - "signature": "", - "docstring": null + "signature": "decode_value(data: bytes) -> dict", + "docstring": "Decode cache bytes back into a dict.\n\nArgs:\n data (bytes):\n UTF-8 JSON bytes produced by encode_value().\n\nReturns:\n dict: The decoded dictionary." } } } diff --git a/mongo-ops/mcp/modules/mongo_ops.cache.repository.json b/mongo-ops/mcp/modules/mongo_ops.cache.repository.json index 9053509..d3970cc 100644 --- a/mongo-ops/mcp/modules/mongo_ops.cache.repository.json +++ b/mongo-ops/mcp/modules/mongo_ops.cache.repository.json @@ -2,111 +2,69 @@ "module": "mongo_ops.cache.repository", "content": { "path": "mongo_ops.cache.repository", - "docstring": null, + "docstring": "# Summary\n\nCached repository layer that combines a repository with a cache backend.", "objects": { - "Generic": { - "name": "Generic", - "kind": "alias", - "path": "mongo_ops.cache.repository.Generic", - "signature": "", - "docstring": null - }, - "List": { - "name": "List", - "kind": "alias", - "path": "mongo_ops.cache.repository.List", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.cache.repository.Optional", - "signature": "", - "docstring": null - }, - "TypeVar": { - "name": "TypeVar", - "kind": "alias", - "path": "mongo_ops.cache.repository.TypeVar", - "signature": "", - "docstring": null - }, - "Union": { - "name": "Union", - "kind": "alias", - "path": "mongo_ops.cache.repository.Union", - "signature": "", - "docstring": null - }, - "ObjectId": { - "name": "ObjectId", - "kind": "alias", - "path": "mongo_ops.cache.repository.ObjectId", - "signature": "", - "docstring": null - }, "BaseDocument": { "name": "BaseDocument", "kind": "class", "path": "mongo_ops.cache.repository.BaseDocument", - "signature": "", - "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent \nMongoDB documents. It includes automatic handling of the `_id` field \nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", + "signature": null, + "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent\nMongoDB documents. It includes automatic handling of the `_id` field\nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", "members": { "id": { "name": "id", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseDocument.id", - "signature": "", + "signature": null, "docstring": null }, "created_at": { "name": "created_at", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseDocument.created_at", - "signature": "", + "signature": null, "docstring": null }, "updated_at": { "name": "updated_at", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseDocument.updated_at", - "signature": "", + "signature": null, "docstring": null }, "Config": { "name": "Config", "kind": "class", "path": "mongo_ops.cache.repository.BaseDocument.Config", - "signature": "", + "signature": null, "docstring": null, "members": { "populate_by_name": { "name": "populate_by_name", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseDocument.Config.populate_by_name", - "signature": "", + "signature": null, "docstring": null }, "arbitrary_types_allowed": { "name": "arbitrary_types_allowed", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseDocument.Config.arbitrary_types_allowed", - "signature": "", + "signature": null, "docstring": null }, "json_encoders": { "name": "json_encoders", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseDocument.Config.json_encoders", - "signature": "", + "signature": null, "docstring": null }, "json_schema_extra": { "name": "json_schema_extra", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseDocument.Config.json_schema_extra", - "signature": "", + "signature": null, "docstring": null } } @@ -117,14 +75,14 @@ "name": "BaseRepository", "kind": "class", "path": "mongo_ops.cache.repository.BaseRepository", - "signature": "", - "docstring": "Base repository class combining CRUD operations and collection management.\n\nThis class simplifies repository creation by automatically obtaining the \ndatabase connection and collection instance.\n\nAttributes:\n collection_name: The name of the collection managed by this repository.", + "signature": "BaseRepository(collection_name: str, model: type[T])", + "docstring": "Base repository class combining CRUD operations and collection management.\n\nThis class simplifies repository creation by automatically obtaining the\ndatabase connection and collection instance.\n\nAttributes:\n collection_name: The name of the collection managed by this repository.", "members": { "collection_name": { "name": "collection_name", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseRepository.collection_name", - "signature": "", + "signature": null, "docstring": null } } @@ -133,64 +91,64 @@ "name": "CacheBackend", "kind": "class", "path": "mongo_ops.cache.repository.CacheBackend", - "signature": "", - "docstring": null, + "signature": null, + "docstring": "Abstract interface for cache backends.\n\nImplementations store byte-encoded values keyed by string, track usage\nstatistics, and manage their own lifecycle. The in-memory and Redis\nbackends both implement this contract.", "members": { "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str)", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None)", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds. When None, the backend default\n applies." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str)", + "docstring": "Remove a key from the cache.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str)", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str)", + "docstring": "Remove all keys matching a glob pattern.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats()", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: A copy of the current stats counters." }, "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize()", + "docstring": "Start background resources owned by the backend.\n\nShould be called once during application startup, after the\nrepositories are connected." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown()", + "docstring": "Stop and release background resources.\n\nShould be called once during application shutdown." } } }, @@ -198,56 +156,56 @@ "name": "CacheConfig", "kind": "class", "path": "mongo_ops.cache.repository.CacheConfig", - "signature": "", - "docstring": null, + "signature": "CacheConfig(enabled: bool = True, backend: Literal['memory', 'redis'] = 'memory', redis_client: Redis | None = None, default_ttl: int = 300, max_entries: int = 10000, key_prefix: str = '', cleanup_interval: int = 60)", + "docstring": "Configuration for the cached repository layer.\n\nAttributes:\n enabled (bool):\n Whether caching is active for the repository.\n backend (Literal[\"memory\", \"redis\"]):\n Which backend to use. Defaults to \"memory\".\n redis_client (Optional[Redis]):\n Redis client required when backend is \"redis\".\n default_ttl (int):\n Default time-to-live for cached entries, in seconds.\n max_entries (int):\n Maximum entries for the in-memory backend.\n key_prefix (str):\n Prefix applied to cache keys; defaults to the collection name\n when empty.\n cleanup_interval (int):\n Interval (seconds) for the in-memory expiry sweep.", "members": { "enabled": { "name": "enabled", "kind": "attribute", "path": "mongo_ops.cache.repository.CacheConfig.enabled", - "signature": "", + "signature": null, "docstring": null }, "backend": { "name": "backend", "kind": "attribute", "path": "mongo_ops.cache.repository.CacheConfig.backend", - "signature": "", + "signature": null, "docstring": null }, "redis_client": { "name": "redis_client", "kind": "attribute", "path": "mongo_ops.cache.repository.CacheConfig.redis_client", - "signature": "", + "signature": null, "docstring": null }, "default_ttl": { "name": "default_ttl", "kind": "attribute", "path": "mongo_ops.cache.repository.CacheConfig.default_ttl", - "signature": "", + "signature": null, "docstring": null }, "max_entries": { "name": "max_entries", "kind": "attribute", "path": "mongo_ops.cache.repository.CacheConfig.max_entries", - "signature": "", + "signature": null, "docstring": null }, "key_prefix": { "name": "key_prefix", "kind": "attribute", "path": "mongo_ops.cache.repository.CacheConfig.key_prefix", - "signature": "", + "signature": null, "docstring": null }, "cleanup_interval": { "name": "cleanup_interval", "kind": "attribute", "path": "mongo_ops.cache.repository.CacheConfig.cleanup_interval", - "signature": "", + "signature": null, "docstring": null } } @@ -256,15 +214,15 @@ "name": "decode_value", "kind": "function", "path": "mongo_ops.cache.repository.decode_value", - "signature": "", - "docstring": null + "signature": "decode_value(data: bytes)", + "docstring": "Decode cache bytes back into a dict.\n\nArgs:\n data (bytes):\n UTF-8 JSON bytes produced by encode_value().\n\nReturns:\n dict: The decoded dictionary." }, "encode_value": { "name": "encode_value", "kind": "function", "path": "mongo_ops.cache.repository.encode_value", - "signature": "", - "docstring": null + "signature": "encode_value(value: dict)", + "docstring": "Encode a dict into cache-ready bytes.\n\nNon-serializable values (e.g., ObjectId) are coerced with str().\n\nArgs:\n value (dict):\n The dictionary to encode.\n\nReturns:\n bytes: UTF-8 JSON bytes." }, "T": { "name": "T", @@ -277,50 +235,50 @@ "name": "CachedBaseRepository", "kind": "class", "path": "mongo_ops.cache.repository.CachedBaseRepository", - "signature": "", - "docstring": null, + "signature": "CachedBaseRepository(collection_name: str, model: type[T], cache_backend: CacheBackend, config: CacheConfig | None = None)", + "docstring": "Repository that reads and writes through a cache backend.\n\nWraps an existing BaseRepository with an ID-keyed cache. Reads consult\nthe backend first and fall through to MongoDB on a miss, populating the\ncache on success. Writes invalidate or refresh the affected key.\n\nNotes:\n **Guarantees:**\n\n - The cache holds the raw document shape (``model_dump``), so FK\n references round-trip as hex strings, not populated models.\n - When ``config.enabled`` is False the repository behaves exactly\n like its parent with no cache access.", "members": { "get_by_id": { "name": "get_by_id", "kind": "function", "path": "mongo_ops.cache.repository.CachedBaseRepository.get_by_id", - "signature": "", - "docstring": null + "signature": "get_by_id(id: str | ObjectId) -> T | None", + "docstring": "Fetch a document, reading through the cache when enabled.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID.\n\nReturns:\n Optional[T]: The model instance, or None when not found." }, "create": { "name": "create", "kind": "function", "path": "mongo_ops.cache.repository.CachedBaseRepository.create", - "signature": "", - "docstring": null + "signature": "create(data: T) -> T", + "docstring": "Insert a document and cache the raw snapshot.\n\nArgs:\n data (T):\n The model instance to insert.\n\nReturns:\n T: The created model instance, including its ID." }, "update": { "name": "update", "kind": "function", "path": "mongo_ops.cache.repository.CachedBaseRepository.update", - "signature": "", - "docstring": null + "signature": "update(id: str | ObjectId, data: dict) -> T | None", + "docstring": "Update a document and refresh its cache entry.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID.\n data (dict):\n Fields to set via $set.\n\nReturns:\n Optional[T]: The updated model instance, or None when not found." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.repository.CachedBaseRepository.delete", - "signature": "", - "docstring": null + "signature": "delete(id: str | ObjectId) -> bool", + "docstring": "Delete a document and remove its cache entry.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID.\n\nReturns:\n bool: True if a document was deleted, False otherwise." }, "warm_cache": { "name": "warm_cache", "kind": "function", "path": "mongo_ops.cache.repository.CachedBaseRepository.warm_cache", - "signature": "", - "docstring": null + "signature": "warm_cache(ids: list[str | ObjectId]) -> int", + "docstring": "Pre-populate the cache for a set of document IDs.\n\nDocs already present in the cache are skipped.\n\nArgs:\n ids (list[Union[str, ObjectId]]):\n Document IDs to warm.\n\nReturns:\n int: Number of entries added to the cache." }, "invalidate_cache": { "name": "invalidate_cache", "kind": "function", "path": "mongo_ops.cache.repository.CachedBaseRepository.invalidate_cache", - "signature": "", - "docstring": null + "signature": "invalidate_cache(id: str | ObjectId) -> None", + "docstring": "Remove a single document's cache entry.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID to invalidate." } } } diff --git a/mongo-ops/mcp/modules/mongo_ops.connection.json b/mongo-ops/mcp/modules/mongo_ops.connection.json index e7721ec..086ee93 100644 --- a/mongo-ops/mcp/modules/mongo_ops.connection.json +++ b/mongo-ops/mcp/modules/mongo_ops.connection.json @@ -2,77 +2,49 @@ "module": "mongo_ops.connection", "content": { "path": "mongo_ops.connection", - "docstring": "MongoDB connection management.", + "docstring": "# Summary\n\nMongoDB connection management.", "objects": { - "asynccontextmanager": { - "name": "asynccontextmanager", - "kind": "alias", - "path": "mongo_ops.connection.asynccontextmanager", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.connection.Optional", - "signature": "", - "docstring": null - }, - "AsyncIOMotorClient": { - "name": "AsyncIOMotorClient", - "kind": "alias", - "path": "mongo_ops.connection.AsyncIOMotorClient", - "signature": "", - "docstring": null - }, - "AsyncIOMotorDatabase": { - "name": "AsyncIOMotorDatabase", - "kind": "alias", - "path": "mongo_ops.connection.AsyncIOMotorDatabase", - "signature": "", - "docstring": null - }, "MongoConnectionManager": { "name": "MongoConnectionManager", "kind": "class", "path": "mongo_ops.connection.MongoConnectionManager", - "signature": "", - "docstring": "Manages MongoDB connections with async lifecycle.\n\nThis class provides a singleton-like manager for the MongoDB client and \ndatabase instances, ensuring they are properly initialized and closed \nacross the application lifecycle.", + "signature": null, + "docstring": "Manages MongoDB connections with async lifecycle.\n\nThis class provides a singleton-like manager for the MongoDB client and\ndatabase instances, ensuring they are properly initialized and closed\nacross the application lifecycle.", "members": { "connect": { "name": "connect", "kind": "function", "path": "mongo_ops.connection.MongoConnectionManager.connect", - "signature": "", - "docstring": "Connect to MongoDB and initialize the shared client.\n\nArgs:\n uri: MongoDB connection URI (e.g., \"mongodb://localhost:27017\").\n db_name: Name of the database to use.\n **kwargs: Additional Motor client options (e.g., maxPoolSize).\n\nReturns:\n AsyncIOMotorDatabase: The initialized database instance." + "signature": "connect(uri: str, db_name: str, **kwargs: Any) -> AsyncIOMotorDatabase", + "docstring": "Connect to MongoDB and initialize the shared client.\n\nArgs:\n uri (str): MongoDB connection URI (e.g., \"mongodb://localhost:27017\").\n db_name (str): Name of the database to use.\n **kwargs: Additional Motor client options (e.g., maxPoolSize).\n\nReturns:\n AsyncIOMotorDatabase: The initialized database instance." }, "disconnect": { "name": "disconnect", "kind": "function", "path": "mongo_ops.connection.MongoConnectionManager.disconnect", - "signature": "", + "signature": "disconnect() -> None", "docstring": "Close the active MongoDB connection and cleanup resources." }, "get_database": { "name": "get_database", "kind": "function", "path": "mongo_ops.connection.MongoConnectionManager.get_database", - "signature": "", + "signature": "get_database() -> AsyncIOMotorDatabase", "docstring": "Retrieve the current database instance.\n\nReturns:\n AsyncIOMotorDatabase: The active database instance.\n\nRaises:\n RuntimeError: If connect() has not been called yet." }, "get_client": { "name": "get_client", "kind": "function", "path": "mongo_ops.connection.MongoConnectionManager.get_client", - "signature": "", + "signature": "get_client() -> AsyncIOMotorClient", "docstring": "Retrieve the current client instance.\n\nReturns:\n AsyncIOMotorClient: The active Motor client instance.\n\nRaises:\n RuntimeError: If connect() has not been called yet." }, "lifespan": { "name": "lifespan", "kind": "function", "path": "mongo_ops.connection.MongoConnectionManager.lifespan", - "signature": "", - "docstring": "Async context manager for managing connection lifecycle.\n\nDesigned for use with FastAPI or other frameworks supporting \nlifespan management.\n\nArgs:\n uri: MongoDB connection URI.\n db_name: Name of the database.\n **kwargs: Additional Motor client options.\n\nYields:\n AsyncIOMotorDatabase: The active database instance.\n\nUsage:\n @asynccontextmanager\n async def lifespan(app: FastAPI):\n async with MongoConnectionManager.lifespan(uri, db_name):\n yield" + "signature": "lifespan(uri: str, db_name: str, **kwargs: Any) -> AbstractAsyncContextManager[AsyncIOMotorDatabase]", + "docstring": "Async context manager for managing connection lifecycle.\n\nDesigned for use with FastAPI or other frameworks supporting\nlifespan management.\n\nNotes:\n Connects to MongoDB on entry, yields the active database instance\n to the context body, and disconnects on exit.\n\nUsage:\n @asynccontextmanager\n async def lifespan(app: FastAPI):\n async with MongoConnectionManager.lifespan(uri, db_name):\n yield" } } } diff --git a/mongo-ops/mcp/modules/mongo_ops.json b/mongo-ops/mcp/modules/mongo_ops.json index fd2c0a7..607d07d 100644 --- a/mongo-ops/mcp/modules/mongo_ops.json +++ b/mongo-ops/mcp/modules/mongo_ops.json @@ -2,56 +2,56 @@ "module": "mongo_ops", "content": { "path": "mongo_ops", - "docstring": "mongo-ops: A modular MongoDB operations layer for FastAPI microservices.\n\nThis package provide a standardized way to interact with MongoDB in async \nPython applications, particularly optimized for FastAPI. It includes:\n\n- **Connection Management**: Async lifecycle management for Motor clients.\n- **Base Models**: Pydantic v2 models for MongoDB documents.\n- **Repository Pattern**: Generic CRUD operations and base repository classes.\n- **Caching**: In-memory and Redis cache backends with ID-based caching.\n- **Population**: Recursive document populating with cycle detection.\n- **Transactions**: Helpers for multi-document ACID transactions.\n- **Registry**: Centralized model, index, and cache lifecycle management.\n\nExample:\n ```python\n from mongo_ops import BaseDocument, BaseRepository, CachedBaseRepository\n from mongo_ops.cache import InMemoryCacheBackend, CacheConfig\n\n class User(BaseDocument):\n username: str\n\n class UserRepository(BaseRepository[User]):\n def __init__(self):\n super().__init__(\"users\", User)\n\n # With caching:\n cache = InMemoryCacheBackend()\n class CachedUserRepo(CachedBaseRepository[User]):\n def __init__(self):\n super().__init__(\"users\", User, cache)\n ```", + "docstring": "# Summary\n\nmongo-ops: A modular MongoDB operations layer for FastAPI microservices.\n\nThis package provide a standardized way to interact with MongoDB in async\nPython applications, particularly optimized for FastAPI. It includes:\n\n- **Connection Management**: Async lifecycle management for Motor clients.\n- **Base Models**: Pydantic v2 models for MongoDB documents.\n- **Repository Pattern**: Generic CRUD operations and base repository classes.\n- **Caching**: In-memory and Redis cache backends with ID-based caching.\n- **Population**: Recursive document populating with cycle detection.\n- **Transactions**: Helpers for multi-document ACID transactions.\n- **Registry**: Centralized model, index, and cache lifecycle management.\n\nExample:\n ```python\n from mongo_ops import BaseDocument, BaseRepository, CachedBaseRepository\n from mongo_ops.cache import InMemoryCacheBackend, CacheConfig\n\n class User(BaseDocument):\n username: str\n\n class UserRepository(BaseRepository[User]):\n def __init__(self):\n super().__init__(\"users\", User)\n\n # With caching:\n cache = InMemoryCacheBackend()\n class CachedUserRepo(CachedBaseRepository[User]):\n def __init__(self):\n super().__init__(\"users\", User, cache)\n ```", "objects": { "CachedBaseRepository": { "name": "CachedBaseRepository", "kind": "class", "path": "mongo_ops.CachedBaseRepository", - "signature": "", - "docstring": null, + "signature": "CachedBaseRepository(collection_name: str, model: type[T], cache_backend: CacheBackend, config: CacheConfig | None = None)", + "docstring": "Repository that reads and writes through a cache backend.\n\nWraps an existing BaseRepository with an ID-keyed cache. Reads consult\nthe backend first and fall through to MongoDB on a miss, populating the\ncache on success. Writes invalidate or refresh the affected key.\n\nNotes:\n **Guarantees:**\n\n - The cache holds the raw document shape (``model_dump``), so FK\n references round-trip as hex strings, not populated models.\n - When ``config.enabled`` is False the repository behaves exactly\n like its parent with no cache access.", "members": { "get_by_id": { "name": "get_by_id", "kind": "function", "path": "mongo_ops.CachedBaseRepository.get_by_id", - "signature": "", - "docstring": null + "signature": "get_by_id(id: str | ObjectId)", + "docstring": "Fetch a document, reading through the cache when enabled.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID.\n\nReturns:\n Optional[T]: The model instance, or None when not found." }, "create": { "name": "create", "kind": "function", "path": "mongo_ops.CachedBaseRepository.create", - "signature": "", - "docstring": null + "signature": "create(data: T)", + "docstring": "Insert a document and cache the raw snapshot.\n\nArgs:\n data (T):\n The model instance to insert.\n\nReturns:\n T: The created model instance, including its ID." }, "update": { "name": "update", "kind": "function", "path": "mongo_ops.CachedBaseRepository.update", - "signature": "", - "docstring": null + "signature": "update(id: str | ObjectId, data: dict)", + "docstring": "Update a document and refresh its cache entry.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID.\n data (dict):\n Fields to set via $set.\n\nReturns:\n Optional[T]: The updated model instance, or None when not found." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.CachedBaseRepository.delete", - "signature": "", - "docstring": null + "signature": "delete(id: str | ObjectId)", + "docstring": "Delete a document and remove its cache entry.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID.\n\nReturns:\n bool: True if a document was deleted, False otherwise." }, "warm_cache": { "name": "warm_cache", "kind": "function", "path": "mongo_ops.CachedBaseRepository.warm_cache", - "signature": "", - "docstring": null + "signature": "warm_cache(ids: list[str | ObjectId])", + "docstring": "Pre-populate the cache for a set of document IDs.\n\nDocs already present in the cache are skipped.\n\nArgs:\n ids (list[Union[str, ObjectId]]):\n Document IDs to warm.\n\nReturns:\n int: Number of entries added to the cache." }, "invalidate_cache": { "name": "invalidate_cache", "kind": "function", "path": "mongo_ops.CachedBaseRepository.invalidate_cache", - "signature": "", - "docstring": null + "signature": "invalidate_cache(id: str | ObjectId)", + "docstring": "Remove a single document's cache entry.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID to invalidate." } } }, @@ -59,43 +59,43 @@ "name": "MongoConnectionManager", "kind": "class", "path": "mongo_ops.MongoConnectionManager", - "signature": "", - "docstring": "Manages MongoDB connections with async lifecycle.\n\nThis class provides a singleton-like manager for the MongoDB client and \ndatabase instances, ensuring they are properly initialized and closed \nacross the application lifecycle.", + "signature": null, + "docstring": "Manages MongoDB connections with async lifecycle.\n\nThis class provides a singleton-like manager for the MongoDB client and\ndatabase instances, ensuring they are properly initialized and closed\nacross the application lifecycle.", "members": { "connect": { "name": "connect", "kind": "function", "path": "mongo_ops.MongoConnectionManager.connect", - "signature": "", - "docstring": "Connect to MongoDB and initialize the shared client.\n\nArgs:\n uri: MongoDB connection URI (e.g., \"mongodb://localhost:27017\").\n db_name: Name of the database to use.\n **kwargs: Additional Motor client options (e.g., maxPoolSize).\n\nReturns:\n AsyncIOMotorDatabase: The initialized database instance." + "signature": "connect(uri: str, db_name: str, **kwargs: Any)", + "docstring": "Connect to MongoDB and initialize the shared client.\n\nArgs:\n uri (str): MongoDB connection URI (e.g., \"mongodb://localhost:27017\").\n db_name (str): Name of the database to use.\n **kwargs: Additional Motor client options (e.g., maxPoolSize).\n\nReturns:\n AsyncIOMotorDatabase: The initialized database instance." }, "disconnect": { "name": "disconnect", "kind": "function", "path": "mongo_ops.MongoConnectionManager.disconnect", - "signature": "", + "signature": "disconnect()", "docstring": "Close the active MongoDB connection and cleanup resources." }, "get_database": { "name": "get_database", "kind": "function", "path": "mongo_ops.MongoConnectionManager.get_database", - "signature": "", + "signature": "get_database()", "docstring": "Retrieve the current database instance.\n\nReturns:\n AsyncIOMotorDatabase: The active database instance.\n\nRaises:\n RuntimeError: If connect() has not been called yet." }, "get_client": { "name": "get_client", "kind": "function", "path": "mongo_ops.MongoConnectionManager.get_client", - "signature": "", + "signature": "get_client()", "docstring": "Retrieve the current client instance.\n\nReturns:\n AsyncIOMotorClient: The active Motor client instance.\n\nRaises:\n RuntimeError: If connect() has not been called yet." }, "lifespan": { "name": "lifespan", "kind": "function", "path": "mongo_ops.MongoConnectionManager.lifespan", - "signature": "", - "docstring": "Async context manager for managing connection lifecycle.\n\nDesigned for use with FastAPI or other frameworks supporting \nlifespan management.\n\nArgs:\n uri: MongoDB connection URI.\n db_name: Name of the database.\n **kwargs: Additional Motor client options.\n\nYields:\n AsyncIOMotorDatabase: The active database instance.\n\nUsage:\n @asynccontextmanager\n async def lifespan(app: FastAPI):\n async with MongoConnectionManager.lifespan(uri, db_name):\n yield" + "signature": "lifespan(uri: str, db_name: str, **kwargs: Any)", + "docstring": "Async context manager for managing connection lifecycle.\n\nDesigned for use with FastAPI or other frameworks supporting\nlifespan management.\n\nNotes:\n Connects to MongoDB on entry, yields the active database instance\n to the context body, and disconnects on exit.\n\nUsage:\n @asynccontextmanager\n async def lifespan(app: FastAPI):\n async with MongoConnectionManager.lifespan(uri, db_name):\n yield" } } }, @@ -103,63 +103,63 @@ "name": "BaseDocument", "kind": "class", "path": "mongo_ops.BaseDocument", - "signature": "", - "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent \nMongoDB documents. It includes automatic handling of the `_id` field \nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", + "signature": null, + "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent\nMongoDB documents. It includes automatic handling of the `_id` field\nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", "members": { "id": { "name": "id", "kind": "attribute", "path": "mongo_ops.BaseDocument.id", - "signature": "", + "signature": null, "docstring": null }, "created_at": { "name": "created_at", "kind": "attribute", "path": "mongo_ops.BaseDocument.created_at", - "signature": "", + "signature": null, "docstring": null }, "updated_at": { "name": "updated_at", "kind": "attribute", "path": "mongo_ops.BaseDocument.updated_at", - "signature": "", + "signature": null, "docstring": null }, "Config": { "name": "Config", "kind": "class", "path": "mongo_ops.BaseDocument.Config", - "signature": "", + "signature": null, "docstring": null, "members": { "populate_by_name": { "name": "populate_by_name", "kind": "attribute", "path": "mongo_ops.BaseDocument.Config.populate_by_name", - "signature": "", + "signature": null, "docstring": null }, "arbitrary_types_allowed": { "name": "arbitrary_types_allowed", "kind": "attribute", "path": "mongo_ops.BaseDocument.Config.arbitrary_types_allowed", - "signature": "", + "signature": null, "docstring": null }, "json_encoders": { "name": "json_encoders", "kind": "attribute", "path": "mongo_ops.BaseDocument.Config.json_encoders", - "signature": "", + "signature": null, "docstring": null }, "json_schema_extra": { "name": "json_schema_extra", "kind": "attribute", "path": "mongo_ops.BaseDocument.Config.json_schema_extra", - "signature": "", + "signature": null, "docstring": null } } @@ -170,63 +170,63 @@ "name": "ModelRegistry", "kind": "class", "path": "mongo_ops.ModelRegistry", - "signature": "", - "docstring": "Registry for managing multiple models and their collections.\n\nThis registry allows central management of collections and their \nassociated indexes, making it easier to perform mass initialization \nat application startup.", + "signature": null, + "docstring": "Registry for managing multiple models and their collections.\n\nThis registry allows central management of collections and their\nassociated indexes, making it easier to perform mass initialization\nat application startup.", "members": { "register": { "name": "register", "kind": "function", "path": "mongo_ops.ModelRegistry.register", - "signature": "", - "docstring": "Register a model with its collection and indexes.\n\nArgs:\n collection_name: Name of the MongoDB collection.\n model: Document model class (subclass of BaseDocument).\n indexes: List of index specifications (e.g., [(\"email\", 1)]).\n\nUsage:\n ModelRegistry.register(\n \"users\",\n UserDocument,\n indexes=[(\"email\", 1), (\"created_at\", -1)]\n )" + "signature": "register(collection_name: str, model: type[BaseDocument], indexes: list[tuple] | None = None)", + "docstring": "Register a model with its collection and indexes.\n\nArgs:\n collection_name (str): Name of the MongoDB collection.\n model (type[BaseDocument]): Document model class (subclass of BaseDocument).\n indexes (list[Any]): List of index specifications. Each spec is passed\n directly to pymongo's create_index. Supported forms:\n - single field: (\"email\", 1)\n - compound: [(\"author_id\", 1), (\"created_at\", -1)]\n - with options: {\"keys\": [(\"username\", 1)], \"options\": {\"unique\": True}}\n\nExample:\n ModelRegistry.register(\"users\", UserDocument, indexes=[(\"email\", 1)])\n ModelRegistry.register(\n \"posts\",\n PostDocument,\n indexes=[[(\"author_id\", 1), (\"created_at\", -1)]],\n )" }, "initialize_all": { "name": "initialize_all", "kind": "function", "path": "mongo_ops.ModelRegistry.initialize_all", - "signature": "", - "docstring": "Initialize all registered collections and create indexes.\n\nThis method should be called during application startup to ensure \nall necessary indexes exist in the database.\n\nArgs:\n db: Database instance. If not provided, uses the global database \n from MongoConnectionManager." + "signature": "initialize_all(db: AsyncIOMotorDatabase | None = None)", + "docstring": "Initialize all registered collections and create indexes.\n\nThis method should be called during application startup to ensure\nall necessary indexes exist in the database.\n\nArgs:\n db (Optional[AsyncIOMotorDatabase]): Database instance. If not provided,\n uses the global database from MongoConnectionManager." }, "get_model": { "name": "get_model", "kind": "function", "path": "mongo_ops.ModelRegistry.get_model", - "signature": "", - "docstring": "Retrieve a registered model by its collection name.\n\nArgs:\n collection_name: The name of the collection.\n\nReturns:\n Type[BaseDocument]: The registered model class.\n\nRaises:\n KeyError: If the model for the given collection is not registered." + "signature": "get_model(collection_name: str)", + "docstring": "Retrieve a registered model by its collection name.\n\nArgs:\n collection_name (str): The name of the collection.\n\nReturns:\n type[BaseDocument]: The registered model class.\n\nRaises:\n KeyError: If the model for the given collection is not registered." }, "list_collections": { "name": "list_collections", "kind": "function", "path": "mongo_ops.ModelRegistry.list_collections", - "signature": "", - "docstring": "List all registered collection names.\n\nReturns:\n List[str]: A list of collection names." + "signature": "list_collections()", + "docstring": "List all registered collection names.\n\nReturns:\n list[str]: A list of collection names." }, "get_cache_backend": { "name": "get_cache_backend", "kind": "function", "path": "mongo_ops.ModelRegistry.get_cache_backend", - "signature": "", + "signature": "get_cache_backend()", "docstring": "Get the registered cache backend instance.\n\nReturns:\n Optional[CacheBackend]: The cache backend, if registered." }, "set_cache_backend": { "name": "set_cache_backend", "kind": "function", "path": "mongo_ops.ModelRegistry.set_cache_backend", - "signature": "", - "docstring": "Register a cache backend for all cache-enabled repositories.\n\nShould be called after MongoDB connection is established,\nbefore cache-backed repositories are used.\n\nArgs:\n backend: A CacheBackend instance (InMemoryCacheBackend\n or RedisCacheBackend)." + "signature": "set_cache_backend(backend: CacheBackend)", + "docstring": "Register a cache backend for all cache-enabled repositories.\n\nShould be called after MongoDB connection is established,\nbefore cache-backed repositories are used.\n\nArgs:\n backend (CacheBackend): A CacheBackend instance (InMemoryCacheBackend\n or RedisCacheBackend)." }, "initialize_cache": { "name": "initialize_cache", "kind": "function", "path": "mongo_ops.ModelRegistry.initialize_cache", - "signature": "", + "signature": "initialize_cache()", "docstring": "Initialize the registered cache backend.\n\nMust be called after set_cache_backend() and before any\ncache-backed repository operations. Typically called right\nafter MongoDB connection is established.\n\nRaises:\n RuntimeError: If no cache backend has been registered." }, "shutdown_cache": { "name": "shutdown_cache", "kind": "function", "path": "mongo_ops.ModelRegistry.shutdown_cache", - "signature": "", + "signature": "shutdown_cache()", "docstring": "Shutdown the registered cache backend gracefully.\n\nShould be called during application shutdown to clean up\nbackground tasks (e.g., in-memory TTL cleanup)." } } @@ -235,14 +235,14 @@ "name": "BaseRepository", "kind": "class", "path": "mongo_ops.BaseRepository", - "signature": "", - "docstring": "Base repository class combining CRUD operations and collection management.\n\nThis class simplifies repository creation by automatically obtaining the \ndatabase connection and collection instance.\n\nAttributes:\n collection_name: The name of the collection managed by this repository.", + "signature": "BaseRepository(collection_name: str, model: type[T])", + "docstring": "Base repository class combining CRUD operations and collection management.\n\nThis class simplifies repository creation by automatically obtaining the\ndatabase connection and collection instance.\n\nAttributes:\n collection_name: The name of the collection managed by this repository.", "members": { "collection_name": { "name": "collection_name", "kind": "attribute", "path": "mongo_ops.BaseRepository.collection_name", - "signature": "", + "signature": null, "docstring": null } } @@ -251,78 +251,78 @@ "name": "CRUDMixin", "kind": "class", "path": "mongo_ops.CRUDMixin", - "signature": "", - "docstring": "Generic CRUD operations mixin for MongoDB collections.\n\nThis mixin provides standard Create, Read, Update, and Delete operations \nthat work with Pydantic models.\n\nAttributes:\n collection: The Motor collection instance.\n model: The Pydantic model class representing the document.", + "signature": "CRUDMixin(collection: AsyncIOMotorCollection, model: type[T])", + "docstring": "Generic CRUD operations mixin for MongoDB collections.\n\nThis mixin provides standard Create, Read, Update, and Delete operations\nthat work with Pydantic models.\n\nAttributes:\n collection: The Motor collection instance.\n model: The Pydantic model class representing the document.", "members": { "collection": { "name": "collection", "kind": "attribute", "path": "mongo_ops.CRUDMixin.collection", - "signature": "", + "signature": null, "docstring": null }, "model": { "name": "model", "kind": "attribute", "path": "mongo_ops.CRUDMixin.model", - "signature": "", + "signature": null, "docstring": null }, "data_to_model": { "name": "data_to_model", "kind": "function", "path": "mongo_ops.CRUDMixin.data_to_model", - "signature": "", + "signature": "data_to_model(data: dict)", "docstring": null }, "create": { "name": "create", "kind": "function", "path": "mongo_ops.CRUDMixin.create", - "signature": "", - "docstring": "Create a new document in the collection.\n\nArgs:\n data: The Pydantic model instance to insert.\n\nReturns:\n T: The created Pydantic model instance, including the assigned ID." + "signature": "create(data: T)", + "docstring": "Create a new document in the collection.\n\nArgs:\n data (T): The Pydantic model instance to insert.\n\nReturns:\n T: The created Pydantic model instance, including the assigned ID." }, "get_by_id": { "name": "get_by_id", "kind": "function", "path": "mongo_ops.CRUDMixin.get_by_id", - "signature": "", - "docstring": "Retrieve a document by its ID.\n\nArgs:\n id: The document ID (string or ObjectId).\n\nReturns:\n Optional[T]: The Pydantic model instance if found, else None." + "signature": "get_by_id(id: str | ObjectId)", + "docstring": "Retrieve a document by its ID.\n\nArgs:\n id (Union[str, ObjectId]): The document ID (string or ObjectId).\n\nReturns:\n Optional[T]: The Pydantic model instance if found, else None." }, "get_many": { "name": "get_many", "kind": "function", "path": "mongo_ops.CRUDMixin.get_many", - "signature": "", - "docstring": "Retrieve multiple documents with filtering, pagination, and sorting.\n\nArgs:\n filter: MongoDB filter dictionary (e.g., {\"is_active\": True}).\n skip: Number of documents to skip for pagination.\n limit: Maximum number of documents to return (default 100).\n sort: List of sort specifications [(field, direction), ...].\n E.g., [(\"created_at\", -1)] for descending.\n\nReturns:\n List[T]: A list of Pydantic model instances.\n\nUsage:\n ```python\n users = await repo.get_many(\n filter={\"role\": \"admin\"},\n limit=10,\n sort=[(\"username\", 1)]\n )\n ```" + "signature": "get_many(filter: dict[str, Any] | None = None, skip: int = 0, limit: int = 100, sort: list[tuple] | None = None)", + "docstring": "Retrieve multiple documents with filtering, pagination, and sorting.\n\nArgs:\n filter (Optional[Dict[str, Any]]): MongoDB filter dictionary (e.g., {\"is_active\": True}).\n skip (int): Number of documents to skip for pagination.\n limit (int): Maximum number of documents to return (default 100).\n sort (Optional[List[tuple]]): List of sort specifications [(field, direction), ...].\n E.g., [(\"created_at\", -1)] for descending.\n\nReturns:\n List[T]: A list of Pydantic model instances.\n\nExample:\n ```python\n users = await repo.get_many(\n filter={\"role\": \"admin\"},\n limit=10,\n sort=[(\"username\", 1)]\n )\n ```" }, "update": { "name": "update", "kind": "function", "path": "mongo_ops.CRUDMixin.update", - "signature": "", - "docstring": "Update a document by its ID using the $set operator.\n\nArgs:\n id: The document ID (string or ObjectId).\n data: A dictionary of fields and values to update.\n\nReturns:\n Optional[T]: The updated Pydantic model instance if found, else None.\n\nUsage:\n ```python\n updated_user = await repo.update(user_id, {\"email\": \"new@example.com\"})\n ```" + "signature": "update(id: str | ObjectId, data: dict[str, Any])", + "docstring": "Update a document by its ID using the $set operator.\n\nArgs:\n id (Union[str, ObjectId]): The document ID (string or ObjectId).\n data (Dict[str, Any]): A dictionary of fields and values to update.\n\nReturns:\n Optional[T]: The updated Pydantic model instance if found, else None.\n\nExample:\n ```python\n updated_user = await repo.update(user_id, {\"email\": \"new@example.com\"})\n ```" }, "patch": { "name": "patch", "kind": "function", "path": "mongo_ops.CRUDMixin.patch", - "signature": "", - "docstring": "Partially update a document using $set (REST PATCH semantics).\n\nUnlike update(), patch() takes a partial dict and applies only those\nfields. PopulatingRepository overrides this to prevent patching FK fields.\n\nArgs:\n id: The document ID (string or ObjectId).\n data: A partial dictionary of fields and values to update.\n\nReturns:\n Optional[T]: The updated Pydantic model instance if found, else None." + "signature": "patch(id: str | ObjectId, data: dict[str, Any])", + "docstring": "Partially update a document using $set (REST PATCH semantics).\n\nUnlike update(), patch() takes a partial dict and applies only those\nfields. PopulatingRepository overrides this to prevent patching FK fields.\n\nArgs:\n id (Union[str, ObjectId]): The document ID (string or ObjectId).\n data (Dict[str, Any]): A partial dictionary of fields and values to update.\n\nReturns:\n Optional[T]: The updated Pydantic model instance if found, else None." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.CRUDMixin.delete", - "signature": "", - "docstring": "Delete a document by its ID.\n\nArgs:\n id: The document ID (string or ObjectId).\n\nReturns:\n bool: True if a document was deleted, False otherwise." + "signature": "delete(id: str | ObjectId)", + "docstring": "Delete a document by its ID.\n\nArgs:\n id (Union[str, ObjectId]): The document ID (string or ObjectId).\n\nReturns:\n bool: True if a document was deleted, False otherwise." }, "count": { "name": "count", "kind": "function", "path": "mongo_ops.CRUDMixin.count", - "signature": "", - "docstring": "Count documents matching a filter.\n\nArgs:\n filter: MongoDB filter dictionary.\n\nReturns:\n int: The number of matching documents." + "signature": "count(filter: dict[str, Any] | None = None)", + "docstring": "Count documents matching a filter.\n\nArgs:\n filter (Optional[Dict[str, Any]]): MongoDB filter dictionary.\n\nReturns:\n int: The number of matching documents." } } }, @@ -330,57 +330,57 @@ "name": "PopulatingRepository", "kind": "class", "path": "mongo_ops.PopulatingRepository", - "signature": "", - "docstring": null, + "signature": "PopulatingRepository(collection_name: str, model: type[T], population_engine: PopulationEngine | None = None, populate_rules: list[PopulateRule] | None = None)", + "docstring": "Repository that auto-populates and depopulates FK references.\n\nOn read, ObjectId FK fields are resolved to model instances according to\nthe configured PopulateRules. On write, populated model references are\ncollapsed back to ObjectIds before hitting MongoDB.\n\nNotes:\n **Guarantees:**\n\n - Populate rules apply on both read (data_to_model) and write\n (create/update) paths.\n - Patching FK fields via patch() is rejected.\n - Class attributes `population_engine` and `_populate_rules` must\n be set (see set_population_engine/set_populate_rules) for any\n population to occur.", "members": { "population_engine": { "name": "population_engine", "kind": "attribute", "path": "mongo_ops.PopulatingRepository.population_engine", - "signature": "", + "signature": null, "docstring": null }, "set_population_engine": { "name": "set_population_engine", "kind": "function", "path": "mongo_ops.PopulatingRepository.set_population_engine", - "signature": "", - "docstring": null + "signature": "set_population_engine(engine: PopulationEngine)", + "docstring": "Attach (or replace) the population engine.\n\nArgs:\n engine (PopulationEngine):\n Engine used to resolve references." }, "set_populate_rules": { "name": "set_populate_rules", "kind": "function", "path": "mongo_ops.PopulatingRepository.set_populate_rules", - "signature": "", - "docstring": null + "signature": "set_populate_rules(rules: list[PopulateRule])", + "docstring": "Set the FK resolution rules.\n\nArgs:\n rules (list[PopulateRule]):\n Rules describing which fields resolve and how deep." }, "data_to_model": { "name": "data_to_model", "kind": "function", "path": "mongo_ops.PopulatingRepository.data_to_model", - "signature": "", - "docstring": null + "signature": "data_to_model(data: dict)", + "docstring": "Convert a raw dict to a model, resolving FK references first.\n\nArgs:\n data (dict):\n Raw document dictionary.\n\nReturns:\n T: The populated model instance.\n\nRaises:\n ValueError: If a FK field holds an embedded dict instead of an\n ObjectId." }, "create": { "name": "create", "kind": "function", "path": "mongo_ops.PopulatingRepository.create", - "signature": "", - "docstring": null + "signature": "create(data: T)", + "docstring": "Depopulate, insert, and re-populate a new document.\n\nArgs:\n data (T):\n The model instance to insert (FK fields may hold models).\n\nReturns:\n T: The created model instance, including its ID." }, "update": { "name": "update", "kind": "function", "path": "mongo_ops.PopulatingRepository.update", - "signature": "", - "docstring": null + "signature": "update(id: str | ObjectId, data: T)", + "docstring": "Depopulate and update a document by ID.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID.\n data (T):\n The model instance holding the updated fields.\n\nReturns:\n Optional[T]: The updated model instance, or None when not found." }, "patch": { "name": "patch", "kind": "function", "path": "mongo_ops.PopulatingRepository.patch", - "signature": "", - "docstring": null + "signature": "patch(id: str | ObjectId, data: dict[str, Any])", + "docstring": "Partially update a document, rejecting FK field changes.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID.\n data (dict[str, Any]):\n Partial dictionary of fields to update.\n\nReturns:\n Optional[T]: The updated model instance, or None when not found.\n\nRaises:\n ValueError: If any FK field is present in the patch payload." } } }, @@ -388,22 +388,22 @@ "name": "TransactionManager", "kind": "class", "path": "mongo_ops.TransactionManager", - "signature": "", - "docstring": "Simplified multi-document transaction handling.\n\nThis class provides helpers for executing operations within a MongoDB \ntransaction, ensuring ACID compliance for multi-document updates.", + "signature": null, + "docstring": "Simplified multi-document transaction handling.\n\nThis class provides helpers for executing operations within a MongoDB\ntransaction, ensuring ACID compliance for multi-document updates.", "members": { "start_session": { "name": "start_session", "kind": "function", "path": "mongo_ops.TransactionManager.start_session", - "signature": "", - "docstring": "Start a transaction session as an async context manager.\n\nArgs:\n **kwargs: Transaction options (e.g., read_concern, write_concern).\n\nYields:\n AsyncIOMotorClientSession: The active session with a started transaction.\n\nUsage:\n async with TransactionManager.start_session() as session:\n await collection.insert_one(doc, session=session)\n await other_collection.update_one(filter, update, session=session)" + "signature": "start_session(**kwargs: Any)", + "docstring": "Start a transaction session as an async context manager.\n\nNotes:\n Yields the active session with a started transaction; callers\n run their operations against the session inside the with-block.\n\nUsage:\n async with TransactionManager.start_session() as session:\n await collection.insert_one(doc, session=session)\n await other_collection.update_one(filter, update, session=session)" }, "execute_transaction": { "name": "execute_transaction", "kind": "function", "path": "mongo_ops.TransactionManager.execute_transaction", - "signature": "", - "docstring": "Execute multiple operations within a single transaction.\n\nArgs:\n operations: A list of async callables that accept a session \n parameter and return a result.\n **kwargs: Transaction options.\n\nReturns:\n List[Any]: A list containing the results of each operation.\n\nUsage:\n results = await TransactionManager.execute_transaction([\n lambda s: repo1.create(data1, session=s),\n lambda s: repo2.update(id, data2, session=s),\n ])" + "signature": "execute_transaction(operations: list[Callable[[AsyncIOMotorClientSession], Awaitable[Any]]], **kwargs: Any)", + "docstring": "Execute multiple operations within a single transaction.\n\nArgs:\n operations (List[Callable[[AsyncIOMotorClientSession], Awaitable[Any]]]): A list of async\n callables that accept a session parameter and return a result.\n **kwargs: Transaction options.\n\nReturns:\n List[Any]: A list containing the results of each operation.\n\nExample:\n results = await TransactionManager.execute_transaction([\n lambda s: repo1.create(data1, session=s),\n lambda s: repo2.update(id, data2, session=s),\n ])" } } }, @@ -412,77 +412,49 @@ "kind": "module", "path": "mongo_ops.connection", "signature": null, - "docstring": "MongoDB connection management.", + "docstring": "# Summary\n\nMongoDB connection management.", "members": { - "asynccontextmanager": { - "name": "asynccontextmanager", - "kind": "alias", - "path": "mongo_ops.connection.asynccontextmanager", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.connection.Optional", - "signature": "", - "docstring": null - }, - "AsyncIOMotorClient": { - "name": "AsyncIOMotorClient", - "kind": "alias", - "path": "mongo_ops.connection.AsyncIOMotorClient", - "signature": "", - "docstring": null - }, - "AsyncIOMotorDatabase": { - "name": "AsyncIOMotorDatabase", - "kind": "alias", - "path": "mongo_ops.connection.AsyncIOMotorDatabase", - "signature": "", - "docstring": null - }, "MongoConnectionManager": { "name": "MongoConnectionManager", "kind": "class", "path": "mongo_ops.connection.MongoConnectionManager", - "signature": "", - "docstring": "Manages MongoDB connections with async lifecycle.\n\nThis class provides a singleton-like manager for the MongoDB client and \ndatabase instances, ensuring they are properly initialized and closed \nacross the application lifecycle.", + "signature": null, + "docstring": "Manages MongoDB connections with async lifecycle.\n\nThis class provides a singleton-like manager for the MongoDB client and\ndatabase instances, ensuring they are properly initialized and closed\nacross the application lifecycle.", "members": { "connect": { "name": "connect", "kind": "function", "path": "mongo_ops.connection.MongoConnectionManager.connect", - "signature": "", - "docstring": "Connect to MongoDB and initialize the shared client.\n\nArgs:\n uri: MongoDB connection URI (e.g., \"mongodb://localhost:27017\").\n db_name: Name of the database to use.\n **kwargs: Additional Motor client options (e.g., maxPoolSize).\n\nReturns:\n AsyncIOMotorDatabase: The initialized database instance." + "signature": "connect(uri: str, db_name: str, **kwargs: Any) -> AsyncIOMotorDatabase", + "docstring": "Connect to MongoDB and initialize the shared client.\n\nArgs:\n uri (str): MongoDB connection URI (e.g., \"mongodb://localhost:27017\").\n db_name (str): Name of the database to use.\n **kwargs: Additional Motor client options (e.g., maxPoolSize).\n\nReturns:\n AsyncIOMotorDatabase: The initialized database instance." }, "disconnect": { "name": "disconnect", "kind": "function", "path": "mongo_ops.connection.MongoConnectionManager.disconnect", - "signature": "", + "signature": "disconnect() -> None", "docstring": "Close the active MongoDB connection and cleanup resources." }, "get_database": { "name": "get_database", "kind": "function", "path": "mongo_ops.connection.MongoConnectionManager.get_database", - "signature": "", + "signature": "get_database() -> AsyncIOMotorDatabase", "docstring": "Retrieve the current database instance.\n\nReturns:\n AsyncIOMotorDatabase: The active database instance.\n\nRaises:\n RuntimeError: If connect() has not been called yet." }, "get_client": { "name": "get_client", "kind": "function", "path": "mongo_ops.connection.MongoConnectionManager.get_client", - "signature": "", + "signature": "get_client() -> AsyncIOMotorClient", "docstring": "Retrieve the current client instance.\n\nReturns:\n AsyncIOMotorClient: The active Motor client instance.\n\nRaises:\n RuntimeError: If connect() has not been called yet." }, "lifespan": { "name": "lifespan", "kind": "function", "path": "mongo_ops.connection.MongoConnectionManager.lifespan", - "signature": "", - "docstring": "Async context manager for managing connection lifecycle.\n\nDesigned for use with FastAPI or other frameworks supporting \nlifespan management.\n\nArgs:\n uri: MongoDB connection URI.\n db_name: Name of the database.\n **kwargs: Additional Motor client options.\n\nYields:\n AsyncIOMotorDatabase: The active database instance.\n\nUsage:\n @asynccontextmanager\n async def lifespan(app: FastAPI):\n async with MongoConnectionManager.lifespan(uri, db_name):\n yield" + "signature": "lifespan(uri: str, db_name: str, **kwargs: Any) -> AbstractAsyncContextManager[AsyncIOMotorDatabase]", + "docstring": "Async context manager for managing connection lifecycle.\n\nDesigned for use with FastAPI or other frameworks supporting\nlifespan management.\n\nNotes:\n Connects to MongoDB on entry, yields the active database instance\n to the context body, and disconnects on exit.\n\nUsage:\n @asynccontextmanager\n async def lifespan(app: FastAPI):\n async with MongoConnectionManager.lifespan(uri, db_name):\n yield" } } } @@ -493,77 +465,21 @@ "kind": "module", "path": "mongo_ops.models", "signature": null, - "docstring": "Base document models for MongoDB.", + "docstring": "# Summary\n\nBase document models for MongoDB.", "members": { - "datetime": { - "name": "datetime", - "kind": "alias", - "path": "mongo_ops.models.datetime", - "signature": "", - "docstring": null - }, - "Any": { - "name": "Any", - "kind": "alias", - "path": "mongo_ops.models.Any", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.models.Optional", - "signature": "", - "docstring": null - }, - "ObjectId": { - "name": "ObjectId", - "kind": "alias", - "path": "mongo_ops.models.ObjectId", - "signature": "", - "docstring": null - }, - "BaseModel": { - "name": "BaseModel", - "kind": "alias", - "path": "mongo_ops.models.BaseModel", - "signature": "", - "docstring": null - }, - "Field": { - "name": "Field", - "kind": "alias", - "path": "mongo_ops.models.Field", - "signature": "", - "docstring": null - }, - "GetCoreSchemaHandler": { - "name": "GetCoreSchemaHandler", - "kind": "alias", - "path": "mongo_ops.models.GetCoreSchemaHandler", - "signature": "", - "docstring": null - }, - "core_schema": { - "name": "core_schema", - "kind": "alias", - "path": "mongo_ops.models.core_schema", - "signature": "", - "docstring": null - }, "PyObjectId": { "name": "PyObjectId", "kind": "class", "path": "mongo_ops.models.PyObjectId", - "signature": "", - "docstring": "Custom ObjectId type compatible with Pydantic v2.\n\nThis class extends the standard BSON ObjectId to provide validation and \nserialization support within Pydantic models.", + "signature": null, + "docstring": "Custom ObjectId type compatible with Pydantic v2.\n\nThis class extends the standard BSON ObjectId to provide validation and\nserialization support within Pydantic models.", "members": { "validate": { "name": "validate", "kind": "function", "path": "mongo_ops.models.PyObjectId.validate", - "signature": "", - "docstring": "Validate the input value and convert it to an ObjectId if possible.\n\nArgs:\n v: The value to validate (can be str or ObjectId).\n\nReturns:\n ObjectId: The validated ObjectId instance.\n\nRaises:\n ValueError: If the value is not a valid ObjectId." + "signature": "validate(v: Any) -> ObjectId", + "docstring": "Validate the input value and convert it to an ObjectId if possible.\n\nArgs:\n v (Any): The value to validate (can be str or ObjectId).\n\nReturns:\n ObjectId: The validated ObjectId instance.\n\nRaises:\n ValueError: If the value is not a valid ObjectId." } } }, @@ -571,8 +487,8 @@ "name": "BaseDocument", "kind": "class", "path": "mongo_ops.models.BaseDocument", - "signature": "", - "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent \nMongoDB documents. It includes automatic handling of the `_id` field \nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", + "signature": null, + "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent\nMongoDB documents. It includes automatic handling of the `_id` field\nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", "members": { "id": { "name": "id", @@ -599,7 +515,7 @@ "name": "Config", "kind": "class", "path": "mongo_ops.models.BaseDocument.Config", - "signature": "", + "signature": null, "docstring": null, "members": { "populate_by_name": { @@ -641,105 +557,70 @@ "kind": "module", "path": "mongo_ops.registry", "signature": null, - "docstring": "Model registration for multi-service initialization.", + "docstring": "# Summary\n\nModel registration for multi-service initialization.", "members": { - "Dict": { - "name": "Dict", - "kind": "alias", - "path": "mongo_ops.registry.Dict", - "signature": "", - "docstring": null - }, - "List": { - "name": "List", - "kind": "alias", - "path": "mongo_ops.registry.List", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.registry.Optional", - "signature": "", - "docstring": null - }, - "Type": { - "name": "Type", - "kind": "alias", - "path": "mongo_ops.registry.Type", - "signature": "", - "docstring": null - }, - "AsyncIOMotorDatabase": { - "name": "AsyncIOMotorDatabase", - "kind": "alias", - "path": "mongo_ops.registry.AsyncIOMotorDatabase", - "signature": "", - "docstring": null - }, "CacheBackend": { "name": "CacheBackend", "kind": "class", "path": "mongo_ops.registry.CacheBackend", - "signature": "", - "docstring": null, + "signature": null, + "docstring": "Abstract interface for cache backends.\n\nImplementations store byte-encoded values keyed by string, track usage\nstatistics, and manage their own lifecycle. The in-memory and Redis\nbackends both implement this contract.", "members": { "get": { "name": "get", "kind": "function", "path": "mongo_ops.registry.CacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str)", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.registry.CacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None)", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds. When None, the backend default\n applies." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.registry.CacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str)", + "docstring": "Remove a key from the cache.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.registry.CacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str)", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.registry.CacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str)", + "docstring": "Remove all keys matching a glob pattern.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.registry.CacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats()", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: A copy of the current stats counters." }, "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.registry.CacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize()", + "docstring": "Start background resources owned by the backend.\n\nShould be called once during application startup, after the\nrepositories are connected." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.registry.CacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown()", + "docstring": "Stop and release background resources.\n\nShould be called once during application shutdown." } } }, @@ -747,43 +628,43 @@ "name": "MongoConnectionManager", "kind": "class", "path": "mongo_ops.registry.MongoConnectionManager", - "signature": "", - "docstring": "Manages MongoDB connections with async lifecycle.\n\nThis class provides a singleton-like manager for the MongoDB client and \ndatabase instances, ensuring they are properly initialized and closed \nacross the application lifecycle.", + "signature": null, + "docstring": "Manages MongoDB connections with async lifecycle.\n\nThis class provides a singleton-like manager for the MongoDB client and\ndatabase instances, ensuring they are properly initialized and closed\nacross the application lifecycle.", "members": { "connect": { "name": "connect", "kind": "function", "path": "mongo_ops.registry.MongoConnectionManager.connect", - "signature": "", - "docstring": "Connect to MongoDB and initialize the shared client.\n\nArgs:\n uri: MongoDB connection URI (e.g., \"mongodb://localhost:27017\").\n db_name: Name of the database to use.\n **kwargs: Additional Motor client options (e.g., maxPoolSize).\n\nReturns:\n AsyncIOMotorDatabase: The initialized database instance." + "signature": "connect(uri: str, db_name: str, **kwargs: Any)", + "docstring": "Connect to MongoDB and initialize the shared client.\n\nArgs:\n uri (str): MongoDB connection URI (e.g., \"mongodb://localhost:27017\").\n db_name (str): Name of the database to use.\n **kwargs: Additional Motor client options (e.g., maxPoolSize).\n\nReturns:\n AsyncIOMotorDatabase: The initialized database instance." }, "disconnect": { "name": "disconnect", "kind": "function", "path": "mongo_ops.registry.MongoConnectionManager.disconnect", - "signature": "", + "signature": "disconnect()", "docstring": "Close the active MongoDB connection and cleanup resources." }, "get_database": { "name": "get_database", "kind": "function", "path": "mongo_ops.registry.MongoConnectionManager.get_database", - "signature": "", + "signature": "get_database()", "docstring": "Retrieve the current database instance.\n\nReturns:\n AsyncIOMotorDatabase: The active database instance.\n\nRaises:\n RuntimeError: If connect() has not been called yet." }, "get_client": { "name": "get_client", "kind": "function", "path": "mongo_ops.registry.MongoConnectionManager.get_client", - "signature": "", + "signature": "get_client()", "docstring": "Retrieve the current client instance.\n\nReturns:\n AsyncIOMotorClient: The active Motor client instance.\n\nRaises:\n RuntimeError: If connect() has not been called yet." }, "lifespan": { "name": "lifespan", "kind": "function", "path": "mongo_ops.registry.MongoConnectionManager.lifespan", - "signature": "", - "docstring": "Async context manager for managing connection lifecycle.\n\nDesigned for use with FastAPI or other frameworks supporting \nlifespan management.\n\nArgs:\n uri: MongoDB connection URI.\n db_name: Name of the database.\n **kwargs: Additional Motor client options.\n\nYields:\n AsyncIOMotorDatabase: The active database instance.\n\nUsage:\n @asynccontextmanager\n async def lifespan(app: FastAPI):\n async with MongoConnectionManager.lifespan(uri, db_name):\n yield" + "signature": "lifespan(uri: str, db_name: str, **kwargs: Any)", + "docstring": "Async context manager for managing connection lifecycle.\n\nDesigned for use with FastAPI or other frameworks supporting\nlifespan management.\n\nNotes:\n Connects to MongoDB on entry, yields the active database instance\n to the context body, and disconnects on exit.\n\nUsage:\n @asynccontextmanager\n async def lifespan(app: FastAPI):\n async with MongoConnectionManager.lifespan(uri, db_name):\n yield" } } }, @@ -791,63 +672,63 @@ "name": "BaseDocument", "kind": "class", "path": "mongo_ops.registry.BaseDocument", - "signature": "", - "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent \nMongoDB documents. It includes automatic handling of the `_id` field \nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", + "signature": null, + "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent\nMongoDB documents. It includes automatic handling of the `_id` field\nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", "members": { "id": { "name": "id", "kind": "attribute", "path": "mongo_ops.registry.BaseDocument.id", - "signature": "", + "signature": null, "docstring": null }, "created_at": { "name": "created_at", "kind": "attribute", "path": "mongo_ops.registry.BaseDocument.created_at", - "signature": "", + "signature": null, "docstring": null }, "updated_at": { "name": "updated_at", "kind": "attribute", "path": "mongo_ops.registry.BaseDocument.updated_at", - "signature": "", + "signature": null, "docstring": null }, "Config": { "name": "Config", "kind": "class", "path": "mongo_ops.registry.BaseDocument.Config", - "signature": "", + "signature": null, "docstring": null, "members": { "populate_by_name": { "name": "populate_by_name", "kind": "attribute", "path": "mongo_ops.registry.BaseDocument.Config.populate_by_name", - "signature": "", + "signature": null, "docstring": null }, "arbitrary_types_allowed": { "name": "arbitrary_types_allowed", "kind": "attribute", "path": "mongo_ops.registry.BaseDocument.Config.arbitrary_types_allowed", - "signature": "", + "signature": null, "docstring": null }, "json_encoders": { "name": "json_encoders", "kind": "attribute", "path": "mongo_ops.registry.BaseDocument.Config.json_encoders", - "signature": "", + "signature": null, "docstring": null }, "json_schema_extra": { "name": "json_schema_extra", "kind": "attribute", "path": "mongo_ops.registry.BaseDocument.Config.json_schema_extra", - "signature": "", + "signature": null, "docstring": null } } @@ -858,63 +739,63 @@ "name": "ModelRegistry", "kind": "class", "path": "mongo_ops.registry.ModelRegistry", - "signature": "", - "docstring": "Registry for managing multiple models and their collections.\n\nThis registry allows central management of collections and their \nassociated indexes, making it easier to perform mass initialization \nat application startup.", + "signature": null, + "docstring": "Registry for managing multiple models and their collections.\n\nThis registry allows central management of collections and their\nassociated indexes, making it easier to perform mass initialization\nat application startup.", "members": { "register": { "name": "register", "kind": "function", "path": "mongo_ops.registry.ModelRegistry.register", - "signature": "", - "docstring": "Register a model with its collection and indexes.\n\nArgs:\n collection_name: Name of the MongoDB collection.\n model: Document model class (subclass of BaseDocument).\n indexes: List of index specifications (e.g., [(\"email\", 1)]).\n\nUsage:\n ModelRegistry.register(\n \"users\",\n UserDocument,\n indexes=[(\"email\", 1), (\"created_at\", -1)]\n )" + "signature": "register(collection_name: str, model: type[BaseDocument], indexes: list[tuple] | None = None) -> None", + "docstring": "Register a model with its collection and indexes.\n\nArgs:\n collection_name (str): Name of the MongoDB collection.\n model (type[BaseDocument]): Document model class (subclass of BaseDocument).\n indexes (list[Any]): List of index specifications. Each spec is passed\n directly to pymongo's create_index. Supported forms:\n - single field: (\"email\", 1)\n - compound: [(\"author_id\", 1), (\"created_at\", -1)]\n - with options: {\"keys\": [(\"username\", 1)], \"options\": {\"unique\": True}}\n\nExample:\n ModelRegistry.register(\"users\", UserDocument, indexes=[(\"email\", 1)])\n ModelRegistry.register(\n \"posts\",\n PostDocument,\n indexes=[[(\"author_id\", 1), (\"created_at\", -1)]],\n )" }, "initialize_all": { "name": "initialize_all", "kind": "function", "path": "mongo_ops.registry.ModelRegistry.initialize_all", - "signature": "", - "docstring": "Initialize all registered collections and create indexes.\n\nThis method should be called during application startup to ensure \nall necessary indexes exist in the database.\n\nArgs:\n db: Database instance. If not provided, uses the global database \n from MongoConnectionManager." + "signature": "initialize_all(db: AsyncIOMotorDatabase | None = None) -> None", + "docstring": "Initialize all registered collections and create indexes.\n\nThis method should be called during application startup to ensure\nall necessary indexes exist in the database.\n\nArgs:\n db (Optional[AsyncIOMotorDatabase]): Database instance. If not provided,\n uses the global database from MongoConnectionManager." }, "get_model": { "name": "get_model", "kind": "function", "path": "mongo_ops.registry.ModelRegistry.get_model", - "signature": "", - "docstring": "Retrieve a registered model by its collection name.\n\nArgs:\n collection_name: The name of the collection.\n\nReturns:\n Type[BaseDocument]: The registered model class.\n\nRaises:\n KeyError: If the model for the given collection is not registered." + "signature": "get_model(collection_name: str) -> type[BaseDocument]", + "docstring": "Retrieve a registered model by its collection name.\n\nArgs:\n collection_name (str): The name of the collection.\n\nReturns:\n type[BaseDocument]: The registered model class.\n\nRaises:\n KeyError: If the model for the given collection is not registered." }, "list_collections": { "name": "list_collections", "kind": "function", "path": "mongo_ops.registry.ModelRegistry.list_collections", - "signature": "", - "docstring": "List all registered collection names.\n\nReturns:\n List[str]: A list of collection names." + "signature": "list_collections() -> list[str]", + "docstring": "List all registered collection names.\n\nReturns:\n list[str]: A list of collection names." }, "get_cache_backend": { "name": "get_cache_backend", "kind": "function", "path": "mongo_ops.registry.ModelRegistry.get_cache_backend", - "signature": "", + "signature": "get_cache_backend() -> CacheBackend | None", "docstring": "Get the registered cache backend instance.\n\nReturns:\n Optional[CacheBackend]: The cache backend, if registered." }, "set_cache_backend": { "name": "set_cache_backend", "kind": "function", "path": "mongo_ops.registry.ModelRegistry.set_cache_backend", - "signature": "", - "docstring": "Register a cache backend for all cache-enabled repositories.\n\nShould be called after MongoDB connection is established,\nbefore cache-backed repositories are used.\n\nArgs:\n backend: A CacheBackend instance (InMemoryCacheBackend\n or RedisCacheBackend)." + "signature": "set_cache_backend(backend: CacheBackend) -> None", + "docstring": "Register a cache backend for all cache-enabled repositories.\n\nShould be called after MongoDB connection is established,\nbefore cache-backed repositories are used.\n\nArgs:\n backend (CacheBackend): A CacheBackend instance (InMemoryCacheBackend\n or RedisCacheBackend)." }, "initialize_cache": { "name": "initialize_cache", "kind": "function", "path": "mongo_ops.registry.ModelRegistry.initialize_cache", - "signature": "", + "signature": "initialize_cache() -> None", "docstring": "Initialize the registered cache backend.\n\nMust be called after set_cache_backend() and before any\ncache-backed repository operations. Typically called right\nafter MongoDB connection is established.\n\nRaises:\n RuntimeError: If no cache backend has been registered." }, "shutdown_cache": { "name": "shutdown_cache", "kind": "function", "path": "mongo_ops.registry.ModelRegistry.shutdown_cache", - "signature": "", + "signature": "shutdown_cache() -> None", "docstring": "Shutdown the registered cache backend gracefully.\n\nShould be called during application shutdown to clean up\nbackground tasks (e.g., in-memory TTL cleanup)." } } @@ -926,119 +807,49 @@ "kind": "module", "path": "mongo_ops.repository", "signature": null, - "docstring": "Repository patterns and CRUD mixins for MongoDB.", + "docstring": "# Summary\n\nRepository patterns and CRUD mixins for MongoDB.", "members": { - "datetime": { - "name": "datetime", - "kind": "alias", - "path": "mongo_ops.repository.datetime", - "signature": "", - "docstring": null - }, - "Any": { - "name": "Any", - "kind": "alias", - "path": "mongo_ops.repository.Any", - "signature": "", - "docstring": null - }, - "Dict": { - "name": "Dict", - "kind": "alias", - "path": "mongo_ops.repository.Dict", - "signature": "", - "docstring": null - }, - "Generic": { - "name": "Generic", - "kind": "alias", - "path": "mongo_ops.repository.Generic", - "signature": "", - "docstring": null - }, - "List": { - "name": "List", - "kind": "alias", - "path": "mongo_ops.repository.List", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.repository.Optional", - "signature": "", - "docstring": null - }, - "TypeVar": { - "name": "TypeVar", - "kind": "alias", - "path": "mongo_ops.repository.TypeVar", - "signature": "", - "docstring": null - }, - "Union": { - "name": "Union", - "kind": "alias", - "path": "mongo_ops.repository.Union", - "signature": "", - "docstring": null - }, - "ObjectId": { - "name": "ObjectId", - "kind": "alias", - "path": "mongo_ops.repository.ObjectId", - "signature": "", - "docstring": null - }, - "AsyncIOMotorCollection": { - "name": "AsyncIOMotorCollection", - "kind": "alias", - "path": "mongo_ops.repository.AsyncIOMotorCollection", - "signature": "", - "docstring": null - }, "MongoConnectionManager": { "name": "MongoConnectionManager", "kind": "class", "path": "mongo_ops.repository.MongoConnectionManager", - "signature": "", - "docstring": "Manages MongoDB connections with async lifecycle.\n\nThis class provides a singleton-like manager for the MongoDB client and \ndatabase instances, ensuring they are properly initialized and closed \nacross the application lifecycle.", + "signature": null, + "docstring": "Manages MongoDB connections with async lifecycle.\n\nThis class provides a singleton-like manager for the MongoDB client and\ndatabase instances, ensuring they are properly initialized and closed\nacross the application lifecycle.", "members": { "connect": { "name": "connect", "kind": "function", "path": "mongo_ops.repository.MongoConnectionManager.connect", - "signature": "", - "docstring": "Connect to MongoDB and initialize the shared client.\n\nArgs:\n uri: MongoDB connection URI (e.g., \"mongodb://localhost:27017\").\n db_name: Name of the database to use.\n **kwargs: Additional Motor client options (e.g., maxPoolSize).\n\nReturns:\n AsyncIOMotorDatabase: The initialized database instance." + "signature": "connect(uri: str, db_name: str, **kwargs: Any)", + "docstring": "Connect to MongoDB and initialize the shared client.\n\nArgs:\n uri (str): MongoDB connection URI (e.g., \"mongodb://localhost:27017\").\n db_name (str): Name of the database to use.\n **kwargs: Additional Motor client options (e.g., maxPoolSize).\n\nReturns:\n AsyncIOMotorDatabase: The initialized database instance." }, "disconnect": { "name": "disconnect", "kind": "function", "path": "mongo_ops.repository.MongoConnectionManager.disconnect", - "signature": "", + "signature": "disconnect()", "docstring": "Close the active MongoDB connection and cleanup resources." }, "get_database": { "name": "get_database", "kind": "function", "path": "mongo_ops.repository.MongoConnectionManager.get_database", - "signature": "", + "signature": "get_database()", "docstring": "Retrieve the current database instance.\n\nReturns:\n AsyncIOMotorDatabase: The active database instance.\n\nRaises:\n RuntimeError: If connect() has not been called yet." }, "get_client": { "name": "get_client", "kind": "function", "path": "mongo_ops.repository.MongoConnectionManager.get_client", - "signature": "", + "signature": "get_client()", "docstring": "Retrieve the current client instance.\n\nReturns:\n AsyncIOMotorClient: The active Motor client instance.\n\nRaises:\n RuntimeError: If connect() has not been called yet." }, "lifespan": { "name": "lifespan", "kind": "function", "path": "mongo_ops.repository.MongoConnectionManager.lifespan", - "signature": "", - "docstring": "Async context manager for managing connection lifecycle.\n\nDesigned for use with FastAPI or other frameworks supporting \nlifespan management.\n\nArgs:\n uri: MongoDB connection URI.\n db_name: Name of the database.\n **kwargs: Additional Motor client options.\n\nYields:\n AsyncIOMotorDatabase: The active database instance.\n\nUsage:\n @asynccontextmanager\n async def lifespan(app: FastAPI):\n async with MongoConnectionManager.lifespan(uri, db_name):\n yield" + "signature": "lifespan(uri: str, db_name: str, **kwargs: Any)", + "docstring": "Async context manager for managing connection lifecycle.\n\nDesigned for use with FastAPI or other frameworks supporting\nlifespan management.\n\nNotes:\n Connects to MongoDB on entry, yields the active database instance\n to the context body, and disconnects on exit.\n\nUsage:\n @asynccontextmanager\n async def lifespan(app: FastAPI):\n async with MongoConnectionManager.lifespan(uri, db_name):\n yield" } } }, @@ -1046,63 +857,63 @@ "name": "BaseDocument", "kind": "class", "path": "mongo_ops.repository.BaseDocument", - "signature": "", - "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent \nMongoDB documents. It includes automatic handling of the `_id` field \nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", + "signature": null, + "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent\nMongoDB documents. It includes automatic handling of the `_id` field\nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", "members": { "id": { "name": "id", "kind": "attribute", "path": "mongo_ops.repository.BaseDocument.id", - "signature": "", + "signature": null, "docstring": null }, "created_at": { "name": "created_at", "kind": "attribute", "path": "mongo_ops.repository.BaseDocument.created_at", - "signature": "", + "signature": null, "docstring": null }, "updated_at": { "name": "updated_at", "kind": "attribute", "path": "mongo_ops.repository.BaseDocument.updated_at", - "signature": "", + "signature": null, "docstring": null }, "Config": { "name": "Config", "kind": "class", "path": "mongo_ops.repository.BaseDocument.Config", - "signature": "", + "signature": null, "docstring": null, "members": { "populate_by_name": { "name": "populate_by_name", "kind": "attribute", "path": "mongo_ops.repository.BaseDocument.Config.populate_by_name", - "signature": "", + "signature": null, "docstring": null }, "arbitrary_types_allowed": { "name": "arbitrary_types_allowed", "kind": "attribute", "path": "mongo_ops.repository.BaseDocument.Config.arbitrary_types_allowed", - "signature": "", + "signature": null, "docstring": null }, "json_encoders": { "name": "json_encoders", "kind": "attribute", "path": "mongo_ops.repository.BaseDocument.Config.json_encoders", - "signature": "", + "signature": null, "docstring": null }, "json_schema_extra": { "name": "json_schema_extra", "kind": "attribute", "path": "mongo_ops.repository.BaseDocument.Config.json_schema_extra", - "signature": "", + "signature": null, "docstring": null } } @@ -1113,29 +924,29 @@ "name": "PopulationEngine", "kind": "class", "path": "mongo_ops.repository.PopulationEngine", - "signature": "", - "docstring": null, + "signature": "PopulationEngine(repos: dict[str, Any], global_max_depth: int = 10)", + "docstring": "Resolves FK references across collections with cycle detection.\n\nThe engine holds a registry of repositories keyed by collection name and\nwalks documents according to PopulateRules, resolving ObjectId references\ninto model instances. A visited-path set guards against circular graphs.\n\nNotes:\n **Guarantees:**\n\n - A reference that cannot be resolved becomes None (scalar) or a\n None entry (list) rather than raising.\n - Depth is capped by both per-rule max_depth and a global\n global_max_depth.", "members": { "register_repo": { "name": "register_repo", "kind": "function", "path": "mongo_ops.repository.PopulationEngine.register_repo", - "signature": "", - "docstring": null + "signature": "register_repo(collection_name: str, repo: Any)", + "docstring": "Register or replace the repository for a collection.\n\nArgs:\n collection_name (str):\n Collection the repository manages.\n repo (Any):\n Repository exposing get_by_id() used to resolve references." }, "populate": { "name": "populate", "kind": "function", "path": "mongo_ops.repository.PopulationEngine.populate", - "signature": "", - "docstring": null + "signature": "populate(document: T, rules: list[PopulateRule], depth: int = 0, _visited: set[tuple[str, str]] | None = None, _path: list[str] | None = None)", + "docstring": "Resolve FK fields on a document according to the given rules.\n\nArgs:\n document (T):\n The document to populate in place.\n rules (list[PopulateRule]):\n Rules describing which fields to resolve and how deep.\n depth (int):\n Current recursion depth. Defaults to 0.\n _visited (Optional[set[tuple[str, str]]]):\n Internal set of (class, id) pairs on the active path.\n _path (Optional[list[str]]):\n Internal path labels used for cycle reporting.\n\nReturns:\n T: The populated document.\n\nRaises:\n CircularReferenceError: If a cycle is detected on the active path." }, "depopulate": { "name": "depopulate", "kind": "function", "path": "mongo_ops.repository.PopulationEngine.depopulate", - "signature": "", - "docstring": null + "signature": "depopulate(document: T, rules: list[PopulateRule])", + "docstring": "Collapse populated model references back to their ObjectIds.\n\nThis is the inverse of populate(): model-valued FK fields are reduced\nto stored identifiers before the document is written to MongoDB.\n\nArgs:\n document (T):\n The document to depopulate in place.\n rules (list[PopulateRule]):\n Rules describing which fields to collapse.\n\nReturns:\n T: The depopulated document.\n\nRaises:\n AttributeError: If a list entry is not a BaseDocument where\n expected." } } }, @@ -1143,49 +954,49 @@ "name": "PopulateRule", "kind": "class", "path": "mongo_ops.repository.PopulateRule", - "signature": "", - "docstring": null, + "signature": "PopulateRule(field_name: str, collection_name: str, nested_rules: list[PopulateRule] | None = None, max_depth: int = 1, filter: dict[str, Any] | None = None, projection: dict[str, Any] | None = None)", + "docstring": "Describes how a foreign-key field resolves to another collection.\n\nA rule declares the field to resolve, the collection its references\npoint at, and optional nested rules applied to the referenced document\nitself. It also bounds how deep the resolution may recurse.\n\nAttributes:\n field_name (str):\n Name of the FK field on the source document.\n collection_name (str):\n Collection the reference points into.\n nested_rules (Optional[list[PopulateRule]]):\n Sub-rules applied to the referenced document. Defaults to None.\n max_depth (int):\n Maximum recursion depth for this rule. Defaults to 1.\n filter (Optional[dict[str, Any]]):\n Optional Mongo filter applied when fetching the reference.\n Defaults to None.\n projection (Optional[dict[str, Any]]):\n Optional Mongo projection applied when fetching the reference.\n Defaults to None.", "members": { "field_name": { "name": "field_name", "kind": "attribute", "path": "mongo_ops.repository.PopulateRule.field_name", - "signature": "", + "signature": null, "docstring": null }, "collection_name": { "name": "collection_name", "kind": "attribute", "path": "mongo_ops.repository.PopulateRule.collection_name", - "signature": "", + "signature": null, "docstring": null }, "nested_rules": { "name": "nested_rules", "kind": "attribute", "path": "mongo_ops.repository.PopulateRule.nested_rules", - "signature": "", + "signature": null, "docstring": null }, "max_depth": { "name": "max_depth", "kind": "attribute", "path": "mongo_ops.repository.PopulateRule.max_depth", - "signature": "", + "signature": null, "docstring": null }, "filter": { "name": "filter", "kind": "attribute", "path": "mongo_ops.repository.PopulateRule.filter", - "signature": "", + "signature": null, "docstring": null }, "projection": { "name": "projection", "kind": "attribute", "path": "mongo_ops.repository.PopulateRule.projection", - "signature": "", + "signature": null, "docstring": null } } @@ -1201,8 +1012,8 @@ "name": "CRUDMixin", "kind": "class", "path": "mongo_ops.repository.CRUDMixin", - "signature": "", - "docstring": "Generic CRUD operations mixin for MongoDB collections.\n\nThis mixin provides standard Create, Read, Update, and Delete operations \nthat work with Pydantic models.\n\nAttributes:\n collection: The Motor collection instance.\n model: The Pydantic model class representing the document.", + "signature": "CRUDMixin(collection: AsyncIOMotorCollection, model: type[T])", + "docstring": "Generic CRUD operations mixin for MongoDB collections.\n\nThis mixin provides standard Create, Read, Update, and Delete operations\nthat work with Pydantic models.\n\nAttributes:\n collection: The Motor collection instance.\n model: The Pydantic model class representing the document.", "members": { "collection": { "name": "collection", @@ -1222,57 +1033,57 @@ "name": "data_to_model", "kind": "function", "path": "mongo_ops.repository.CRUDMixin.data_to_model", - "signature": "", + "signature": "data_to_model(data: dict) -> T", "docstring": null }, "create": { "name": "create", "kind": "function", "path": "mongo_ops.repository.CRUDMixin.create", - "signature": "", - "docstring": "Create a new document in the collection.\n\nArgs:\n data: The Pydantic model instance to insert.\n\nReturns:\n T: The created Pydantic model instance, including the assigned ID." + "signature": "create(data: T) -> T", + "docstring": "Create a new document in the collection.\n\nArgs:\n data (T): The Pydantic model instance to insert.\n\nReturns:\n T: The created Pydantic model instance, including the assigned ID." }, "get_by_id": { "name": "get_by_id", "kind": "function", "path": "mongo_ops.repository.CRUDMixin.get_by_id", - "signature": "", - "docstring": "Retrieve a document by its ID.\n\nArgs:\n id: The document ID (string or ObjectId).\n\nReturns:\n Optional[T]: The Pydantic model instance if found, else None." + "signature": "get_by_id(id: str | ObjectId) -> T | None", + "docstring": "Retrieve a document by its ID.\n\nArgs:\n id (Union[str, ObjectId]): The document ID (string or ObjectId).\n\nReturns:\n Optional[T]: The Pydantic model instance if found, else None." }, "get_many": { "name": "get_many", "kind": "function", "path": "mongo_ops.repository.CRUDMixin.get_many", - "signature": "", - "docstring": "Retrieve multiple documents with filtering, pagination, and sorting.\n\nArgs:\n filter: MongoDB filter dictionary (e.g., {\"is_active\": True}).\n skip: Number of documents to skip for pagination.\n limit: Maximum number of documents to return (default 100).\n sort: List of sort specifications [(field, direction), ...].\n E.g., [(\"created_at\", -1)] for descending.\n\nReturns:\n List[T]: A list of Pydantic model instances.\n\nUsage:\n ```python\n users = await repo.get_many(\n filter={\"role\": \"admin\"},\n limit=10,\n sort=[(\"username\", 1)]\n )\n ```" + "signature": "get_many(filter: dict[str, Any] | None = None, skip: int = 0, limit: int = 100, sort: list[tuple] | None = None) -> list[T]", + "docstring": "Retrieve multiple documents with filtering, pagination, and sorting.\n\nArgs:\n filter (Optional[Dict[str, Any]]): MongoDB filter dictionary (e.g., {\"is_active\": True}).\n skip (int): Number of documents to skip for pagination.\n limit (int): Maximum number of documents to return (default 100).\n sort (Optional[List[tuple]]): List of sort specifications [(field, direction), ...].\n E.g., [(\"created_at\", -1)] for descending.\n\nReturns:\n List[T]: A list of Pydantic model instances.\n\nExample:\n ```python\n users = await repo.get_many(\n filter={\"role\": \"admin\"},\n limit=10,\n sort=[(\"username\", 1)]\n )\n ```" }, "update": { "name": "update", "kind": "function", "path": "mongo_ops.repository.CRUDMixin.update", - "signature": "", - "docstring": "Update a document by its ID using the $set operator.\n\nArgs:\n id: The document ID (string or ObjectId).\n data: A dictionary of fields and values to update.\n\nReturns:\n Optional[T]: The updated Pydantic model instance if found, else None.\n\nUsage:\n ```python\n updated_user = await repo.update(user_id, {\"email\": \"new@example.com\"})\n ```" + "signature": "update(id: str | ObjectId, data: dict[str, Any]) -> T | None", + "docstring": "Update a document by its ID using the $set operator.\n\nArgs:\n id (Union[str, ObjectId]): The document ID (string or ObjectId).\n data (Dict[str, Any]): A dictionary of fields and values to update.\n\nReturns:\n Optional[T]: The updated Pydantic model instance if found, else None.\n\nExample:\n ```python\n updated_user = await repo.update(user_id, {\"email\": \"new@example.com\"})\n ```" }, "patch": { "name": "patch", "kind": "function", "path": "mongo_ops.repository.CRUDMixin.patch", - "signature": "", - "docstring": "Partially update a document using $set (REST PATCH semantics).\n\nUnlike update(), patch() takes a partial dict and applies only those\nfields. PopulatingRepository overrides this to prevent patching FK fields.\n\nArgs:\n id: The document ID (string or ObjectId).\n data: A partial dictionary of fields and values to update.\n\nReturns:\n Optional[T]: The updated Pydantic model instance if found, else None." + "signature": "patch(id: str | ObjectId, data: dict[str, Any]) -> T | None", + "docstring": "Partially update a document using $set (REST PATCH semantics).\n\nUnlike update(), patch() takes a partial dict and applies only those\nfields. PopulatingRepository overrides this to prevent patching FK fields.\n\nArgs:\n id (Union[str, ObjectId]): The document ID (string or ObjectId).\n data (Dict[str, Any]): A partial dictionary of fields and values to update.\n\nReturns:\n Optional[T]: The updated Pydantic model instance if found, else None." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.repository.CRUDMixin.delete", - "signature": "", - "docstring": "Delete a document by its ID.\n\nArgs:\n id: The document ID (string or ObjectId).\n\nReturns:\n bool: True if a document was deleted, False otherwise." + "signature": "delete(id: str | ObjectId) -> bool", + "docstring": "Delete a document by its ID.\n\nArgs:\n id (Union[str, ObjectId]): The document ID (string or ObjectId).\n\nReturns:\n bool: True if a document was deleted, False otherwise." }, "count": { "name": "count", "kind": "function", "path": "mongo_ops.repository.CRUDMixin.count", - "signature": "", - "docstring": "Count documents matching a filter.\n\nArgs:\n filter: MongoDB filter dictionary.\n\nReturns:\n int: The number of matching documents." + "signature": "count(filter: dict[str, Any] | None = None) -> int", + "docstring": "Count documents matching a filter.\n\nArgs:\n filter (Optional[Dict[str, Any]]): MongoDB filter dictionary.\n\nReturns:\n int: The number of matching documents." } } }, @@ -1280,8 +1091,8 @@ "name": "BaseRepository", "kind": "class", "path": "mongo_ops.repository.BaseRepository", - "signature": "", - "docstring": "Base repository class combining CRUD operations and collection management.\n\nThis class simplifies repository creation by automatically obtaining the \ndatabase connection and collection instance.\n\nAttributes:\n collection_name: The name of the collection managed by this repository.", + "signature": "BaseRepository(collection_name: str, model: type[T])", + "docstring": "Base repository class combining CRUD operations and collection management.\n\nThis class simplifies repository creation by automatically obtaining the\ndatabase connection and collection instance.\n\nAttributes:\n collection_name: The name of the collection managed by this repository.", "members": { "collection_name": { "name": "collection_name", @@ -1296,8 +1107,8 @@ "name": "PopulatingRepository", "kind": "class", "path": "mongo_ops.repository.PopulatingRepository", - "signature": "", - "docstring": null, + "signature": "PopulatingRepository(collection_name: str, model: type[T], population_engine: PopulationEngine | None = None, populate_rules: list[PopulateRule] | None = None)", + "docstring": "Repository that auto-populates and depopulates FK references.\n\nOn read, ObjectId FK fields are resolved to model instances according to\nthe configured PopulateRules. On write, populated model references are\ncollapsed back to ObjectIds before hitting MongoDB.\n\nNotes:\n **Guarantees:**\n\n - Populate rules apply on both read (data_to_model) and write\n (create/update) paths.\n - Patching FK fields via patch() is rejected.\n - Class attributes `population_engine` and `_populate_rules` must\n be set (see set_population_engine/set_populate_rules) for any\n population to occur.", "members": { "population_engine": { "name": "population_engine", @@ -1310,43 +1121,43 @@ "name": "set_population_engine", "kind": "function", "path": "mongo_ops.repository.PopulatingRepository.set_population_engine", - "signature": "", - "docstring": null + "signature": "set_population_engine(engine: PopulationEngine) -> None", + "docstring": "Attach (or replace) the population engine.\n\nArgs:\n engine (PopulationEngine):\n Engine used to resolve references." }, "set_populate_rules": { "name": "set_populate_rules", "kind": "function", "path": "mongo_ops.repository.PopulatingRepository.set_populate_rules", - "signature": "", - "docstring": null + "signature": "set_populate_rules(rules: list[PopulateRule]) -> None", + "docstring": "Set the FK resolution rules.\n\nArgs:\n rules (list[PopulateRule]):\n Rules describing which fields resolve and how deep." }, "data_to_model": { "name": "data_to_model", "kind": "function", "path": "mongo_ops.repository.PopulatingRepository.data_to_model", - "signature": "", - "docstring": null + "signature": "data_to_model(data: dict) -> T", + "docstring": "Convert a raw dict to a model, resolving FK references first.\n\nArgs:\n data (dict):\n Raw document dictionary.\n\nReturns:\n T: The populated model instance.\n\nRaises:\n ValueError: If a FK field holds an embedded dict instead of an\n ObjectId." }, "create": { "name": "create", "kind": "function", "path": "mongo_ops.repository.PopulatingRepository.create", - "signature": "", - "docstring": null + "signature": "create(data: T) -> T", + "docstring": "Depopulate, insert, and re-populate a new document.\n\nArgs:\n data (T):\n The model instance to insert (FK fields may hold models).\n\nReturns:\n T: The created model instance, including its ID." }, "update": { "name": "update", "kind": "function", "path": "mongo_ops.repository.PopulatingRepository.update", - "signature": "", - "docstring": null + "signature": "update(id: str | ObjectId, data: T) -> T | None", + "docstring": "Depopulate and update a document by ID.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID.\n data (T):\n The model instance holding the updated fields.\n\nReturns:\n Optional[T]: The updated model instance, or None when not found." }, "patch": { "name": "patch", "kind": "function", "path": "mongo_ops.repository.PopulatingRepository.patch", - "signature": "", - "docstring": null + "signature": "patch(id: str | ObjectId, data: dict[str, Any]) -> T | None", + "docstring": "Partially update a document, rejecting FK field changes.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID.\n data (dict[str, Any]):\n Partial dictionary of fields to update.\n\nReturns:\n Optional[T]: The updated model instance, or None when not found.\n\nRaises:\n ValueError: If any FK field is present in the patch payload." } } } @@ -1357,91 +1168,49 @@ "kind": "module", "path": "mongo_ops.transactions", "signature": null, - "docstring": "Transaction management helpers for MongoDB.", + "docstring": "# Summary\n\nTransaction management helpers for MongoDB.", "members": { - "Awaitable": { - "name": "Awaitable", - "kind": "alias", - "path": "mongo_ops.transactions.Awaitable", - "signature": "", - "docstring": null - }, - "asynccontextmanager": { - "name": "asynccontextmanager", - "kind": "alias", - "path": "mongo_ops.transactions.asynccontextmanager", - "signature": "", - "docstring": null - }, - "Any": { - "name": "Any", - "kind": "alias", - "path": "mongo_ops.transactions.Any", - "signature": "", - "docstring": null - }, - "Callable": { - "name": "Callable", - "kind": "alias", - "path": "mongo_ops.transactions.Callable", - "signature": "", - "docstring": null - }, - "List": { - "name": "List", - "kind": "alias", - "path": "mongo_ops.transactions.List", - "signature": "", - "docstring": null - }, - "AsyncIOMotorClientSession": { - "name": "AsyncIOMotorClientSession", - "kind": "alias", - "path": "mongo_ops.transactions.AsyncIOMotorClientSession", - "signature": "", - "docstring": null - }, "MongoConnectionManager": { "name": "MongoConnectionManager", "kind": "class", "path": "mongo_ops.transactions.MongoConnectionManager", - "signature": "", - "docstring": "Manages MongoDB connections with async lifecycle.\n\nThis class provides a singleton-like manager for the MongoDB client and \ndatabase instances, ensuring they are properly initialized and closed \nacross the application lifecycle.", + "signature": null, + "docstring": "Manages MongoDB connections with async lifecycle.\n\nThis class provides a singleton-like manager for the MongoDB client and\ndatabase instances, ensuring they are properly initialized and closed\nacross the application lifecycle.", "members": { "connect": { "name": "connect", "kind": "function", "path": "mongo_ops.transactions.MongoConnectionManager.connect", - "signature": "", - "docstring": "Connect to MongoDB and initialize the shared client.\n\nArgs:\n uri: MongoDB connection URI (e.g., \"mongodb://localhost:27017\").\n db_name: Name of the database to use.\n **kwargs: Additional Motor client options (e.g., maxPoolSize).\n\nReturns:\n AsyncIOMotorDatabase: The initialized database instance." + "signature": "connect(uri: str, db_name: str, **kwargs: Any)", + "docstring": "Connect to MongoDB and initialize the shared client.\n\nArgs:\n uri (str): MongoDB connection URI (e.g., \"mongodb://localhost:27017\").\n db_name (str): Name of the database to use.\n **kwargs: Additional Motor client options (e.g., maxPoolSize).\n\nReturns:\n AsyncIOMotorDatabase: The initialized database instance." }, "disconnect": { "name": "disconnect", "kind": "function", "path": "mongo_ops.transactions.MongoConnectionManager.disconnect", - "signature": "", + "signature": "disconnect()", "docstring": "Close the active MongoDB connection and cleanup resources." }, "get_database": { "name": "get_database", "kind": "function", "path": "mongo_ops.transactions.MongoConnectionManager.get_database", - "signature": "", + "signature": "get_database()", "docstring": "Retrieve the current database instance.\n\nReturns:\n AsyncIOMotorDatabase: The active database instance.\n\nRaises:\n RuntimeError: If connect() has not been called yet." }, "get_client": { "name": "get_client", "kind": "function", "path": "mongo_ops.transactions.MongoConnectionManager.get_client", - "signature": "", + "signature": "get_client()", "docstring": "Retrieve the current client instance.\n\nReturns:\n AsyncIOMotorClient: The active Motor client instance.\n\nRaises:\n RuntimeError: If connect() has not been called yet." }, "lifespan": { "name": "lifespan", "kind": "function", "path": "mongo_ops.transactions.MongoConnectionManager.lifespan", - "signature": "", - "docstring": "Async context manager for managing connection lifecycle.\n\nDesigned for use with FastAPI or other frameworks supporting \nlifespan management.\n\nArgs:\n uri: MongoDB connection URI.\n db_name: Name of the database.\n **kwargs: Additional Motor client options.\n\nYields:\n AsyncIOMotorDatabase: The active database instance.\n\nUsage:\n @asynccontextmanager\n async def lifespan(app: FastAPI):\n async with MongoConnectionManager.lifespan(uri, db_name):\n yield" + "signature": "lifespan(uri: str, db_name: str, **kwargs: Any)", + "docstring": "Async context manager for managing connection lifecycle.\n\nDesigned for use with FastAPI or other frameworks supporting\nlifespan management.\n\nNotes:\n Connects to MongoDB on entry, yields the active database instance\n to the context body, and disconnects on exit.\n\nUsage:\n @asynccontextmanager\n async def lifespan(app: FastAPI):\n async with MongoConnectionManager.lifespan(uri, db_name):\n yield" } } }, @@ -1449,22 +1218,22 @@ "name": "TransactionManager", "kind": "class", "path": "mongo_ops.transactions.TransactionManager", - "signature": "", - "docstring": "Simplified multi-document transaction handling.\n\nThis class provides helpers for executing operations within a MongoDB \ntransaction, ensuring ACID compliance for multi-document updates.", + "signature": null, + "docstring": "Simplified multi-document transaction handling.\n\nThis class provides helpers for executing operations within a MongoDB\ntransaction, ensuring ACID compliance for multi-document updates.", "members": { "start_session": { "name": "start_session", "kind": "function", "path": "mongo_ops.transactions.TransactionManager.start_session", - "signature": "", - "docstring": "Start a transaction session as an async context manager.\n\nArgs:\n **kwargs: Transaction options (e.g., read_concern, write_concern).\n\nYields:\n AsyncIOMotorClientSession: The active session with a started transaction.\n\nUsage:\n async with TransactionManager.start_session() as session:\n await collection.insert_one(doc, session=session)\n await other_collection.update_one(filter, update, session=session)" + "signature": "start_session(**kwargs: Any) -> AbstractAsyncContextManager[AsyncIOMotorClientSession]", + "docstring": "Start a transaction session as an async context manager.\n\nNotes:\n Yields the active session with a started transaction; callers\n run their operations against the session inside the with-block.\n\nUsage:\n async with TransactionManager.start_session() as session:\n await collection.insert_one(doc, session=session)\n await other_collection.update_one(filter, update, session=session)" }, "execute_transaction": { "name": "execute_transaction", "kind": "function", "path": "mongo_ops.transactions.TransactionManager.execute_transaction", - "signature": "", - "docstring": "Execute multiple operations within a single transaction.\n\nArgs:\n operations: A list of async callables that accept a session \n parameter and return a result.\n **kwargs: Transaction options.\n\nReturns:\n List[Any]: A list containing the results of each operation.\n\nUsage:\n results = await TransactionManager.execute_transaction([\n lambda s: repo1.create(data1, session=s),\n lambda s: repo2.update(id, data2, session=s),\n ])" + "signature": "execute_transaction(operations: list[Callable[[AsyncIOMotorClientSession], Awaitable[Any]]], **kwargs: Any) -> list[Any]", + "docstring": "Execute multiple operations within a single transaction.\n\nArgs:\n operations (List[Callable[[AsyncIOMotorClientSession], Awaitable[Any]]]): A list of async\n callables that accept a session parameter and return a result.\n **kwargs: Transaction options.\n\nReturns:\n List[Any]: A list containing the results of each operation.\n\nExample:\n results = await TransactionManager.execute_transaction([\n lambda s: repo1.create(data1, session=s),\n lambda s: repo2.update(id, data2, session=s),\n ])" } } } @@ -1475,70 +1244,70 @@ "kind": "module", "path": "mongo_ops.cache", "signature": null, - "docstring": null, + "docstring": "# Summary\n\nCache backends and configuration for mongo-ops.", "members": { "CacheBackend": { "name": "CacheBackend", "kind": "class", "path": "mongo_ops.cache.CacheBackend", - "signature": "", - "docstring": null, + "signature": null, + "docstring": "Abstract interface for cache backends.\n\nImplementations store byte-encoded values keyed by string, track usage\nstatistics, and manage their own lifecycle. The in-memory and Redis\nbackends both implement this contract.", "members": { "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.CacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str)", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.CacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None)", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds. When None, the backend default\n applies." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.CacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str)", + "docstring": "Remove a key from the cache.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.CacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str)", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.CacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str)", + "docstring": "Remove all keys matching a glob pattern.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.CacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats()", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: A copy of the current stats counters." }, "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.CacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize()", + "docstring": "Start background resources owned by the backend.\n\nShould be called once during application startup, after the\nrepositories are connected." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.CacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown()", + "docstring": "Stop and release background resources.\n\nShould be called once during application shutdown." } } }, @@ -1546,49 +1315,49 @@ "name": "CacheStats", "kind": "class", "path": "mongo_ops.cache.CacheStats", - "signature": "", - "docstring": null, + "signature": "CacheStats(hits: int = 0, misses: int = 0, sets: int = 0, deletes: int = 0, current_size: int = 0, max_size: int = 0)", + "docstring": "Snapshot of cache usage and activity counters.\n\nAttributes:\n hits (int):\n Number of get() calls that found a value.\n misses (int):\n Number of get() calls that returned None.\n sets (int):\n Number of values written to the cache.\n deletes (int):\n Number of keys removed.\n current_size (int):\n Number of entries currently held.\n max_size (int):\n Maximum number of entries the cache allows (0 = unbounded).", "members": { "hits": { "name": "hits", "kind": "attribute", "path": "mongo_ops.cache.CacheStats.hits", - "signature": "", + "signature": null, "docstring": null }, "misses": { "name": "misses", "kind": "attribute", "path": "mongo_ops.cache.CacheStats.misses", - "signature": "", + "signature": null, "docstring": null }, "sets": { "name": "sets", "kind": "attribute", "path": "mongo_ops.cache.CacheStats.sets", - "signature": "", + "signature": null, "docstring": null }, "deletes": { "name": "deletes", "kind": "attribute", "path": "mongo_ops.cache.CacheStats.deletes", - "signature": "", + "signature": null, "docstring": null }, "current_size": { "name": "current_size", "kind": "attribute", "path": "mongo_ops.cache.CacheStats.current_size", - "signature": "", + "signature": null, "docstring": null }, "max_size": { "name": "max_size", "kind": "attribute", "path": "mongo_ops.cache.CacheStats.max_size", - "signature": "", + "signature": null, "docstring": null } } @@ -1597,28 +1366,28 @@ "name": "CircularReferenceError", "kind": "class", "path": "mongo_ops.cache.CircularReferenceError", - "signature": "", - "docstring": null, + "signature": "CircularReferenceError(collection: str, doc_id: ObjectId, path: list[str])", + "docstring": "Raised when population detects a cycle in the reference graph.\n\nAttributes:\n collection (str):\n Collection where the cycle was detected.\n doc_id (ObjectId):\n Document ID where the cycle was detected.\n path (list[str]):\n Ordered labels describing the visited reference path.", "members": { "collection": { "name": "collection", "kind": "attribute", "path": "mongo_ops.cache.CircularReferenceError.collection", - "signature": "", + "signature": null, "docstring": null }, "doc_id": { "name": "doc_id", "kind": "attribute", "path": "mongo_ops.cache.CircularReferenceError.doc_id", - "signature": "", + "signature": null, "docstring": null }, "path": { "name": "path", "kind": "attribute", "path": "mongo_ops.cache.CircularReferenceError.path", - "signature": "", + "signature": null, "docstring": null } } @@ -1627,56 +1396,56 @@ "name": "CacheConfig", "kind": "class", "path": "mongo_ops.cache.CacheConfig", - "signature": "", - "docstring": null, + "signature": "CacheConfig(enabled: bool = True, backend: Literal['memory', 'redis'] = 'memory', redis_client: Redis | None = None, default_ttl: int = 300, max_entries: int = 10000, key_prefix: str = '', cleanup_interval: int = 60)", + "docstring": "Configuration for the cached repository layer.\n\nAttributes:\n enabled (bool):\n Whether caching is active for the repository.\n backend (Literal[\"memory\", \"redis\"]):\n Which backend to use. Defaults to \"memory\".\n redis_client (Optional[Redis]):\n Redis client required when backend is \"redis\".\n default_ttl (int):\n Default time-to-live for cached entries, in seconds.\n max_entries (int):\n Maximum entries for the in-memory backend.\n key_prefix (str):\n Prefix applied to cache keys; defaults to the collection name\n when empty.\n cleanup_interval (int):\n Interval (seconds) for the in-memory expiry sweep.", "members": { "enabled": { "name": "enabled", "kind": "attribute", "path": "mongo_ops.cache.CacheConfig.enabled", - "signature": "", + "signature": null, "docstring": null }, "backend": { "name": "backend", "kind": "attribute", "path": "mongo_ops.cache.CacheConfig.backend", - "signature": "", + "signature": null, "docstring": null }, "redis_client": { "name": "redis_client", "kind": "attribute", "path": "mongo_ops.cache.CacheConfig.redis_client", - "signature": "", + "signature": null, "docstring": null }, "default_ttl": { "name": "default_ttl", "kind": "attribute", "path": "mongo_ops.cache.CacheConfig.default_ttl", - "signature": "", + "signature": null, "docstring": null }, "max_entries": { "name": "max_entries", "kind": "attribute", "path": "mongo_ops.cache.CacheConfig.max_entries", - "signature": "", + "signature": null, "docstring": null }, "key_prefix": { "name": "key_prefix", "kind": "attribute", "path": "mongo_ops.cache.CacheConfig.key_prefix", - "signature": "", + "signature": null, "docstring": null }, "cleanup_interval": { "name": "cleanup_interval", "kind": "attribute", "path": "mongo_ops.cache.CacheConfig.cleanup_interval", - "signature": "", + "signature": null, "docstring": null } } @@ -1685,64 +1454,64 @@ "name": "InMemoryCacheBackend", "kind": "class", "path": "mongo_ops.cache.InMemoryCacheBackend", - "signature": "", - "docstring": null, + "signature": "InMemoryCacheBackend(max_entries: int = 10000, default_ttl: int = 300, cleanup_interval: int = 60)", + "docstring": "Cache backend backed by an in-memory dict with TTL expiry.\n\nEntries are stored in an OrderedDict for LRU-compatible eviction and a\nmin-heap of expiry timestamps drives periodic removal of stale entries.\n\nNotes:\n **Thread safety:**\n\n All operations take an asyncio lock; the backend is safe for\n concurrent use within a single event loop.", "members": { "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.InMemoryCacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize()", + "docstring": "Start the periodic expired-entry cleanup task." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.InMemoryCacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown()", + "docstring": "Cancel and await the cleanup task." }, "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.InMemoryCacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str)", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.InMemoryCacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None)", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds; defaults to the backend default." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.InMemoryCacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str)", + "docstring": "Remove a key from the cache.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.InMemoryCacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str)", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.InMemoryCacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str)", + "docstring": "Remove all keys matching a glob pattern.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.InMemoryCacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats()", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: A copy of the current stats counters." } } }, @@ -1751,49 +1520,14 @@ "kind": "module", "path": "mongo_ops.cache.backend", "signature": null, - "docstring": "Cache backend abstraction.", + "docstring": "# Summary\n\nCache backend abstraction.", "members": { - "ABC": { - "name": "ABC", - "kind": "alias", - "path": "mongo_ops.cache.backend.ABC", - "signature": "", - "docstring": null - }, - "abstractmethod": { - "name": "abstractmethod", - "kind": "alias", - "path": "mongo_ops.cache.backend.abstractmethod", - "signature": "", - "docstring": null - }, - "dataclass": { - "name": "dataclass", - "kind": "alias", - "path": "mongo_ops.cache.backend.dataclass", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.cache.backend.Optional", - "signature": "", - "docstring": null - }, - "ObjectId": { - "name": "ObjectId", - "kind": "alias", - "path": "mongo_ops.cache.backend.ObjectId", - "signature": "", - "docstring": null - }, "CacheStats": { "name": "CacheStats", "kind": "class", "path": "mongo_ops.cache.backend.CacheStats", - "signature": "", - "docstring": null, + "signature": "CacheStats(hits: int = 0, misses: int = 0, sets: int = 0, deletes: int = 0, current_size: int = 0, max_size: int = 0)", + "docstring": "Snapshot of cache usage and activity counters.\n\nAttributes:\n hits (int):\n Number of get() calls that found a value.\n misses (int):\n Number of get() calls that returned None.\n sets (int):\n Number of values written to the cache.\n deletes (int):\n Number of keys removed.\n current_size (int):\n Number of entries currently held.\n max_size (int):\n Maximum number of entries the cache allows (0 = unbounded).", "members": { "hits": { "name": "hits", @@ -1843,8 +1577,8 @@ "name": "CircularReferenceError", "kind": "class", "path": "mongo_ops.cache.backend.CircularReferenceError", - "signature": "", - "docstring": null, + "signature": "CircularReferenceError(collection: str, doc_id: ObjectId, path: list[str])", + "docstring": "Raised when population detects a cycle in the reference graph.\n\nAttributes:\n collection (str):\n Collection where the cycle was detected.\n doc_id (ObjectId):\n Document ID where the cycle was detected.\n path (list[str]):\n Ordered labels describing the visited reference path.", "members": { "collection": { "name": "collection", @@ -1873,64 +1607,64 @@ "name": "CacheBackend", "kind": "class", "path": "mongo_ops.cache.backend.CacheBackend", - "signature": "", - "docstring": null, + "signature": null, + "docstring": "Abstract interface for cache backends.\n\nImplementations store byte-encoded values keyed by string, track usage\nstatistics, and manage their own lifecycle. The in-memory and Redis\nbackends both implement this contract.", "members": { "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str) -> bytes | None", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None) -> None", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds. When None, the backend default\n applies." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str) -> None", + "docstring": "Remove a key from the cache.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str) -> bool", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str) -> None", + "docstring": "Remove all keys matching a glob pattern.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats() -> CacheStats", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: A copy of the current stats counters." }, "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize() -> None", + "docstring": "Start background resources owned by the backend.\n\nShould be called once during application startup, after the\nrepositories are connected." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.backend.CacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown() -> None", + "docstring": "Stop and release background resources.\n\nShould be called once during application shutdown." } } } @@ -1941,42 +1675,14 @@ "kind": "module", "path": "mongo_ops.cache.config", "signature": null, - "docstring": "Cache configuration.", + "docstring": "# Summary\n\nCache configuration.", "members": { - "dataclass": { - "name": "dataclass", - "kind": "alias", - "path": "mongo_ops.cache.config.dataclass", - "signature": "", - "docstring": null - }, - "Literal": { - "name": "Literal", - "kind": "alias", - "path": "mongo_ops.cache.config.Literal", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.cache.config.Optional", - "signature": "", - "docstring": null - }, - "Redis": { - "name": "Redis", - "kind": "alias", - "path": "mongo_ops.cache.config.Redis", - "signature": "", - "docstring": null - }, "CacheConfig": { "name": "CacheConfig", "kind": "class", "path": "mongo_ops.cache.config.CacheConfig", - "signature": "", - "docstring": null, + "signature": "CacheConfig(enabled: bool = True, backend: Literal['memory', 'redis'] = 'memory', redis_client: Redis | None = None, default_ttl: int = 300, max_entries: int = 10000, key_prefix: str = '', cleanup_interval: int = 60)", + "docstring": "Configuration for the cached repository layer.\n\nAttributes:\n enabled (bool):\n Whether caching is active for the repository.\n backend (Literal[\"memory\", \"redis\"]):\n Which backend to use. Defaults to \"memory\".\n redis_client (Optional[Redis]):\n Redis client required when backend is \"redis\".\n default_ttl (int):\n Default time-to-live for cached entries, in seconds.\n max_entries (int):\n Maximum entries for the in-memory backend.\n key_prefix (str):\n Prefix applied to cache keys; defaults to the collection name\n when empty.\n cleanup_interval (int):\n Interval (seconds) for the in-memory expiry sweep.", "members": { "enabled": { "name": "enabled", @@ -2036,126 +1742,70 @@ "kind": "module", "path": "mongo_ops.cache.in_memory", "signature": null, - "docstring": null, + "docstring": "# Summary\n\nIn-memory cache backend with TTL-based eviction and encode/decode helpers.", "members": { - "asyncio": { - "name": "asyncio", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.asyncio", - "signature": "", - "docstring": null - }, - "heapq": { - "name": "heapq", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.heapq", - "signature": "", - "docstring": null - }, - "json": { - "name": "json", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.json", - "signature": "", - "docstring": null - }, - "OrderedDict": { - "name": "OrderedDict", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.OrderedDict", - "signature": "", - "docstring": null - }, - "datetime": { - "name": "datetime", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.datetime", - "signature": "", - "docstring": null - }, - "timedelta": { - "name": "timedelta", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.timedelta", - "signature": "", - "docstring": null - }, - "timezone": { - "name": "timezone", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.timezone", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.cache.in_memory.Optional", - "signature": "", - "docstring": null - }, "CacheBackend": { "name": "CacheBackend", "kind": "class", "path": "mongo_ops.cache.in_memory.CacheBackend", - "signature": "", - "docstring": null, + "signature": null, + "docstring": "Abstract interface for cache backends.\n\nImplementations store byte-encoded values keyed by string, track usage\nstatistics, and manage their own lifecycle. The in-memory and Redis\nbackends both implement this contract.", "members": { "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str)", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None)", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds. When None, the backend default\n applies." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str)", + "docstring": "Remove a key from the cache.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str)", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str)", + "docstring": "Remove all keys matching a glob pattern.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats()", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: A copy of the current stats counters." }, "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize()", + "docstring": "Start background resources owned by the backend.\n\nShould be called once during application startup, after the\nrepositories are connected." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.in_memory.CacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown()", + "docstring": "Stop and release background resources.\n\nShould be called once during application shutdown." } } }, @@ -2163,49 +1813,49 @@ "name": "CacheStats", "kind": "class", "path": "mongo_ops.cache.in_memory.CacheStats", - "signature": "", - "docstring": null, + "signature": "CacheStats(hits: int = 0, misses: int = 0, sets: int = 0, deletes: int = 0, current_size: int = 0, max_size: int = 0)", + "docstring": "Snapshot of cache usage and activity counters.\n\nAttributes:\n hits (int):\n Number of get() calls that found a value.\n misses (int):\n Number of get() calls that returned None.\n sets (int):\n Number of values written to the cache.\n deletes (int):\n Number of keys removed.\n current_size (int):\n Number of entries currently held.\n max_size (int):\n Maximum number of entries the cache allows (0 = unbounded).", "members": { "hits": { "name": "hits", "kind": "attribute", "path": "mongo_ops.cache.in_memory.CacheStats.hits", - "signature": "", + "signature": null, "docstring": null }, "misses": { "name": "misses", "kind": "attribute", "path": "mongo_ops.cache.in_memory.CacheStats.misses", - "signature": "", + "signature": null, "docstring": null }, "sets": { "name": "sets", "kind": "attribute", "path": "mongo_ops.cache.in_memory.CacheStats.sets", - "signature": "", + "signature": null, "docstring": null }, "deletes": { "name": "deletes", "kind": "attribute", "path": "mongo_ops.cache.in_memory.CacheStats.deletes", - "signature": "", + "signature": null, "docstring": null }, "current_size": { "name": "current_size", "kind": "attribute", "path": "mongo_ops.cache.in_memory.CacheStats.current_size", - "signature": "", + "signature": null, "docstring": null }, "max_size": { "name": "max_size", "kind": "attribute", "path": "mongo_ops.cache.in_memory.CacheStats.max_size", - "signature": "", + "signature": null, "docstring": null } } @@ -2214,64 +1864,64 @@ "name": "InMemoryCacheBackend", "kind": "class", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend", - "signature": "", - "docstring": null, + "signature": "InMemoryCacheBackend(max_entries: int = 10000, default_ttl: int = 300, cleanup_interval: int = 60)", + "docstring": "Cache backend backed by an in-memory dict with TTL expiry.\n\nEntries are stored in an OrderedDict for LRU-compatible eviction and a\nmin-heap of expiry timestamps drives periodic removal of stale entries.\n\nNotes:\n **Thread safety:**\n\n All operations take an asyncio lock; the backend is safe for\n concurrent use within a single event loop.", "members": { "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize() -> None", + "docstring": "Start the periodic expired-entry cleanup task." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown() -> None", + "docstring": "Cancel and await the cleanup task." }, "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str) -> bytes | None", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None) -> None", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds; defaults to the backend default." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str) -> None", + "docstring": "Remove a key from the cache.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str) -> bool", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str) -> None", + "docstring": "Remove all keys matching a glob pattern.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.in_memory.InMemoryCacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats() -> CacheStats", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: A copy of the current stats counters." } } }, @@ -2279,15 +1929,15 @@ "name": "encode_value", "kind": "function", "path": "mongo_ops.cache.in_memory.encode_value", - "signature": "", - "docstring": null + "signature": "encode_value(value: dict) -> bytes", + "docstring": "Encode a dict into cache-ready bytes.\n\nNon-serializable values (e.g., ObjectId) are coerced with str().\n\nArgs:\n value (dict):\n The dictionary to encode.\n\nReturns:\n bytes: UTF-8 JSON bytes." }, "decode_value": { "name": "decode_value", "kind": "function", "path": "mongo_ops.cache.in_memory.decode_value", - "signature": "", - "docstring": null + "signature": "decode_value(data: bytes) -> dict", + "docstring": "Decode cache bytes back into a dict.\n\nArgs:\n data (bytes):\n UTF-8 JSON bytes produced by encode_value().\n\nReturns:\n dict: The decoded dictionary." } } }, @@ -2296,84 +1946,70 @@ "kind": "module", "path": "mongo_ops.cache.redis_backend", "signature": null, - "docstring": null, + "docstring": "# Summary\n\nRedis cache backend with key prefixing and pub/sub invalidation.", "members": { - "json": { - "name": "json", - "kind": "alias", - "path": "mongo_ops.cache.redis_backend.json", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.cache.redis_backend.Optional", - "signature": "", - "docstring": null - }, "CacheBackend": { "name": "CacheBackend", "kind": "class", "path": "mongo_ops.cache.redis_backend.CacheBackend", - "signature": "", - "docstring": null, + "signature": null, + "docstring": "Abstract interface for cache backends.\n\nImplementations store byte-encoded values keyed by string, track usage\nstatistics, and manage their own lifecycle. The in-memory and Redis\nbackends both implement this contract.", "members": { "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str)", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None)", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds. When None, the backend default\n applies." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str)", + "docstring": "Remove a key from the cache.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str)", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str)", + "docstring": "Remove all keys matching a glob pattern.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats()", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: A copy of the current stats counters." }, "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize()", + "docstring": "Start background resources owned by the backend.\n\nShould be called once during application startup, after the\nrepositories are connected." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.redis_backend.CacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown()", + "docstring": "Stop and release background resources.\n\nShould be called once during application shutdown." } } }, @@ -2381,129 +2017,122 @@ "name": "CacheStats", "kind": "class", "path": "mongo_ops.cache.redis_backend.CacheStats", - "signature": "", - "docstring": null, + "signature": "CacheStats(hits: int = 0, misses: int = 0, sets: int = 0, deletes: int = 0, current_size: int = 0, max_size: int = 0)", + "docstring": "Snapshot of cache usage and activity counters.\n\nAttributes:\n hits (int):\n Number of get() calls that found a value.\n misses (int):\n Number of get() calls that returned None.\n sets (int):\n Number of values written to the cache.\n deletes (int):\n Number of keys removed.\n current_size (int):\n Number of entries currently held.\n max_size (int):\n Maximum number of entries the cache allows (0 = unbounded).", "members": { "hits": { "name": "hits", "kind": "attribute", "path": "mongo_ops.cache.redis_backend.CacheStats.hits", - "signature": "", + "signature": null, "docstring": null }, "misses": { "name": "misses", "kind": "attribute", "path": "mongo_ops.cache.redis_backend.CacheStats.misses", - "signature": "", + "signature": null, "docstring": null }, "sets": { "name": "sets", "kind": "attribute", "path": "mongo_ops.cache.redis_backend.CacheStats.sets", - "signature": "", + "signature": null, "docstring": null }, "deletes": { "name": "deletes", "kind": "attribute", "path": "mongo_ops.cache.redis_backend.CacheStats.deletes", - "signature": "", + "signature": null, "docstring": null }, "current_size": { "name": "current_size", "kind": "attribute", "path": "mongo_ops.cache.redis_backend.CacheStats.current_size", - "signature": "", + "signature": null, "docstring": null }, "max_size": { "name": "max_size", "kind": "attribute", "path": "mongo_ops.cache.redis_backend.CacheStats.max_size", - "signature": "", + "signature": null, "docstring": null } } }, - "Redis": { - "name": "Redis", - "kind": "alias", - "path": "mongo_ops.cache.redis_backend.Redis", - "signature": "", - "docstring": null - }, "RedisCacheBackend": { "name": "RedisCacheBackend", "kind": "class", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend", - "signature": "", - "docstring": null, + "signature": "RedisCacheBackend(redis_client: Redis, key_prefix: str = '', default_ttl: int = 300)", + "docstring": "Cache backend backed by Redis with key prefixing.\n\nValues are stored with a configurable key prefix, and deletions publish\non a shared invalidation channel so other processes can react.\n\nNotes:\n **Lifecycle:**\n\n Requires ``pip install mongo-ops[redis]`` and a live Redis\n connection supplied by the caller.", "members": { "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize() -> None", + "docstring": "Open the pub/sub subscription used for invalidation." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown() -> None", + "docstring": "Close the pub/sub subscription." }, "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str) -> bytes | None", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None) -> None", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds; defaults to the backend default." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str) -> None", + "docstring": "Remove a key and publish an invalidation notice.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str) -> bool", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str) -> None", + "docstring": "Remove all keys matching a glob pattern via SCAN/DEL.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats() -> CacheStats", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: Stats with current_size taken from Redis dbsize." }, "publish_invalidate": { "name": "publish_invalidate", "kind": "function", "path": "mongo_ops.cache.redis_backend.RedisCacheBackend.publish_invalidate", - "signature": "", - "docstring": null + "signature": "publish_invalidate(key: str) -> None", + "docstring": "Publish an invalidation notice for a key.\n\nArgs:\n key (str):\n The cache key to broadcast." } } }, @@ -2511,15 +2140,15 @@ "name": "encode_value", "kind": "function", "path": "mongo_ops.cache.redis_backend.encode_value", - "signature": "", - "docstring": null + "signature": "encode_value(value: dict) -> bytes", + "docstring": "Encode a dict into cache-ready bytes.\n\nNon-serializable values (e.g., ObjectId) are coerced with str().\n\nArgs:\n value (dict):\n The dictionary to encode.\n\nReturns:\n bytes: UTF-8 JSON bytes." }, "decode_value": { "name": "decode_value", "kind": "function", "path": "mongo_ops.cache.redis_backend.decode_value", - "signature": "", - "docstring": null + "signature": "decode_value(data: bytes) -> dict", + "docstring": "Decode cache bytes back into a dict.\n\nArgs:\n data (bytes):\n UTF-8 JSON bytes produced by encode_value().\n\nReturns:\n dict: The decoded dictionary." } } }, @@ -2528,111 +2157,69 @@ "kind": "module", "path": "mongo_ops.cache.repository", "signature": null, - "docstring": null, + "docstring": "# Summary\n\nCached repository layer that combines a repository with a cache backend.", "members": { - "Generic": { - "name": "Generic", - "kind": "alias", - "path": "mongo_ops.cache.repository.Generic", - "signature": "", - "docstring": null - }, - "List": { - "name": "List", - "kind": "alias", - "path": "mongo_ops.cache.repository.List", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.cache.repository.Optional", - "signature": "", - "docstring": null - }, - "TypeVar": { - "name": "TypeVar", - "kind": "alias", - "path": "mongo_ops.cache.repository.TypeVar", - "signature": "", - "docstring": null - }, - "Union": { - "name": "Union", - "kind": "alias", - "path": "mongo_ops.cache.repository.Union", - "signature": "", - "docstring": null - }, - "ObjectId": { - "name": "ObjectId", - "kind": "alias", - "path": "mongo_ops.cache.repository.ObjectId", - "signature": "", - "docstring": null - }, "BaseDocument": { "name": "BaseDocument", "kind": "class", "path": "mongo_ops.cache.repository.BaseDocument", - "signature": "", - "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent \nMongoDB documents. It includes automatic handling of the `_id` field \nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", + "signature": null, + "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent\nMongoDB documents. It includes automatic handling of the `_id` field\nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", "members": { "id": { "name": "id", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseDocument.id", - "signature": "", + "signature": null, "docstring": null }, "created_at": { "name": "created_at", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseDocument.created_at", - "signature": "", + "signature": null, "docstring": null }, "updated_at": { "name": "updated_at", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseDocument.updated_at", - "signature": "", + "signature": null, "docstring": null }, "Config": { "name": "Config", "kind": "class", "path": "mongo_ops.cache.repository.BaseDocument.Config", - "signature": "", + "signature": null, "docstring": null, "members": { "populate_by_name": { "name": "populate_by_name", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseDocument.Config.populate_by_name", - "signature": "", + "signature": null, "docstring": null }, "arbitrary_types_allowed": { "name": "arbitrary_types_allowed", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseDocument.Config.arbitrary_types_allowed", - "signature": "", + "signature": null, "docstring": null }, "json_encoders": { "name": "json_encoders", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseDocument.Config.json_encoders", - "signature": "", + "signature": null, "docstring": null }, "json_schema_extra": { "name": "json_schema_extra", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseDocument.Config.json_schema_extra", - "signature": "", + "signature": null, "docstring": null } } @@ -2643,14 +2230,14 @@ "name": "BaseRepository", "kind": "class", "path": "mongo_ops.cache.repository.BaseRepository", - "signature": "", - "docstring": "Base repository class combining CRUD operations and collection management.\n\nThis class simplifies repository creation by automatically obtaining the \ndatabase connection and collection instance.\n\nAttributes:\n collection_name: The name of the collection managed by this repository.", + "signature": "BaseRepository(collection_name: str, model: type[T])", + "docstring": "Base repository class combining CRUD operations and collection management.\n\nThis class simplifies repository creation by automatically obtaining the\ndatabase connection and collection instance.\n\nAttributes:\n collection_name: The name of the collection managed by this repository.", "members": { "collection_name": { "name": "collection_name", "kind": "attribute", "path": "mongo_ops.cache.repository.BaseRepository.collection_name", - "signature": "", + "signature": null, "docstring": null } } @@ -2659,64 +2246,64 @@ "name": "CacheBackend", "kind": "class", "path": "mongo_ops.cache.repository.CacheBackend", - "signature": "", - "docstring": null, + "signature": null, + "docstring": "Abstract interface for cache backends.\n\nImplementations store byte-encoded values keyed by string, track usage\nstatistics, and manage their own lifecycle. The in-memory and Redis\nbackends both implement this contract.", "members": { "get": { "name": "get", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str)", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None)", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds. When None, the backend default\n applies." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str)", + "docstring": "Remove a key from the cache.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str)", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str)", + "docstring": "Remove all keys matching a glob pattern.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats()", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: A copy of the current stats counters." }, "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize()", + "docstring": "Start background resources owned by the backend.\n\nShould be called once during application startup, after the\nrepositories are connected." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.cache.repository.CacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown()", + "docstring": "Stop and release background resources.\n\nShould be called once during application shutdown." } } }, @@ -2724,56 +2311,56 @@ "name": "CacheConfig", "kind": "class", "path": "mongo_ops.cache.repository.CacheConfig", - "signature": "", - "docstring": null, + "signature": "CacheConfig(enabled: bool = True, backend: Literal['memory', 'redis'] = 'memory', redis_client: Redis | None = None, default_ttl: int = 300, max_entries: int = 10000, key_prefix: str = '', cleanup_interval: int = 60)", + "docstring": "Configuration for the cached repository layer.\n\nAttributes:\n enabled (bool):\n Whether caching is active for the repository.\n backend (Literal[\"memory\", \"redis\"]):\n Which backend to use. Defaults to \"memory\".\n redis_client (Optional[Redis]):\n Redis client required when backend is \"redis\".\n default_ttl (int):\n Default time-to-live for cached entries, in seconds.\n max_entries (int):\n Maximum entries for the in-memory backend.\n key_prefix (str):\n Prefix applied to cache keys; defaults to the collection name\n when empty.\n cleanup_interval (int):\n Interval (seconds) for the in-memory expiry sweep.", "members": { "enabled": { "name": "enabled", "kind": "attribute", "path": "mongo_ops.cache.repository.CacheConfig.enabled", - "signature": "", + "signature": null, "docstring": null }, "backend": { "name": "backend", "kind": "attribute", "path": "mongo_ops.cache.repository.CacheConfig.backend", - "signature": "", + "signature": null, "docstring": null }, "redis_client": { "name": "redis_client", "kind": "attribute", "path": "mongo_ops.cache.repository.CacheConfig.redis_client", - "signature": "", + "signature": null, "docstring": null }, "default_ttl": { "name": "default_ttl", "kind": "attribute", "path": "mongo_ops.cache.repository.CacheConfig.default_ttl", - "signature": "", + "signature": null, "docstring": null }, "max_entries": { "name": "max_entries", "kind": "attribute", "path": "mongo_ops.cache.repository.CacheConfig.max_entries", - "signature": "", + "signature": null, "docstring": null }, "key_prefix": { "name": "key_prefix", "kind": "attribute", "path": "mongo_ops.cache.repository.CacheConfig.key_prefix", - "signature": "", + "signature": null, "docstring": null }, "cleanup_interval": { "name": "cleanup_interval", "kind": "attribute", "path": "mongo_ops.cache.repository.CacheConfig.cleanup_interval", - "signature": "", + "signature": null, "docstring": null } } @@ -2782,15 +2369,15 @@ "name": "decode_value", "kind": "function", "path": "mongo_ops.cache.repository.decode_value", - "signature": "", - "docstring": null + "signature": "decode_value(data: bytes)", + "docstring": "Decode cache bytes back into a dict.\n\nArgs:\n data (bytes):\n UTF-8 JSON bytes produced by encode_value().\n\nReturns:\n dict: The decoded dictionary." }, "encode_value": { "name": "encode_value", "kind": "function", "path": "mongo_ops.cache.repository.encode_value", - "signature": "", - "docstring": null + "signature": "encode_value(value: dict)", + "docstring": "Encode a dict into cache-ready bytes.\n\nNon-serializable values (e.g., ObjectId) are coerced with str().\n\nArgs:\n value (dict):\n The dictionary to encode.\n\nReturns:\n bytes: UTF-8 JSON bytes." }, "T": { "name": "T", @@ -2803,50 +2390,50 @@ "name": "CachedBaseRepository", "kind": "class", "path": "mongo_ops.cache.repository.CachedBaseRepository", - "signature": "", - "docstring": null, + "signature": "CachedBaseRepository(collection_name: str, model: type[T], cache_backend: CacheBackend, config: CacheConfig | None = None)", + "docstring": "Repository that reads and writes through a cache backend.\n\nWraps an existing BaseRepository with an ID-keyed cache. Reads consult\nthe backend first and fall through to MongoDB on a miss, populating the\ncache on success. Writes invalidate or refresh the affected key.\n\nNotes:\n **Guarantees:**\n\n - The cache holds the raw document shape (``model_dump``), so FK\n references round-trip as hex strings, not populated models.\n - When ``config.enabled`` is False the repository behaves exactly\n like its parent with no cache access.", "members": { "get_by_id": { "name": "get_by_id", "kind": "function", "path": "mongo_ops.cache.repository.CachedBaseRepository.get_by_id", - "signature": "", - "docstring": null + "signature": "get_by_id(id: str | ObjectId) -> T | None", + "docstring": "Fetch a document, reading through the cache when enabled.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID.\n\nReturns:\n Optional[T]: The model instance, or None when not found." }, "create": { "name": "create", "kind": "function", "path": "mongo_ops.cache.repository.CachedBaseRepository.create", - "signature": "", - "docstring": null + "signature": "create(data: T) -> T", + "docstring": "Insert a document and cache the raw snapshot.\n\nArgs:\n data (T):\n The model instance to insert.\n\nReturns:\n T: The created model instance, including its ID." }, "update": { "name": "update", "kind": "function", "path": "mongo_ops.cache.repository.CachedBaseRepository.update", - "signature": "", - "docstring": null + "signature": "update(id: str | ObjectId, data: dict) -> T | None", + "docstring": "Update a document and refresh its cache entry.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID.\n data (dict):\n Fields to set via $set.\n\nReturns:\n Optional[T]: The updated model instance, or None when not found." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.cache.repository.CachedBaseRepository.delete", - "signature": "", - "docstring": null + "signature": "delete(id: str | ObjectId) -> bool", + "docstring": "Delete a document and remove its cache entry.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID.\n\nReturns:\n bool: True if a document was deleted, False otherwise." }, "warm_cache": { "name": "warm_cache", "kind": "function", "path": "mongo_ops.cache.repository.CachedBaseRepository.warm_cache", - "signature": "", - "docstring": null + "signature": "warm_cache(ids: list[str | ObjectId]) -> int", + "docstring": "Pre-populate the cache for a set of document IDs.\n\nDocs already present in the cache are skipped.\n\nArgs:\n ids (list[Union[str, ObjectId]]):\n Document IDs to warm.\n\nReturns:\n int: Number of entries added to the cache." }, "invalidate_cache": { "name": "invalidate_cache", "kind": "function", "path": "mongo_ops.cache.repository.CachedBaseRepository.invalidate_cache", - "signature": "", - "docstring": null + "signature": "invalidate_cache(id: str | ObjectId) -> None", + "docstring": "Remove a single document's cache entry.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID to invalidate." } } } @@ -2859,35 +2446,35 @@ "kind": "module", "path": "mongo_ops.populate", "signature": null, - "docstring": null, + "docstring": "# Summary\n\nDocument populating rules and population engine.", "members": { "PopulationEngine": { "name": "PopulationEngine", "kind": "class", "path": "mongo_ops.populate.PopulationEngine", - "signature": "", - "docstring": null, + "signature": "PopulationEngine(repos: dict[str, Any], global_max_depth: int = 10)", + "docstring": "Resolves FK references across collections with cycle detection.\n\nThe engine holds a registry of repositories keyed by collection name and\nwalks documents according to PopulateRules, resolving ObjectId references\ninto model instances. A visited-path set guards against circular graphs.\n\nNotes:\n **Guarantees:**\n\n - A reference that cannot be resolved becomes None (scalar) or a\n None entry (list) rather than raising.\n - Depth is capped by both per-rule max_depth and a global\n global_max_depth.", "members": { "register_repo": { "name": "register_repo", "kind": "function", "path": "mongo_ops.populate.PopulationEngine.register_repo", - "signature": "", - "docstring": null + "signature": "register_repo(collection_name: str, repo: Any)", + "docstring": "Register or replace the repository for a collection.\n\nArgs:\n collection_name (str):\n Collection the repository manages.\n repo (Any):\n Repository exposing get_by_id() used to resolve references." }, "populate": { "name": "populate", "kind": "function", "path": "mongo_ops.populate.PopulationEngine.populate", - "signature": "", - "docstring": null + "signature": "populate(document: T, rules: list[PopulateRule], depth: int = 0, _visited: set[tuple[str, str]] | None = None, _path: list[str] | None = None)", + "docstring": "Resolve FK fields on a document according to the given rules.\n\nArgs:\n document (T):\n The document to populate in place.\n rules (list[PopulateRule]):\n Rules describing which fields to resolve and how deep.\n depth (int):\n Current recursion depth. Defaults to 0.\n _visited (Optional[set[tuple[str, str]]]):\n Internal set of (class, id) pairs on the active path.\n _path (Optional[list[str]]):\n Internal path labels used for cycle reporting.\n\nReturns:\n T: The populated document.\n\nRaises:\n CircularReferenceError: If a cycle is detected on the active path." }, "depopulate": { "name": "depopulate", "kind": "function", "path": "mongo_ops.populate.PopulationEngine.depopulate", - "signature": "", - "docstring": null + "signature": "depopulate(document: T, rules: list[PopulateRule])", + "docstring": "Collapse populated model references back to their ObjectIds.\n\nThis is the inverse of populate(): model-valued FK fields are reduced\nto stored identifiers before the document is written to MongoDB.\n\nArgs:\n document (T):\n The document to depopulate in place.\n rules (list[PopulateRule]):\n Rules describing which fields to collapse.\n\nReturns:\n T: The depopulated document.\n\nRaises:\n AttributeError: If a list entry is not a BaseDocument where\n expected." } } }, @@ -2895,49 +2482,49 @@ "name": "PopulateRule", "kind": "class", "path": "mongo_ops.populate.PopulateRule", - "signature": "", - "docstring": null, + "signature": "PopulateRule(field_name: str, collection_name: str, nested_rules: list[PopulateRule] | None = None, max_depth: int = 1, filter: dict[str, Any] | None = None, projection: dict[str, Any] | None = None)", + "docstring": "Describes how a foreign-key field resolves to another collection.\n\nA rule declares the field to resolve, the collection its references\npoint at, and optional nested rules applied to the referenced document\nitself. It also bounds how deep the resolution may recurse.\n\nAttributes:\n field_name (str):\n Name of the FK field on the source document.\n collection_name (str):\n Collection the reference points into.\n nested_rules (Optional[list[PopulateRule]]):\n Sub-rules applied to the referenced document. Defaults to None.\n max_depth (int):\n Maximum recursion depth for this rule. Defaults to 1.\n filter (Optional[dict[str, Any]]):\n Optional Mongo filter applied when fetching the reference.\n Defaults to None.\n projection (Optional[dict[str, Any]]):\n Optional Mongo projection applied when fetching the reference.\n Defaults to None.", "members": { "field_name": { "name": "field_name", "kind": "attribute", "path": "mongo_ops.populate.PopulateRule.field_name", - "signature": "", + "signature": null, "docstring": null }, "collection_name": { "name": "collection_name", "kind": "attribute", "path": "mongo_ops.populate.PopulateRule.collection_name", - "signature": "", + "signature": null, "docstring": null }, "nested_rules": { "name": "nested_rules", "kind": "attribute", "path": "mongo_ops.populate.PopulateRule.nested_rules", - "signature": "", + "signature": null, "docstring": null }, "max_depth": { "name": "max_depth", "kind": "attribute", "path": "mongo_ops.populate.PopulateRule.max_depth", - "signature": "", + "signature": null, "docstring": null }, "filter": { "name": "filter", "kind": "attribute", "path": "mongo_ops.populate.PopulateRule.filter", - "signature": "", + "signature": null, "docstring": null }, "projection": { "name": "projection", "kind": "attribute", "path": "mongo_ops.populate.PopulateRule.projection", - "signature": "", + "signature": null, "docstring": null } } @@ -2947,90 +2534,34 @@ "kind": "module", "path": "mongo_ops.populate.engine", "signature": null, - "docstring": null, + "docstring": "# Summary\n\nRecursive document population engine with cycle detection.", "members": { - "Any": { - "name": "Any", - "kind": "alias", - "path": "mongo_ops.populate.engine.Any", - "signature": "", - "docstring": null - }, - "Dict": { - "name": "Dict", - "kind": "alias", - "path": "mongo_ops.populate.engine.Dict", - "signature": "", - "docstring": null - }, - "List": { - "name": "List", - "kind": "alias", - "path": "mongo_ops.populate.engine.List", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.populate.engine.Optional", - "signature": "", - "docstring": null - }, - "Set": { - "name": "Set", - "kind": "alias", - "path": "mongo_ops.populate.engine.Set", - "signature": "", - "docstring": null - }, - "Tuple": { - "name": "Tuple", - "kind": "alias", - "path": "mongo_ops.populate.engine.Tuple", - "signature": "", - "docstring": null - }, - "TypeVar": { - "name": "TypeVar", - "kind": "alias", - "path": "mongo_ops.populate.engine.TypeVar", - "signature": "", - "docstring": null - }, - "ObjectId": { - "name": "ObjectId", - "kind": "alias", - "path": "mongo_ops.populate.engine.ObjectId", - "signature": "", - "docstring": null - }, "CircularReferenceError": { "name": "CircularReferenceError", "kind": "class", "path": "mongo_ops.populate.engine.CircularReferenceError", - "signature": "", - "docstring": null, + "signature": "CircularReferenceError(collection: str, doc_id: ObjectId, path: list[str])", + "docstring": "Raised when population detects a cycle in the reference graph.\n\nAttributes:\n collection (str):\n Collection where the cycle was detected.\n doc_id (ObjectId):\n Document ID where the cycle was detected.\n path (list[str]):\n Ordered labels describing the visited reference path.", "members": { "collection": { "name": "collection", "kind": "attribute", "path": "mongo_ops.populate.engine.CircularReferenceError.collection", - "signature": "", + "signature": null, "docstring": null }, "doc_id": { "name": "doc_id", "kind": "attribute", "path": "mongo_ops.populate.engine.CircularReferenceError.doc_id", - "signature": "", + "signature": null, "docstring": null }, "path": { "name": "path", "kind": "attribute", "path": "mongo_ops.populate.engine.CircularReferenceError.path", - "signature": "", + "signature": null, "docstring": null } } @@ -3039,63 +2570,63 @@ "name": "BaseDocument", "kind": "class", "path": "mongo_ops.populate.engine.BaseDocument", - "signature": "", - "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent \nMongoDB documents. It includes automatic handling of the `_id` field \nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", + "signature": null, + "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent\nMongoDB documents. It includes automatic handling of the `_id` field\nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", "members": { "id": { "name": "id", "kind": "attribute", "path": "mongo_ops.populate.engine.BaseDocument.id", - "signature": "", + "signature": null, "docstring": null }, "created_at": { "name": "created_at", "kind": "attribute", "path": "mongo_ops.populate.engine.BaseDocument.created_at", - "signature": "", + "signature": null, "docstring": null }, "updated_at": { "name": "updated_at", "kind": "attribute", "path": "mongo_ops.populate.engine.BaseDocument.updated_at", - "signature": "", + "signature": null, "docstring": null }, "Config": { "name": "Config", "kind": "class", "path": "mongo_ops.populate.engine.BaseDocument.Config", - "signature": "", + "signature": null, "docstring": null, "members": { "populate_by_name": { "name": "populate_by_name", "kind": "attribute", "path": "mongo_ops.populate.engine.BaseDocument.Config.populate_by_name", - "signature": "", + "signature": null, "docstring": null }, "arbitrary_types_allowed": { "name": "arbitrary_types_allowed", "kind": "attribute", "path": "mongo_ops.populate.engine.BaseDocument.Config.arbitrary_types_allowed", - "signature": "", + "signature": null, "docstring": null }, "json_encoders": { "name": "json_encoders", "kind": "attribute", "path": "mongo_ops.populate.engine.BaseDocument.Config.json_encoders", - "signature": "", + "signature": null, "docstring": null }, "json_schema_extra": { "name": "json_schema_extra", "kind": "attribute", "path": "mongo_ops.populate.engine.BaseDocument.Config.json_schema_extra", - "signature": "", + "signature": null, "docstring": null } } @@ -3106,49 +2637,49 @@ "name": "PopulateRule", "kind": "class", "path": "mongo_ops.populate.engine.PopulateRule", - "signature": "", - "docstring": null, + "signature": "PopulateRule(field_name: str, collection_name: str, nested_rules: list[PopulateRule] | None = None, max_depth: int = 1, filter: dict[str, Any] | None = None, projection: dict[str, Any] | None = None)", + "docstring": "Describes how a foreign-key field resolves to another collection.\n\nA rule declares the field to resolve, the collection its references\npoint at, and optional nested rules applied to the referenced document\nitself. It also bounds how deep the resolution may recurse.\n\nAttributes:\n field_name (str):\n Name of the FK field on the source document.\n collection_name (str):\n Collection the reference points into.\n nested_rules (Optional[list[PopulateRule]]):\n Sub-rules applied to the referenced document. Defaults to None.\n max_depth (int):\n Maximum recursion depth for this rule. Defaults to 1.\n filter (Optional[dict[str, Any]]):\n Optional Mongo filter applied when fetching the reference.\n Defaults to None.\n projection (Optional[dict[str, Any]]):\n Optional Mongo projection applied when fetching the reference.\n Defaults to None.", "members": { "field_name": { "name": "field_name", "kind": "attribute", "path": "mongo_ops.populate.engine.PopulateRule.field_name", - "signature": "", + "signature": null, "docstring": null }, "collection_name": { "name": "collection_name", "kind": "attribute", "path": "mongo_ops.populate.engine.PopulateRule.collection_name", - "signature": "", + "signature": null, "docstring": null }, "nested_rules": { "name": "nested_rules", "kind": "attribute", "path": "mongo_ops.populate.engine.PopulateRule.nested_rules", - "signature": "", + "signature": null, "docstring": null }, "max_depth": { "name": "max_depth", "kind": "attribute", "path": "mongo_ops.populate.engine.PopulateRule.max_depth", - "signature": "", + "signature": null, "docstring": null }, "filter": { "name": "filter", "kind": "attribute", "path": "mongo_ops.populate.engine.PopulateRule.filter", - "signature": "", + "signature": null, "docstring": null }, "projection": { "name": "projection", "kind": "attribute", "path": "mongo_ops.populate.engine.PopulateRule.projection", - "signature": "", + "signature": null, "docstring": null } } @@ -3164,29 +2695,29 @@ "name": "PopulationEngine", "kind": "class", "path": "mongo_ops.populate.engine.PopulationEngine", - "signature": "", - "docstring": null, + "signature": "PopulationEngine(repos: dict[str, Any], global_max_depth: int = 10)", + "docstring": "Resolves FK references across collections with cycle detection.\n\nThe engine holds a registry of repositories keyed by collection name and\nwalks documents according to PopulateRules, resolving ObjectId references\ninto model instances. A visited-path set guards against circular graphs.\n\nNotes:\n **Guarantees:**\n\n - A reference that cannot be resolved becomes None (scalar) or a\n None entry (list) rather than raising.\n - Depth is capped by both per-rule max_depth and a global\n global_max_depth.", "members": { "register_repo": { "name": "register_repo", "kind": "function", "path": "mongo_ops.populate.engine.PopulationEngine.register_repo", - "signature": "", - "docstring": null + "signature": "register_repo(collection_name: str, repo: Any) -> None", + "docstring": "Register or replace the repository for a collection.\n\nArgs:\n collection_name (str):\n Collection the repository manages.\n repo (Any):\n Repository exposing get_by_id() used to resolve references." }, "populate": { "name": "populate", "kind": "function", "path": "mongo_ops.populate.engine.PopulationEngine.populate", - "signature": "", - "docstring": null + "signature": "populate(document: T, rules: list[PopulateRule], depth: int = 0, _visited: set[tuple[str, str]] | None = None, _path: list[str] | None = None) -> T", + "docstring": "Resolve FK fields on a document according to the given rules.\n\nArgs:\n document (T):\n The document to populate in place.\n rules (list[PopulateRule]):\n Rules describing which fields to resolve and how deep.\n depth (int):\n Current recursion depth. Defaults to 0.\n _visited (Optional[set[tuple[str, str]]]):\n Internal set of (class, id) pairs on the active path.\n _path (Optional[list[str]]):\n Internal path labels used for cycle reporting.\n\nReturns:\n T: The populated document.\n\nRaises:\n CircularReferenceError: If a cycle is detected on the active path." }, "depopulate": { "name": "depopulate", "kind": "function", "path": "mongo_ops.populate.engine.PopulationEngine.depopulate", - "signature": "", - "docstring": null + "signature": "depopulate(document: T, rules: list[PopulateRule]) -> T", + "docstring": "Collapse populated model references back to their ObjectIds.\n\nThis is the inverse of populate(): model-valued FK fields are reduced\nto stored identifiers before the document is written to MongoDB.\n\nArgs:\n document (T):\n The document to depopulate in place.\n rules (list[PopulateRule]):\n Rules describing which fields to collapse.\n\nReturns:\n T: The depopulated document.\n\nRaises:\n AttributeError: If a list entry is not a BaseDocument where\n expected." } } } @@ -3197,49 +2728,14 @@ "kind": "module", "path": "mongo_ops.populate.rules", "signature": null, - "docstring": null, + "docstring": "# Summary\n\nPopulate rules describing which foreign-key fields to resolve.", "members": { - "dataclass": { - "name": "dataclass", - "kind": "alias", - "path": "mongo_ops.populate.rules.dataclass", - "signature": "", - "docstring": null - }, - "Any": { - "name": "Any", - "kind": "alias", - "path": "mongo_ops.populate.rules.Any", - "signature": "", - "docstring": null - }, - "Dict": { - "name": "Dict", - "kind": "alias", - "path": "mongo_ops.populate.rules.Dict", - "signature": "", - "docstring": null - }, - "List": { - "name": "List", - "kind": "alias", - "path": "mongo_ops.populate.rules.List", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.populate.rules.Optional", - "signature": "", - "docstring": null - }, "PopulateRule": { "name": "PopulateRule", "kind": "class", "path": "mongo_ops.populate.rules.PopulateRule", - "signature": "", - "docstring": null, + "signature": "PopulateRule(field_name: str, collection_name: str, nested_rules: list[PopulateRule] | None = None, max_depth: int = 1, filter: dict[str, Any] | None = None, projection: dict[str, Any] | None = None)", + "docstring": "Describes how a foreign-key field resolves to another collection.\n\nA rule declares the field to resolve, the collection its references\npoint at, and optional nested rules applied to the referenced document\nitself. It also bounds how deep the resolution may recurse.\n\nAttributes:\n field_name (str):\n Name of the FK field on the source document.\n collection_name (str):\n Collection the reference points into.\n nested_rules (Optional[list[PopulateRule]]):\n Sub-rules applied to the referenced document. Defaults to None.\n max_depth (int):\n Maximum recursion depth for this rule. Defaults to 1.\n filter (Optional[dict[str, Any]]):\n Optional Mongo filter applied when fetching the reference.\n Defaults to None.\n projection (Optional[dict[str, Any]]):\n Optional Mongo projection applied when fetching the reference.\n Defaults to None.", "members": { "field_name": { "name": "field_name", diff --git a/mongo-ops/mcp/modules/mongo_ops.models.json b/mongo-ops/mcp/modules/mongo_ops.models.json index a7bbd64..e99495d 100644 --- a/mongo-ops/mcp/modules/mongo_ops.models.json +++ b/mongo-ops/mcp/modules/mongo_ops.models.json @@ -2,77 +2,21 @@ "module": "mongo_ops.models", "content": { "path": "mongo_ops.models", - "docstring": "Base document models for MongoDB.", + "docstring": "# Summary\n\nBase document models for MongoDB.", "objects": { - "datetime": { - "name": "datetime", - "kind": "alias", - "path": "mongo_ops.models.datetime", - "signature": "", - "docstring": null - }, - "Any": { - "name": "Any", - "kind": "alias", - "path": "mongo_ops.models.Any", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.models.Optional", - "signature": "", - "docstring": null - }, - "ObjectId": { - "name": "ObjectId", - "kind": "alias", - "path": "mongo_ops.models.ObjectId", - "signature": "", - "docstring": null - }, - "BaseModel": { - "name": "BaseModel", - "kind": "alias", - "path": "mongo_ops.models.BaseModel", - "signature": "", - "docstring": null - }, - "Field": { - "name": "Field", - "kind": "alias", - "path": "mongo_ops.models.Field", - "signature": "", - "docstring": null - }, - "GetCoreSchemaHandler": { - "name": "GetCoreSchemaHandler", - "kind": "alias", - "path": "mongo_ops.models.GetCoreSchemaHandler", - "signature": "", - "docstring": null - }, - "core_schema": { - "name": "core_schema", - "kind": "alias", - "path": "mongo_ops.models.core_schema", - "signature": "", - "docstring": null - }, "PyObjectId": { "name": "PyObjectId", "kind": "class", "path": "mongo_ops.models.PyObjectId", - "signature": "", - "docstring": "Custom ObjectId type compatible with Pydantic v2.\n\nThis class extends the standard BSON ObjectId to provide validation and \nserialization support within Pydantic models.", + "signature": null, + "docstring": "Custom ObjectId type compatible with Pydantic v2.\n\nThis class extends the standard BSON ObjectId to provide validation and\nserialization support within Pydantic models.", "members": { "validate": { "name": "validate", "kind": "function", "path": "mongo_ops.models.PyObjectId.validate", - "signature": "", - "docstring": "Validate the input value and convert it to an ObjectId if possible.\n\nArgs:\n v: The value to validate (can be str or ObjectId).\n\nReturns:\n ObjectId: The validated ObjectId instance.\n\nRaises:\n ValueError: If the value is not a valid ObjectId." + "signature": "validate(v: Any) -> ObjectId", + "docstring": "Validate the input value and convert it to an ObjectId if possible.\n\nArgs:\n v (Any): The value to validate (can be str or ObjectId).\n\nReturns:\n ObjectId: The validated ObjectId instance.\n\nRaises:\n ValueError: If the value is not a valid ObjectId." } } }, @@ -80,8 +24,8 @@ "name": "BaseDocument", "kind": "class", "path": "mongo_ops.models.BaseDocument", - "signature": "", - "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent \nMongoDB documents. It includes automatic handling of the `_id` field \nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", + "signature": null, + "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent\nMongoDB documents. It includes automatic handling of the `_id` field\nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", "members": { "id": { "name": "id", @@ -108,7 +52,7 @@ "name": "Config", "kind": "class", "path": "mongo_ops.models.BaseDocument.Config", - "signature": "", + "signature": null, "docstring": null, "members": { "populate_by_name": { diff --git a/mongo-ops/mcp/modules/mongo_ops.populate.engine.json b/mongo-ops/mcp/modules/mongo_ops.populate.engine.json index 794f440..bb35b6c 100644 --- a/mongo-ops/mcp/modules/mongo_ops.populate.engine.json +++ b/mongo-ops/mcp/modules/mongo_ops.populate.engine.json @@ -2,90 +2,34 @@ "module": "mongo_ops.populate.engine", "content": { "path": "mongo_ops.populate.engine", - "docstring": null, + "docstring": "# Summary\n\nRecursive document population engine with cycle detection.", "objects": { - "Any": { - "name": "Any", - "kind": "alias", - "path": "mongo_ops.populate.engine.Any", - "signature": "", - "docstring": null - }, - "Dict": { - "name": "Dict", - "kind": "alias", - "path": "mongo_ops.populate.engine.Dict", - "signature": "", - "docstring": null - }, - "List": { - "name": "List", - "kind": "alias", - "path": "mongo_ops.populate.engine.List", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.populate.engine.Optional", - "signature": "", - "docstring": null - }, - "Set": { - "name": "Set", - "kind": "alias", - "path": "mongo_ops.populate.engine.Set", - "signature": "", - "docstring": null - }, - "Tuple": { - "name": "Tuple", - "kind": "alias", - "path": "mongo_ops.populate.engine.Tuple", - "signature": "", - "docstring": null - }, - "TypeVar": { - "name": "TypeVar", - "kind": "alias", - "path": "mongo_ops.populate.engine.TypeVar", - "signature": "", - "docstring": null - }, - "ObjectId": { - "name": "ObjectId", - "kind": "alias", - "path": "mongo_ops.populate.engine.ObjectId", - "signature": "", - "docstring": null - }, "CircularReferenceError": { "name": "CircularReferenceError", "kind": "class", "path": "mongo_ops.populate.engine.CircularReferenceError", - "signature": "", - "docstring": null, + "signature": "CircularReferenceError(collection: str, doc_id: ObjectId, path: list[str])", + "docstring": "Raised when population detects a cycle in the reference graph.\n\nAttributes:\n collection (str):\n Collection where the cycle was detected.\n doc_id (ObjectId):\n Document ID where the cycle was detected.\n path (list[str]):\n Ordered labels describing the visited reference path.", "members": { "collection": { "name": "collection", "kind": "attribute", "path": "mongo_ops.populate.engine.CircularReferenceError.collection", - "signature": "", + "signature": null, "docstring": null }, "doc_id": { "name": "doc_id", "kind": "attribute", "path": "mongo_ops.populate.engine.CircularReferenceError.doc_id", - "signature": "", + "signature": null, "docstring": null }, "path": { "name": "path", "kind": "attribute", "path": "mongo_ops.populate.engine.CircularReferenceError.path", - "signature": "", + "signature": null, "docstring": null } } @@ -94,63 +38,63 @@ "name": "BaseDocument", "kind": "class", "path": "mongo_ops.populate.engine.BaseDocument", - "signature": "", - "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent \nMongoDB documents. It includes automatic handling of the `_id` field \nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", + "signature": null, + "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent\nMongoDB documents. It includes automatic handling of the `_id` field\nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", "members": { "id": { "name": "id", "kind": "attribute", "path": "mongo_ops.populate.engine.BaseDocument.id", - "signature": "", + "signature": null, "docstring": null }, "created_at": { "name": "created_at", "kind": "attribute", "path": "mongo_ops.populate.engine.BaseDocument.created_at", - "signature": "", + "signature": null, "docstring": null }, "updated_at": { "name": "updated_at", "kind": "attribute", "path": "mongo_ops.populate.engine.BaseDocument.updated_at", - "signature": "", + "signature": null, "docstring": null }, "Config": { "name": "Config", "kind": "class", "path": "mongo_ops.populate.engine.BaseDocument.Config", - "signature": "", + "signature": null, "docstring": null, "members": { "populate_by_name": { "name": "populate_by_name", "kind": "attribute", "path": "mongo_ops.populate.engine.BaseDocument.Config.populate_by_name", - "signature": "", + "signature": null, "docstring": null }, "arbitrary_types_allowed": { "name": "arbitrary_types_allowed", "kind": "attribute", "path": "mongo_ops.populate.engine.BaseDocument.Config.arbitrary_types_allowed", - "signature": "", + "signature": null, "docstring": null }, "json_encoders": { "name": "json_encoders", "kind": "attribute", "path": "mongo_ops.populate.engine.BaseDocument.Config.json_encoders", - "signature": "", + "signature": null, "docstring": null }, "json_schema_extra": { "name": "json_schema_extra", "kind": "attribute", "path": "mongo_ops.populate.engine.BaseDocument.Config.json_schema_extra", - "signature": "", + "signature": null, "docstring": null } } @@ -161,49 +105,49 @@ "name": "PopulateRule", "kind": "class", "path": "mongo_ops.populate.engine.PopulateRule", - "signature": "", - "docstring": null, + "signature": "PopulateRule(field_name: str, collection_name: str, nested_rules: list[PopulateRule] | None = None, max_depth: int = 1, filter: dict[str, Any] | None = None, projection: dict[str, Any] | None = None)", + "docstring": "Describes how a foreign-key field resolves to another collection.\n\nA rule declares the field to resolve, the collection its references\npoint at, and optional nested rules applied to the referenced document\nitself. It also bounds how deep the resolution may recurse.\n\nAttributes:\n field_name (str):\n Name of the FK field on the source document.\n collection_name (str):\n Collection the reference points into.\n nested_rules (Optional[list[PopulateRule]]):\n Sub-rules applied to the referenced document. Defaults to None.\n max_depth (int):\n Maximum recursion depth for this rule. Defaults to 1.\n filter (Optional[dict[str, Any]]):\n Optional Mongo filter applied when fetching the reference.\n Defaults to None.\n projection (Optional[dict[str, Any]]):\n Optional Mongo projection applied when fetching the reference.\n Defaults to None.", "members": { "field_name": { "name": "field_name", "kind": "attribute", "path": "mongo_ops.populate.engine.PopulateRule.field_name", - "signature": "", + "signature": null, "docstring": null }, "collection_name": { "name": "collection_name", "kind": "attribute", "path": "mongo_ops.populate.engine.PopulateRule.collection_name", - "signature": "", + "signature": null, "docstring": null }, "nested_rules": { "name": "nested_rules", "kind": "attribute", "path": "mongo_ops.populate.engine.PopulateRule.nested_rules", - "signature": "", + "signature": null, "docstring": null }, "max_depth": { "name": "max_depth", "kind": "attribute", "path": "mongo_ops.populate.engine.PopulateRule.max_depth", - "signature": "", + "signature": null, "docstring": null }, "filter": { "name": "filter", "kind": "attribute", "path": "mongo_ops.populate.engine.PopulateRule.filter", - "signature": "", + "signature": null, "docstring": null }, "projection": { "name": "projection", "kind": "attribute", "path": "mongo_ops.populate.engine.PopulateRule.projection", - "signature": "", + "signature": null, "docstring": null } } @@ -219,29 +163,29 @@ "name": "PopulationEngine", "kind": "class", "path": "mongo_ops.populate.engine.PopulationEngine", - "signature": "", - "docstring": null, + "signature": "PopulationEngine(repos: dict[str, Any], global_max_depth: int = 10)", + "docstring": "Resolves FK references across collections with cycle detection.\n\nThe engine holds a registry of repositories keyed by collection name and\nwalks documents according to PopulateRules, resolving ObjectId references\ninto model instances. A visited-path set guards against circular graphs.\n\nNotes:\n **Guarantees:**\n\n - A reference that cannot be resolved becomes None (scalar) or a\n None entry (list) rather than raising.\n - Depth is capped by both per-rule max_depth and a global\n global_max_depth.", "members": { "register_repo": { "name": "register_repo", "kind": "function", "path": "mongo_ops.populate.engine.PopulationEngine.register_repo", - "signature": "", - "docstring": null + "signature": "register_repo(collection_name: str, repo: Any) -> None", + "docstring": "Register or replace the repository for a collection.\n\nArgs:\n collection_name (str):\n Collection the repository manages.\n repo (Any):\n Repository exposing get_by_id() used to resolve references." }, "populate": { "name": "populate", "kind": "function", "path": "mongo_ops.populate.engine.PopulationEngine.populate", - "signature": "", - "docstring": null + "signature": "populate(document: T, rules: list[PopulateRule], depth: int = 0, _visited: set[tuple[str, str]] | None = None, _path: list[str] | None = None) -> T", + "docstring": "Resolve FK fields on a document according to the given rules.\n\nArgs:\n document (T):\n The document to populate in place.\n rules (list[PopulateRule]):\n Rules describing which fields to resolve and how deep.\n depth (int):\n Current recursion depth. Defaults to 0.\n _visited (Optional[set[tuple[str, str]]]):\n Internal set of (class, id) pairs on the active path.\n _path (Optional[list[str]]):\n Internal path labels used for cycle reporting.\n\nReturns:\n T: The populated document.\n\nRaises:\n CircularReferenceError: If a cycle is detected on the active path." }, "depopulate": { "name": "depopulate", "kind": "function", "path": "mongo_ops.populate.engine.PopulationEngine.depopulate", - "signature": "", - "docstring": null + "signature": "depopulate(document: T, rules: list[PopulateRule]) -> T", + "docstring": "Collapse populated model references back to their ObjectIds.\n\nThis is the inverse of populate(): model-valued FK fields are reduced\nto stored identifiers before the document is written to MongoDB.\n\nArgs:\n document (T):\n The document to depopulate in place.\n rules (list[PopulateRule]):\n Rules describing which fields to collapse.\n\nReturns:\n T: The depopulated document.\n\nRaises:\n AttributeError: If a list entry is not a BaseDocument where\n expected." } } } diff --git a/mongo-ops/mcp/modules/mongo_ops.populate.json b/mongo-ops/mcp/modules/mongo_ops.populate.json index 9a54256..f3f24d4 100644 --- a/mongo-ops/mcp/modules/mongo_ops.populate.json +++ b/mongo-ops/mcp/modules/mongo_ops.populate.json @@ -2,35 +2,35 @@ "module": "mongo_ops.populate", "content": { "path": "mongo_ops.populate", - "docstring": null, + "docstring": "# Summary\n\nDocument populating rules and population engine.", "objects": { "PopulationEngine": { "name": "PopulationEngine", "kind": "class", "path": "mongo_ops.populate.PopulationEngine", - "signature": "", - "docstring": null, + "signature": "PopulationEngine(repos: dict[str, Any], global_max_depth: int = 10)", + "docstring": "Resolves FK references across collections with cycle detection.\n\nThe engine holds a registry of repositories keyed by collection name and\nwalks documents according to PopulateRules, resolving ObjectId references\ninto model instances. A visited-path set guards against circular graphs.\n\nNotes:\n **Guarantees:**\n\n - A reference that cannot be resolved becomes None (scalar) or a\n None entry (list) rather than raising.\n - Depth is capped by both per-rule max_depth and a global\n global_max_depth.", "members": { "register_repo": { "name": "register_repo", "kind": "function", "path": "mongo_ops.populate.PopulationEngine.register_repo", - "signature": "", - "docstring": null + "signature": "register_repo(collection_name: str, repo: Any)", + "docstring": "Register or replace the repository for a collection.\n\nArgs:\n collection_name (str):\n Collection the repository manages.\n repo (Any):\n Repository exposing get_by_id() used to resolve references." }, "populate": { "name": "populate", "kind": "function", "path": "mongo_ops.populate.PopulationEngine.populate", - "signature": "", - "docstring": null + "signature": "populate(document: T, rules: list[PopulateRule], depth: int = 0, _visited: set[tuple[str, str]] | None = None, _path: list[str] | None = None)", + "docstring": "Resolve FK fields on a document according to the given rules.\n\nArgs:\n document (T):\n The document to populate in place.\n rules (list[PopulateRule]):\n Rules describing which fields to resolve and how deep.\n depth (int):\n Current recursion depth. Defaults to 0.\n _visited (Optional[set[tuple[str, str]]]):\n Internal set of (class, id) pairs on the active path.\n _path (Optional[list[str]]):\n Internal path labels used for cycle reporting.\n\nReturns:\n T: The populated document.\n\nRaises:\n CircularReferenceError: If a cycle is detected on the active path." }, "depopulate": { "name": "depopulate", "kind": "function", "path": "mongo_ops.populate.PopulationEngine.depopulate", - "signature": "", - "docstring": null + "signature": "depopulate(document: T, rules: list[PopulateRule])", + "docstring": "Collapse populated model references back to their ObjectIds.\n\nThis is the inverse of populate(): model-valued FK fields are reduced\nto stored identifiers before the document is written to MongoDB.\n\nArgs:\n document (T):\n The document to depopulate in place.\n rules (list[PopulateRule]):\n Rules describing which fields to collapse.\n\nReturns:\n T: The depopulated document.\n\nRaises:\n AttributeError: If a list entry is not a BaseDocument where\n expected." } } }, @@ -38,49 +38,49 @@ "name": "PopulateRule", "kind": "class", "path": "mongo_ops.populate.PopulateRule", - "signature": "", - "docstring": null, + "signature": "PopulateRule(field_name: str, collection_name: str, nested_rules: list[PopulateRule] | None = None, max_depth: int = 1, filter: dict[str, Any] | None = None, projection: dict[str, Any] | None = None)", + "docstring": "Describes how a foreign-key field resolves to another collection.\n\nA rule declares the field to resolve, the collection its references\npoint at, and optional nested rules applied to the referenced document\nitself. It also bounds how deep the resolution may recurse.\n\nAttributes:\n field_name (str):\n Name of the FK field on the source document.\n collection_name (str):\n Collection the reference points into.\n nested_rules (Optional[list[PopulateRule]]):\n Sub-rules applied to the referenced document. Defaults to None.\n max_depth (int):\n Maximum recursion depth for this rule. Defaults to 1.\n filter (Optional[dict[str, Any]]):\n Optional Mongo filter applied when fetching the reference.\n Defaults to None.\n projection (Optional[dict[str, Any]]):\n Optional Mongo projection applied when fetching the reference.\n Defaults to None.", "members": { "field_name": { "name": "field_name", "kind": "attribute", "path": "mongo_ops.populate.PopulateRule.field_name", - "signature": "", + "signature": null, "docstring": null }, "collection_name": { "name": "collection_name", "kind": "attribute", "path": "mongo_ops.populate.PopulateRule.collection_name", - "signature": "", + "signature": null, "docstring": null }, "nested_rules": { "name": "nested_rules", "kind": "attribute", "path": "mongo_ops.populate.PopulateRule.nested_rules", - "signature": "", + "signature": null, "docstring": null }, "max_depth": { "name": "max_depth", "kind": "attribute", "path": "mongo_ops.populate.PopulateRule.max_depth", - "signature": "", + "signature": null, "docstring": null }, "filter": { "name": "filter", "kind": "attribute", "path": "mongo_ops.populate.PopulateRule.filter", - "signature": "", + "signature": null, "docstring": null }, "projection": { "name": "projection", "kind": "attribute", "path": "mongo_ops.populate.PopulateRule.projection", - "signature": "", + "signature": null, "docstring": null } } @@ -90,90 +90,34 @@ "kind": "module", "path": "mongo_ops.populate.engine", "signature": null, - "docstring": null, + "docstring": "# Summary\n\nRecursive document population engine with cycle detection.", "members": { - "Any": { - "name": "Any", - "kind": "alias", - "path": "mongo_ops.populate.engine.Any", - "signature": "", - "docstring": null - }, - "Dict": { - "name": "Dict", - "kind": "alias", - "path": "mongo_ops.populate.engine.Dict", - "signature": "", - "docstring": null - }, - "List": { - "name": "List", - "kind": "alias", - "path": "mongo_ops.populate.engine.List", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.populate.engine.Optional", - "signature": "", - "docstring": null - }, - "Set": { - "name": "Set", - "kind": "alias", - "path": "mongo_ops.populate.engine.Set", - "signature": "", - "docstring": null - }, - "Tuple": { - "name": "Tuple", - "kind": "alias", - "path": "mongo_ops.populate.engine.Tuple", - "signature": "", - "docstring": null - }, - "TypeVar": { - "name": "TypeVar", - "kind": "alias", - "path": "mongo_ops.populate.engine.TypeVar", - "signature": "", - "docstring": null - }, - "ObjectId": { - "name": "ObjectId", - "kind": "alias", - "path": "mongo_ops.populate.engine.ObjectId", - "signature": "", - "docstring": null - }, "CircularReferenceError": { "name": "CircularReferenceError", "kind": "class", "path": "mongo_ops.populate.engine.CircularReferenceError", - "signature": "", - "docstring": null, + "signature": "CircularReferenceError(collection: str, doc_id: ObjectId, path: list[str])", + "docstring": "Raised when population detects a cycle in the reference graph.\n\nAttributes:\n collection (str):\n Collection where the cycle was detected.\n doc_id (ObjectId):\n Document ID where the cycle was detected.\n path (list[str]):\n Ordered labels describing the visited reference path.", "members": { "collection": { "name": "collection", "kind": "attribute", "path": "mongo_ops.populate.engine.CircularReferenceError.collection", - "signature": "", + "signature": null, "docstring": null }, "doc_id": { "name": "doc_id", "kind": "attribute", "path": "mongo_ops.populate.engine.CircularReferenceError.doc_id", - "signature": "", + "signature": null, "docstring": null }, "path": { "name": "path", "kind": "attribute", "path": "mongo_ops.populate.engine.CircularReferenceError.path", - "signature": "", + "signature": null, "docstring": null } } @@ -182,63 +126,63 @@ "name": "BaseDocument", "kind": "class", "path": "mongo_ops.populate.engine.BaseDocument", - "signature": "", - "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent \nMongoDB documents. It includes automatic handling of the `_id` field \nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", + "signature": null, + "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent\nMongoDB documents. It includes automatic handling of the `_id` field\nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", "members": { "id": { "name": "id", "kind": "attribute", "path": "mongo_ops.populate.engine.BaseDocument.id", - "signature": "", + "signature": null, "docstring": null }, "created_at": { "name": "created_at", "kind": "attribute", "path": "mongo_ops.populate.engine.BaseDocument.created_at", - "signature": "", + "signature": null, "docstring": null }, "updated_at": { "name": "updated_at", "kind": "attribute", "path": "mongo_ops.populate.engine.BaseDocument.updated_at", - "signature": "", + "signature": null, "docstring": null }, "Config": { "name": "Config", "kind": "class", "path": "mongo_ops.populate.engine.BaseDocument.Config", - "signature": "", + "signature": null, "docstring": null, "members": { "populate_by_name": { "name": "populate_by_name", "kind": "attribute", "path": "mongo_ops.populate.engine.BaseDocument.Config.populate_by_name", - "signature": "", + "signature": null, "docstring": null }, "arbitrary_types_allowed": { "name": "arbitrary_types_allowed", "kind": "attribute", "path": "mongo_ops.populate.engine.BaseDocument.Config.arbitrary_types_allowed", - "signature": "", + "signature": null, "docstring": null }, "json_encoders": { "name": "json_encoders", "kind": "attribute", "path": "mongo_ops.populate.engine.BaseDocument.Config.json_encoders", - "signature": "", + "signature": null, "docstring": null }, "json_schema_extra": { "name": "json_schema_extra", "kind": "attribute", "path": "mongo_ops.populate.engine.BaseDocument.Config.json_schema_extra", - "signature": "", + "signature": null, "docstring": null } } @@ -249,49 +193,49 @@ "name": "PopulateRule", "kind": "class", "path": "mongo_ops.populate.engine.PopulateRule", - "signature": "", - "docstring": null, + "signature": "PopulateRule(field_name: str, collection_name: str, nested_rules: list[PopulateRule] | None = None, max_depth: int = 1, filter: dict[str, Any] | None = None, projection: dict[str, Any] | None = None)", + "docstring": "Describes how a foreign-key field resolves to another collection.\n\nA rule declares the field to resolve, the collection its references\npoint at, and optional nested rules applied to the referenced document\nitself. It also bounds how deep the resolution may recurse.\n\nAttributes:\n field_name (str):\n Name of the FK field on the source document.\n collection_name (str):\n Collection the reference points into.\n nested_rules (Optional[list[PopulateRule]]):\n Sub-rules applied to the referenced document. Defaults to None.\n max_depth (int):\n Maximum recursion depth for this rule. Defaults to 1.\n filter (Optional[dict[str, Any]]):\n Optional Mongo filter applied when fetching the reference.\n Defaults to None.\n projection (Optional[dict[str, Any]]):\n Optional Mongo projection applied when fetching the reference.\n Defaults to None.", "members": { "field_name": { "name": "field_name", "kind": "attribute", "path": "mongo_ops.populate.engine.PopulateRule.field_name", - "signature": "", + "signature": null, "docstring": null }, "collection_name": { "name": "collection_name", "kind": "attribute", "path": "mongo_ops.populate.engine.PopulateRule.collection_name", - "signature": "", + "signature": null, "docstring": null }, "nested_rules": { "name": "nested_rules", "kind": "attribute", "path": "mongo_ops.populate.engine.PopulateRule.nested_rules", - "signature": "", + "signature": null, "docstring": null }, "max_depth": { "name": "max_depth", "kind": "attribute", "path": "mongo_ops.populate.engine.PopulateRule.max_depth", - "signature": "", + "signature": null, "docstring": null }, "filter": { "name": "filter", "kind": "attribute", "path": "mongo_ops.populate.engine.PopulateRule.filter", - "signature": "", + "signature": null, "docstring": null }, "projection": { "name": "projection", "kind": "attribute", "path": "mongo_ops.populate.engine.PopulateRule.projection", - "signature": "", + "signature": null, "docstring": null } } @@ -307,29 +251,29 @@ "name": "PopulationEngine", "kind": "class", "path": "mongo_ops.populate.engine.PopulationEngine", - "signature": "", - "docstring": null, + "signature": "PopulationEngine(repos: dict[str, Any], global_max_depth: int = 10)", + "docstring": "Resolves FK references across collections with cycle detection.\n\nThe engine holds a registry of repositories keyed by collection name and\nwalks documents according to PopulateRules, resolving ObjectId references\ninto model instances. A visited-path set guards against circular graphs.\n\nNotes:\n **Guarantees:**\n\n - A reference that cannot be resolved becomes None (scalar) or a\n None entry (list) rather than raising.\n - Depth is capped by both per-rule max_depth and a global\n global_max_depth.", "members": { "register_repo": { "name": "register_repo", "kind": "function", "path": "mongo_ops.populate.engine.PopulationEngine.register_repo", - "signature": "", - "docstring": null + "signature": "register_repo(collection_name: str, repo: Any) -> None", + "docstring": "Register or replace the repository for a collection.\n\nArgs:\n collection_name (str):\n Collection the repository manages.\n repo (Any):\n Repository exposing get_by_id() used to resolve references." }, "populate": { "name": "populate", "kind": "function", "path": "mongo_ops.populate.engine.PopulationEngine.populate", - "signature": "", - "docstring": null + "signature": "populate(document: T, rules: list[PopulateRule], depth: int = 0, _visited: set[tuple[str, str]] | None = None, _path: list[str] | None = None) -> T", + "docstring": "Resolve FK fields on a document according to the given rules.\n\nArgs:\n document (T):\n The document to populate in place.\n rules (list[PopulateRule]):\n Rules describing which fields to resolve and how deep.\n depth (int):\n Current recursion depth. Defaults to 0.\n _visited (Optional[set[tuple[str, str]]]):\n Internal set of (class, id) pairs on the active path.\n _path (Optional[list[str]]):\n Internal path labels used for cycle reporting.\n\nReturns:\n T: The populated document.\n\nRaises:\n CircularReferenceError: If a cycle is detected on the active path." }, "depopulate": { "name": "depopulate", "kind": "function", "path": "mongo_ops.populate.engine.PopulationEngine.depopulate", - "signature": "", - "docstring": null + "signature": "depopulate(document: T, rules: list[PopulateRule]) -> T", + "docstring": "Collapse populated model references back to their ObjectIds.\n\nThis is the inverse of populate(): model-valued FK fields are reduced\nto stored identifiers before the document is written to MongoDB.\n\nArgs:\n document (T):\n The document to depopulate in place.\n rules (list[PopulateRule]):\n Rules describing which fields to collapse.\n\nReturns:\n T: The depopulated document.\n\nRaises:\n AttributeError: If a list entry is not a BaseDocument where\n expected." } } } @@ -340,49 +284,14 @@ "kind": "module", "path": "mongo_ops.populate.rules", "signature": null, - "docstring": null, + "docstring": "# Summary\n\nPopulate rules describing which foreign-key fields to resolve.", "members": { - "dataclass": { - "name": "dataclass", - "kind": "alias", - "path": "mongo_ops.populate.rules.dataclass", - "signature": "", - "docstring": null - }, - "Any": { - "name": "Any", - "kind": "alias", - "path": "mongo_ops.populate.rules.Any", - "signature": "", - "docstring": null - }, - "Dict": { - "name": "Dict", - "kind": "alias", - "path": "mongo_ops.populate.rules.Dict", - "signature": "", - "docstring": null - }, - "List": { - "name": "List", - "kind": "alias", - "path": "mongo_ops.populate.rules.List", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.populate.rules.Optional", - "signature": "", - "docstring": null - }, "PopulateRule": { "name": "PopulateRule", "kind": "class", "path": "mongo_ops.populate.rules.PopulateRule", - "signature": "", - "docstring": null, + "signature": "PopulateRule(field_name: str, collection_name: str, nested_rules: list[PopulateRule] | None = None, max_depth: int = 1, filter: dict[str, Any] | None = None, projection: dict[str, Any] | None = None)", + "docstring": "Describes how a foreign-key field resolves to another collection.\n\nA rule declares the field to resolve, the collection its references\npoint at, and optional nested rules applied to the referenced document\nitself. It also bounds how deep the resolution may recurse.\n\nAttributes:\n field_name (str):\n Name of the FK field on the source document.\n collection_name (str):\n Collection the reference points into.\n nested_rules (Optional[list[PopulateRule]]):\n Sub-rules applied to the referenced document. Defaults to None.\n max_depth (int):\n Maximum recursion depth for this rule. Defaults to 1.\n filter (Optional[dict[str, Any]]):\n Optional Mongo filter applied when fetching the reference.\n Defaults to None.\n projection (Optional[dict[str, Any]]):\n Optional Mongo projection applied when fetching the reference.\n Defaults to None.", "members": { "field_name": { "name": "field_name", diff --git a/mongo-ops/mcp/modules/mongo_ops.populate.rules.json b/mongo-ops/mcp/modules/mongo_ops.populate.rules.json index f7c6384..a45e226 100644 --- a/mongo-ops/mcp/modules/mongo_ops.populate.rules.json +++ b/mongo-ops/mcp/modules/mongo_ops.populate.rules.json @@ -2,49 +2,14 @@ "module": "mongo_ops.populate.rules", "content": { "path": "mongo_ops.populate.rules", - "docstring": null, + "docstring": "# Summary\n\nPopulate rules describing which foreign-key fields to resolve.", "objects": { - "dataclass": { - "name": "dataclass", - "kind": "alias", - "path": "mongo_ops.populate.rules.dataclass", - "signature": "", - "docstring": null - }, - "Any": { - "name": "Any", - "kind": "alias", - "path": "mongo_ops.populate.rules.Any", - "signature": "", - "docstring": null - }, - "Dict": { - "name": "Dict", - "kind": "alias", - "path": "mongo_ops.populate.rules.Dict", - "signature": "", - "docstring": null - }, - "List": { - "name": "List", - "kind": "alias", - "path": "mongo_ops.populate.rules.List", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.populate.rules.Optional", - "signature": "", - "docstring": null - }, "PopulateRule": { "name": "PopulateRule", "kind": "class", "path": "mongo_ops.populate.rules.PopulateRule", - "signature": "", - "docstring": null, + "signature": "PopulateRule(field_name: str, collection_name: str, nested_rules: list[PopulateRule] | None = None, max_depth: int = 1, filter: dict[str, Any] | None = None, projection: dict[str, Any] | None = None)", + "docstring": "Describes how a foreign-key field resolves to another collection.\n\nA rule declares the field to resolve, the collection its references\npoint at, and optional nested rules applied to the referenced document\nitself. It also bounds how deep the resolution may recurse.\n\nAttributes:\n field_name (str):\n Name of the FK field on the source document.\n collection_name (str):\n Collection the reference points into.\n nested_rules (Optional[list[PopulateRule]]):\n Sub-rules applied to the referenced document. Defaults to None.\n max_depth (int):\n Maximum recursion depth for this rule. Defaults to 1.\n filter (Optional[dict[str, Any]]):\n Optional Mongo filter applied when fetching the reference.\n Defaults to None.\n projection (Optional[dict[str, Any]]):\n Optional Mongo projection applied when fetching the reference.\n Defaults to None.", "members": { "field_name": { "name": "field_name", diff --git a/mongo-ops/mcp/modules/mongo_ops.registry.json b/mongo-ops/mcp/modules/mongo_ops.registry.json index 57727e2..fadd7cc 100644 --- a/mongo-ops/mcp/modules/mongo_ops.registry.json +++ b/mongo-ops/mcp/modules/mongo_ops.registry.json @@ -2,105 +2,70 @@ "module": "mongo_ops.registry", "content": { "path": "mongo_ops.registry", - "docstring": "Model registration for multi-service initialization.", + "docstring": "# Summary\n\nModel registration for multi-service initialization.", "objects": { - "Dict": { - "name": "Dict", - "kind": "alias", - "path": "mongo_ops.registry.Dict", - "signature": "", - "docstring": null - }, - "List": { - "name": "List", - "kind": "alias", - "path": "mongo_ops.registry.List", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.registry.Optional", - "signature": "", - "docstring": null - }, - "Type": { - "name": "Type", - "kind": "alias", - "path": "mongo_ops.registry.Type", - "signature": "", - "docstring": null - }, - "AsyncIOMotorDatabase": { - "name": "AsyncIOMotorDatabase", - "kind": "alias", - "path": "mongo_ops.registry.AsyncIOMotorDatabase", - "signature": "", - "docstring": null - }, "CacheBackend": { "name": "CacheBackend", "kind": "class", "path": "mongo_ops.registry.CacheBackend", - "signature": "", - "docstring": null, + "signature": null, + "docstring": "Abstract interface for cache backends.\n\nImplementations store byte-encoded values keyed by string, track usage\nstatistics, and manage their own lifecycle. The in-memory and Redis\nbackends both implement this contract.", "members": { "get": { "name": "get", "kind": "function", "path": "mongo_ops.registry.CacheBackend.get", - "signature": "", - "docstring": null + "signature": "get(key: str)", + "docstring": "Fetch a value from the cache.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n Optional[bytes]: The cached bytes, or None on a miss." }, "set": { "name": "set", "kind": "function", "path": "mongo_ops.registry.CacheBackend.set", - "signature": "", - "docstring": null + "signature": "set(key: str, value: bytes, ttl: int | None = None)", + "docstring": "Store a value in the cache.\n\nArgs:\n key (str):\n The cache key.\n value (bytes):\n The byte-encoded value to store.\n ttl (Optional[int]):\n Time-to-live in seconds. When None, the backend default\n applies." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.registry.CacheBackend.delete", - "signature": "", - "docstring": null + "signature": "delete(key: str)", + "docstring": "Remove a key from the cache.\n\nArgs:\n key (str):\n The cache key." }, "exists": { "name": "exists", "kind": "function", "path": "mongo_ops.registry.CacheBackend.exists", - "signature": "", - "docstring": null + "signature": "exists(key: str)", + "docstring": "Check whether a key is present.\n\nArgs:\n key (str):\n The cache key.\n\nReturns:\n bool: True if the key exists, False otherwise." }, "clear_pattern": { "name": "clear_pattern", "kind": "function", "path": "mongo_ops.registry.CacheBackend.clear_pattern", - "signature": "", - "docstring": null + "signature": "clear_pattern(pattern: str)", + "docstring": "Remove all keys matching a glob pattern.\n\nArgs:\n pattern (str):\n Glob-style pattern; a trailing `*` matches prefixes." }, "get_stats": { "name": "get_stats", "kind": "function", "path": "mongo_ops.registry.CacheBackend.get_stats", - "signature": "", - "docstring": null + "signature": "get_stats()", + "docstring": "Return a snapshot of cache statistics.\n\nReturns:\n CacheStats: A copy of the current stats counters." }, "initialize": { "name": "initialize", "kind": "function", "path": "mongo_ops.registry.CacheBackend.initialize", - "signature": "", - "docstring": null + "signature": "initialize()", + "docstring": "Start background resources owned by the backend.\n\nShould be called once during application startup, after the\nrepositories are connected." }, "shutdown": { "name": "shutdown", "kind": "function", "path": "mongo_ops.registry.CacheBackend.shutdown", - "signature": "", - "docstring": null + "signature": "shutdown()", + "docstring": "Stop and release background resources.\n\nShould be called once during application shutdown." } } }, @@ -108,43 +73,43 @@ "name": "MongoConnectionManager", "kind": "class", "path": "mongo_ops.registry.MongoConnectionManager", - "signature": "", - "docstring": "Manages MongoDB connections with async lifecycle.\n\nThis class provides a singleton-like manager for the MongoDB client and \ndatabase instances, ensuring they are properly initialized and closed \nacross the application lifecycle.", + "signature": null, + "docstring": "Manages MongoDB connections with async lifecycle.\n\nThis class provides a singleton-like manager for the MongoDB client and\ndatabase instances, ensuring they are properly initialized and closed\nacross the application lifecycle.", "members": { "connect": { "name": "connect", "kind": "function", "path": "mongo_ops.registry.MongoConnectionManager.connect", - "signature": "", - "docstring": "Connect to MongoDB and initialize the shared client.\n\nArgs:\n uri: MongoDB connection URI (e.g., \"mongodb://localhost:27017\").\n db_name: Name of the database to use.\n **kwargs: Additional Motor client options (e.g., maxPoolSize).\n\nReturns:\n AsyncIOMotorDatabase: The initialized database instance." + "signature": "connect(uri: str, db_name: str, **kwargs: Any)", + "docstring": "Connect to MongoDB and initialize the shared client.\n\nArgs:\n uri (str): MongoDB connection URI (e.g., \"mongodb://localhost:27017\").\n db_name (str): Name of the database to use.\n **kwargs: Additional Motor client options (e.g., maxPoolSize).\n\nReturns:\n AsyncIOMotorDatabase: The initialized database instance." }, "disconnect": { "name": "disconnect", "kind": "function", "path": "mongo_ops.registry.MongoConnectionManager.disconnect", - "signature": "", + "signature": "disconnect()", "docstring": "Close the active MongoDB connection and cleanup resources." }, "get_database": { "name": "get_database", "kind": "function", "path": "mongo_ops.registry.MongoConnectionManager.get_database", - "signature": "", + "signature": "get_database()", "docstring": "Retrieve the current database instance.\n\nReturns:\n AsyncIOMotorDatabase: The active database instance.\n\nRaises:\n RuntimeError: If connect() has not been called yet." }, "get_client": { "name": "get_client", "kind": "function", "path": "mongo_ops.registry.MongoConnectionManager.get_client", - "signature": "", + "signature": "get_client()", "docstring": "Retrieve the current client instance.\n\nReturns:\n AsyncIOMotorClient: The active Motor client instance.\n\nRaises:\n RuntimeError: If connect() has not been called yet." }, "lifespan": { "name": "lifespan", "kind": "function", "path": "mongo_ops.registry.MongoConnectionManager.lifespan", - "signature": "", - "docstring": "Async context manager for managing connection lifecycle.\n\nDesigned for use with FastAPI or other frameworks supporting \nlifespan management.\n\nArgs:\n uri: MongoDB connection URI.\n db_name: Name of the database.\n **kwargs: Additional Motor client options.\n\nYields:\n AsyncIOMotorDatabase: The active database instance.\n\nUsage:\n @asynccontextmanager\n async def lifespan(app: FastAPI):\n async with MongoConnectionManager.lifespan(uri, db_name):\n yield" + "signature": "lifespan(uri: str, db_name: str, **kwargs: Any)", + "docstring": "Async context manager for managing connection lifecycle.\n\nDesigned for use with FastAPI or other frameworks supporting\nlifespan management.\n\nNotes:\n Connects to MongoDB on entry, yields the active database instance\n to the context body, and disconnects on exit.\n\nUsage:\n @asynccontextmanager\n async def lifespan(app: FastAPI):\n async with MongoConnectionManager.lifespan(uri, db_name):\n yield" } } }, @@ -152,63 +117,63 @@ "name": "BaseDocument", "kind": "class", "path": "mongo_ops.registry.BaseDocument", - "signature": "", - "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent \nMongoDB documents. It includes automatic handling of the `_id` field \nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", + "signature": null, + "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent\nMongoDB documents. It includes automatic handling of the `_id` field\nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", "members": { "id": { "name": "id", "kind": "attribute", "path": "mongo_ops.registry.BaseDocument.id", - "signature": "", + "signature": null, "docstring": null }, "created_at": { "name": "created_at", "kind": "attribute", "path": "mongo_ops.registry.BaseDocument.created_at", - "signature": "", + "signature": null, "docstring": null }, "updated_at": { "name": "updated_at", "kind": "attribute", "path": "mongo_ops.registry.BaseDocument.updated_at", - "signature": "", + "signature": null, "docstring": null }, "Config": { "name": "Config", "kind": "class", "path": "mongo_ops.registry.BaseDocument.Config", - "signature": "", + "signature": null, "docstring": null, "members": { "populate_by_name": { "name": "populate_by_name", "kind": "attribute", "path": "mongo_ops.registry.BaseDocument.Config.populate_by_name", - "signature": "", + "signature": null, "docstring": null }, "arbitrary_types_allowed": { "name": "arbitrary_types_allowed", "kind": "attribute", "path": "mongo_ops.registry.BaseDocument.Config.arbitrary_types_allowed", - "signature": "", + "signature": null, "docstring": null }, "json_encoders": { "name": "json_encoders", "kind": "attribute", "path": "mongo_ops.registry.BaseDocument.Config.json_encoders", - "signature": "", + "signature": null, "docstring": null }, "json_schema_extra": { "name": "json_schema_extra", "kind": "attribute", "path": "mongo_ops.registry.BaseDocument.Config.json_schema_extra", - "signature": "", + "signature": null, "docstring": null } } @@ -219,63 +184,63 @@ "name": "ModelRegistry", "kind": "class", "path": "mongo_ops.registry.ModelRegistry", - "signature": "", - "docstring": "Registry for managing multiple models and their collections.\n\nThis registry allows central management of collections and their \nassociated indexes, making it easier to perform mass initialization \nat application startup.", + "signature": null, + "docstring": "Registry for managing multiple models and their collections.\n\nThis registry allows central management of collections and their\nassociated indexes, making it easier to perform mass initialization\nat application startup.", "members": { "register": { "name": "register", "kind": "function", "path": "mongo_ops.registry.ModelRegistry.register", - "signature": "", - "docstring": "Register a model with its collection and indexes.\n\nArgs:\n collection_name: Name of the MongoDB collection.\n model: Document model class (subclass of BaseDocument).\n indexes: List of index specifications (e.g., [(\"email\", 1)]).\n\nUsage:\n ModelRegistry.register(\n \"users\",\n UserDocument,\n indexes=[(\"email\", 1), (\"created_at\", -1)]\n )" + "signature": "register(collection_name: str, model: type[BaseDocument], indexes: list[tuple] | None = None) -> None", + "docstring": "Register a model with its collection and indexes.\n\nArgs:\n collection_name (str): Name of the MongoDB collection.\n model (type[BaseDocument]): Document model class (subclass of BaseDocument).\n indexes (list[Any]): List of index specifications. Each spec is passed\n directly to pymongo's create_index. Supported forms:\n - single field: (\"email\", 1)\n - compound: [(\"author_id\", 1), (\"created_at\", -1)]\n - with options: {\"keys\": [(\"username\", 1)], \"options\": {\"unique\": True}}\n\nExample:\n ModelRegistry.register(\"users\", UserDocument, indexes=[(\"email\", 1)])\n ModelRegistry.register(\n \"posts\",\n PostDocument,\n indexes=[[(\"author_id\", 1), (\"created_at\", -1)]],\n )" }, "initialize_all": { "name": "initialize_all", "kind": "function", "path": "mongo_ops.registry.ModelRegistry.initialize_all", - "signature": "", - "docstring": "Initialize all registered collections and create indexes.\n\nThis method should be called during application startup to ensure \nall necessary indexes exist in the database.\n\nArgs:\n db: Database instance. If not provided, uses the global database \n from MongoConnectionManager." + "signature": "initialize_all(db: AsyncIOMotorDatabase | None = None) -> None", + "docstring": "Initialize all registered collections and create indexes.\n\nThis method should be called during application startup to ensure\nall necessary indexes exist in the database.\n\nArgs:\n db (Optional[AsyncIOMotorDatabase]): Database instance. If not provided,\n uses the global database from MongoConnectionManager." }, "get_model": { "name": "get_model", "kind": "function", "path": "mongo_ops.registry.ModelRegistry.get_model", - "signature": "", - "docstring": "Retrieve a registered model by its collection name.\n\nArgs:\n collection_name: The name of the collection.\n\nReturns:\n Type[BaseDocument]: The registered model class.\n\nRaises:\n KeyError: If the model for the given collection is not registered." + "signature": "get_model(collection_name: str) -> type[BaseDocument]", + "docstring": "Retrieve a registered model by its collection name.\n\nArgs:\n collection_name (str): The name of the collection.\n\nReturns:\n type[BaseDocument]: The registered model class.\n\nRaises:\n KeyError: If the model for the given collection is not registered." }, "list_collections": { "name": "list_collections", "kind": "function", "path": "mongo_ops.registry.ModelRegistry.list_collections", - "signature": "", - "docstring": "List all registered collection names.\n\nReturns:\n List[str]: A list of collection names." + "signature": "list_collections() -> list[str]", + "docstring": "List all registered collection names.\n\nReturns:\n list[str]: A list of collection names." }, "get_cache_backend": { "name": "get_cache_backend", "kind": "function", "path": "mongo_ops.registry.ModelRegistry.get_cache_backend", - "signature": "", + "signature": "get_cache_backend() -> CacheBackend | None", "docstring": "Get the registered cache backend instance.\n\nReturns:\n Optional[CacheBackend]: The cache backend, if registered." }, "set_cache_backend": { "name": "set_cache_backend", "kind": "function", "path": "mongo_ops.registry.ModelRegistry.set_cache_backend", - "signature": "", - "docstring": "Register a cache backend for all cache-enabled repositories.\n\nShould be called after MongoDB connection is established,\nbefore cache-backed repositories are used.\n\nArgs:\n backend: A CacheBackend instance (InMemoryCacheBackend\n or RedisCacheBackend)." + "signature": "set_cache_backend(backend: CacheBackend) -> None", + "docstring": "Register a cache backend for all cache-enabled repositories.\n\nShould be called after MongoDB connection is established,\nbefore cache-backed repositories are used.\n\nArgs:\n backend (CacheBackend): A CacheBackend instance (InMemoryCacheBackend\n or RedisCacheBackend)." }, "initialize_cache": { "name": "initialize_cache", "kind": "function", "path": "mongo_ops.registry.ModelRegistry.initialize_cache", - "signature": "", + "signature": "initialize_cache() -> None", "docstring": "Initialize the registered cache backend.\n\nMust be called after set_cache_backend() and before any\ncache-backed repository operations. Typically called right\nafter MongoDB connection is established.\n\nRaises:\n RuntimeError: If no cache backend has been registered." }, "shutdown_cache": { "name": "shutdown_cache", "kind": "function", "path": "mongo_ops.registry.ModelRegistry.shutdown_cache", - "signature": "", + "signature": "shutdown_cache() -> None", "docstring": "Shutdown the registered cache backend gracefully.\n\nShould be called during application shutdown to clean up\nbackground tasks (e.g., in-memory TTL cleanup)." } } diff --git a/mongo-ops/mcp/modules/mongo_ops.repository.json b/mongo-ops/mcp/modules/mongo_ops.repository.json index 18487fb..f21adf9 100644 --- a/mongo-ops/mcp/modules/mongo_ops.repository.json +++ b/mongo-ops/mcp/modules/mongo_ops.repository.json @@ -2,119 +2,49 @@ "module": "mongo_ops.repository", "content": { "path": "mongo_ops.repository", - "docstring": "Repository patterns and CRUD mixins for MongoDB.", + "docstring": "# Summary\n\nRepository patterns and CRUD mixins for MongoDB.", "objects": { - "datetime": { - "name": "datetime", - "kind": "alias", - "path": "mongo_ops.repository.datetime", - "signature": "", - "docstring": null - }, - "Any": { - "name": "Any", - "kind": "alias", - "path": "mongo_ops.repository.Any", - "signature": "", - "docstring": null - }, - "Dict": { - "name": "Dict", - "kind": "alias", - "path": "mongo_ops.repository.Dict", - "signature": "", - "docstring": null - }, - "Generic": { - "name": "Generic", - "kind": "alias", - "path": "mongo_ops.repository.Generic", - "signature": "", - "docstring": null - }, - "List": { - "name": "List", - "kind": "alias", - "path": "mongo_ops.repository.List", - "signature": "", - "docstring": null - }, - "Optional": { - "name": "Optional", - "kind": "alias", - "path": "mongo_ops.repository.Optional", - "signature": "", - "docstring": null - }, - "TypeVar": { - "name": "TypeVar", - "kind": "alias", - "path": "mongo_ops.repository.TypeVar", - "signature": "", - "docstring": null - }, - "Union": { - "name": "Union", - "kind": "alias", - "path": "mongo_ops.repository.Union", - "signature": "", - "docstring": null - }, - "ObjectId": { - "name": "ObjectId", - "kind": "alias", - "path": "mongo_ops.repository.ObjectId", - "signature": "", - "docstring": null - }, - "AsyncIOMotorCollection": { - "name": "AsyncIOMotorCollection", - "kind": "alias", - "path": "mongo_ops.repository.AsyncIOMotorCollection", - "signature": "", - "docstring": null - }, "MongoConnectionManager": { "name": "MongoConnectionManager", "kind": "class", "path": "mongo_ops.repository.MongoConnectionManager", - "signature": "", - "docstring": "Manages MongoDB connections with async lifecycle.\n\nThis class provides a singleton-like manager for the MongoDB client and \ndatabase instances, ensuring they are properly initialized and closed \nacross the application lifecycle.", + "signature": null, + "docstring": "Manages MongoDB connections with async lifecycle.\n\nThis class provides a singleton-like manager for the MongoDB client and\ndatabase instances, ensuring they are properly initialized and closed\nacross the application lifecycle.", "members": { "connect": { "name": "connect", "kind": "function", "path": "mongo_ops.repository.MongoConnectionManager.connect", - "signature": "", - "docstring": "Connect to MongoDB and initialize the shared client.\n\nArgs:\n uri: MongoDB connection URI (e.g., \"mongodb://localhost:27017\").\n db_name: Name of the database to use.\n **kwargs: Additional Motor client options (e.g., maxPoolSize).\n\nReturns:\n AsyncIOMotorDatabase: The initialized database instance." + "signature": "connect(uri: str, db_name: str, **kwargs: Any)", + "docstring": "Connect to MongoDB and initialize the shared client.\n\nArgs:\n uri (str): MongoDB connection URI (e.g., \"mongodb://localhost:27017\").\n db_name (str): Name of the database to use.\n **kwargs: Additional Motor client options (e.g., maxPoolSize).\n\nReturns:\n AsyncIOMotorDatabase: The initialized database instance." }, "disconnect": { "name": "disconnect", "kind": "function", "path": "mongo_ops.repository.MongoConnectionManager.disconnect", - "signature": "", + "signature": "disconnect()", "docstring": "Close the active MongoDB connection and cleanup resources." }, "get_database": { "name": "get_database", "kind": "function", "path": "mongo_ops.repository.MongoConnectionManager.get_database", - "signature": "", + "signature": "get_database()", "docstring": "Retrieve the current database instance.\n\nReturns:\n AsyncIOMotorDatabase: The active database instance.\n\nRaises:\n RuntimeError: If connect() has not been called yet." }, "get_client": { "name": "get_client", "kind": "function", "path": "mongo_ops.repository.MongoConnectionManager.get_client", - "signature": "", + "signature": "get_client()", "docstring": "Retrieve the current client instance.\n\nReturns:\n AsyncIOMotorClient: The active Motor client instance.\n\nRaises:\n RuntimeError: If connect() has not been called yet." }, "lifespan": { "name": "lifespan", "kind": "function", "path": "mongo_ops.repository.MongoConnectionManager.lifespan", - "signature": "", - "docstring": "Async context manager for managing connection lifecycle.\n\nDesigned for use with FastAPI or other frameworks supporting \nlifespan management.\n\nArgs:\n uri: MongoDB connection URI.\n db_name: Name of the database.\n **kwargs: Additional Motor client options.\n\nYields:\n AsyncIOMotorDatabase: The active database instance.\n\nUsage:\n @asynccontextmanager\n async def lifespan(app: FastAPI):\n async with MongoConnectionManager.lifespan(uri, db_name):\n yield" + "signature": "lifespan(uri: str, db_name: str, **kwargs: Any)", + "docstring": "Async context manager for managing connection lifecycle.\n\nDesigned for use with FastAPI or other frameworks supporting\nlifespan management.\n\nNotes:\n Connects to MongoDB on entry, yields the active database instance\n to the context body, and disconnects on exit.\n\nUsage:\n @asynccontextmanager\n async def lifespan(app: FastAPI):\n async with MongoConnectionManager.lifespan(uri, db_name):\n yield" } } }, @@ -122,63 +52,63 @@ "name": "BaseDocument", "kind": "class", "path": "mongo_ops.repository.BaseDocument", - "signature": "", - "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent \nMongoDB documents. It includes automatic handling of the `_id` field \nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", + "signature": null, + "docstring": "Base document class with common MongoDB fields.\n\nInherit from this class to create Pydantic models that represent\nMongoDB documents. It includes automatic handling of the `_id` field\nand timestamps.\n\nAttributes:\n id: The MongoDB document ID (aliased to `_id`).\n created_at: Timestamp when the document was created.\n updated_at: Timestamp when the document was last updated.", "members": { "id": { "name": "id", "kind": "attribute", "path": "mongo_ops.repository.BaseDocument.id", - "signature": "", + "signature": null, "docstring": null }, "created_at": { "name": "created_at", "kind": "attribute", "path": "mongo_ops.repository.BaseDocument.created_at", - "signature": "", + "signature": null, "docstring": null }, "updated_at": { "name": "updated_at", "kind": "attribute", "path": "mongo_ops.repository.BaseDocument.updated_at", - "signature": "", + "signature": null, "docstring": null }, "Config": { "name": "Config", "kind": "class", "path": "mongo_ops.repository.BaseDocument.Config", - "signature": "", + "signature": null, "docstring": null, "members": { "populate_by_name": { "name": "populate_by_name", "kind": "attribute", "path": "mongo_ops.repository.BaseDocument.Config.populate_by_name", - "signature": "", + "signature": null, "docstring": null }, "arbitrary_types_allowed": { "name": "arbitrary_types_allowed", "kind": "attribute", "path": "mongo_ops.repository.BaseDocument.Config.arbitrary_types_allowed", - "signature": "", + "signature": null, "docstring": null }, "json_encoders": { "name": "json_encoders", "kind": "attribute", "path": "mongo_ops.repository.BaseDocument.Config.json_encoders", - "signature": "", + "signature": null, "docstring": null }, "json_schema_extra": { "name": "json_schema_extra", "kind": "attribute", "path": "mongo_ops.repository.BaseDocument.Config.json_schema_extra", - "signature": "", + "signature": null, "docstring": null } } @@ -189,29 +119,29 @@ "name": "PopulationEngine", "kind": "class", "path": "mongo_ops.repository.PopulationEngine", - "signature": "", - "docstring": null, + "signature": "PopulationEngine(repos: dict[str, Any], global_max_depth: int = 10)", + "docstring": "Resolves FK references across collections with cycle detection.\n\nThe engine holds a registry of repositories keyed by collection name and\nwalks documents according to PopulateRules, resolving ObjectId references\ninto model instances. A visited-path set guards against circular graphs.\n\nNotes:\n **Guarantees:**\n\n - A reference that cannot be resolved becomes None (scalar) or a\n None entry (list) rather than raising.\n - Depth is capped by both per-rule max_depth and a global\n global_max_depth.", "members": { "register_repo": { "name": "register_repo", "kind": "function", "path": "mongo_ops.repository.PopulationEngine.register_repo", - "signature": "", - "docstring": null + "signature": "register_repo(collection_name: str, repo: Any)", + "docstring": "Register or replace the repository for a collection.\n\nArgs:\n collection_name (str):\n Collection the repository manages.\n repo (Any):\n Repository exposing get_by_id() used to resolve references." }, "populate": { "name": "populate", "kind": "function", "path": "mongo_ops.repository.PopulationEngine.populate", - "signature": "", - "docstring": null + "signature": "populate(document: T, rules: list[PopulateRule], depth: int = 0, _visited: set[tuple[str, str]] | None = None, _path: list[str] | None = None)", + "docstring": "Resolve FK fields on a document according to the given rules.\n\nArgs:\n document (T):\n The document to populate in place.\n rules (list[PopulateRule]):\n Rules describing which fields to resolve and how deep.\n depth (int):\n Current recursion depth. Defaults to 0.\n _visited (Optional[set[tuple[str, str]]]):\n Internal set of (class, id) pairs on the active path.\n _path (Optional[list[str]]):\n Internal path labels used for cycle reporting.\n\nReturns:\n T: The populated document.\n\nRaises:\n CircularReferenceError: If a cycle is detected on the active path." }, "depopulate": { "name": "depopulate", "kind": "function", "path": "mongo_ops.repository.PopulationEngine.depopulate", - "signature": "", - "docstring": null + "signature": "depopulate(document: T, rules: list[PopulateRule])", + "docstring": "Collapse populated model references back to their ObjectIds.\n\nThis is the inverse of populate(): model-valued FK fields are reduced\nto stored identifiers before the document is written to MongoDB.\n\nArgs:\n document (T):\n The document to depopulate in place.\n rules (list[PopulateRule]):\n Rules describing which fields to collapse.\n\nReturns:\n T: The depopulated document.\n\nRaises:\n AttributeError: If a list entry is not a BaseDocument where\n expected." } } }, @@ -219,49 +149,49 @@ "name": "PopulateRule", "kind": "class", "path": "mongo_ops.repository.PopulateRule", - "signature": "", - "docstring": null, + "signature": "PopulateRule(field_name: str, collection_name: str, nested_rules: list[PopulateRule] | None = None, max_depth: int = 1, filter: dict[str, Any] | None = None, projection: dict[str, Any] | None = None)", + "docstring": "Describes how a foreign-key field resolves to another collection.\n\nA rule declares the field to resolve, the collection its references\npoint at, and optional nested rules applied to the referenced document\nitself. It also bounds how deep the resolution may recurse.\n\nAttributes:\n field_name (str):\n Name of the FK field on the source document.\n collection_name (str):\n Collection the reference points into.\n nested_rules (Optional[list[PopulateRule]]):\n Sub-rules applied to the referenced document. Defaults to None.\n max_depth (int):\n Maximum recursion depth for this rule. Defaults to 1.\n filter (Optional[dict[str, Any]]):\n Optional Mongo filter applied when fetching the reference.\n Defaults to None.\n projection (Optional[dict[str, Any]]):\n Optional Mongo projection applied when fetching the reference.\n Defaults to None.", "members": { "field_name": { "name": "field_name", "kind": "attribute", "path": "mongo_ops.repository.PopulateRule.field_name", - "signature": "", + "signature": null, "docstring": null }, "collection_name": { "name": "collection_name", "kind": "attribute", "path": "mongo_ops.repository.PopulateRule.collection_name", - "signature": "", + "signature": null, "docstring": null }, "nested_rules": { "name": "nested_rules", "kind": "attribute", "path": "mongo_ops.repository.PopulateRule.nested_rules", - "signature": "", + "signature": null, "docstring": null }, "max_depth": { "name": "max_depth", "kind": "attribute", "path": "mongo_ops.repository.PopulateRule.max_depth", - "signature": "", + "signature": null, "docstring": null }, "filter": { "name": "filter", "kind": "attribute", "path": "mongo_ops.repository.PopulateRule.filter", - "signature": "", + "signature": null, "docstring": null }, "projection": { "name": "projection", "kind": "attribute", "path": "mongo_ops.repository.PopulateRule.projection", - "signature": "", + "signature": null, "docstring": null } } @@ -277,8 +207,8 @@ "name": "CRUDMixin", "kind": "class", "path": "mongo_ops.repository.CRUDMixin", - "signature": "", - "docstring": "Generic CRUD operations mixin for MongoDB collections.\n\nThis mixin provides standard Create, Read, Update, and Delete operations \nthat work with Pydantic models.\n\nAttributes:\n collection: The Motor collection instance.\n model: The Pydantic model class representing the document.", + "signature": "CRUDMixin(collection: AsyncIOMotorCollection, model: type[T])", + "docstring": "Generic CRUD operations mixin for MongoDB collections.\n\nThis mixin provides standard Create, Read, Update, and Delete operations\nthat work with Pydantic models.\n\nAttributes:\n collection: The Motor collection instance.\n model: The Pydantic model class representing the document.", "members": { "collection": { "name": "collection", @@ -298,57 +228,57 @@ "name": "data_to_model", "kind": "function", "path": "mongo_ops.repository.CRUDMixin.data_to_model", - "signature": "", + "signature": "data_to_model(data: dict) -> T", "docstring": null }, "create": { "name": "create", "kind": "function", "path": "mongo_ops.repository.CRUDMixin.create", - "signature": "", - "docstring": "Create a new document in the collection.\n\nArgs:\n data: The Pydantic model instance to insert.\n\nReturns:\n T: The created Pydantic model instance, including the assigned ID." + "signature": "create(data: T) -> T", + "docstring": "Create a new document in the collection.\n\nArgs:\n data (T): The Pydantic model instance to insert.\n\nReturns:\n T: The created Pydantic model instance, including the assigned ID." }, "get_by_id": { "name": "get_by_id", "kind": "function", "path": "mongo_ops.repository.CRUDMixin.get_by_id", - "signature": "", - "docstring": "Retrieve a document by its ID.\n\nArgs:\n id: The document ID (string or ObjectId).\n\nReturns:\n Optional[T]: The Pydantic model instance if found, else None." + "signature": "get_by_id(id: str | ObjectId) -> T | None", + "docstring": "Retrieve a document by its ID.\n\nArgs:\n id (Union[str, ObjectId]): The document ID (string or ObjectId).\n\nReturns:\n Optional[T]: The Pydantic model instance if found, else None." }, "get_many": { "name": "get_many", "kind": "function", "path": "mongo_ops.repository.CRUDMixin.get_many", - "signature": "", - "docstring": "Retrieve multiple documents with filtering, pagination, and sorting.\n\nArgs:\n filter: MongoDB filter dictionary (e.g., {\"is_active\": True}).\n skip: Number of documents to skip for pagination.\n limit: Maximum number of documents to return (default 100).\n sort: List of sort specifications [(field, direction), ...].\n E.g., [(\"created_at\", -1)] for descending.\n\nReturns:\n List[T]: A list of Pydantic model instances.\n\nUsage:\n ```python\n users = await repo.get_many(\n filter={\"role\": \"admin\"},\n limit=10,\n sort=[(\"username\", 1)]\n )\n ```" + "signature": "get_many(filter: dict[str, Any] | None = None, skip: int = 0, limit: int = 100, sort: list[tuple] | None = None) -> list[T]", + "docstring": "Retrieve multiple documents with filtering, pagination, and sorting.\n\nArgs:\n filter (Optional[Dict[str, Any]]): MongoDB filter dictionary (e.g., {\"is_active\": True}).\n skip (int): Number of documents to skip for pagination.\n limit (int): Maximum number of documents to return (default 100).\n sort (Optional[List[tuple]]): List of sort specifications [(field, direction), ...].\n E.g., [(\"created_at\", -1)] for descending.\n\nReturns:\n List[T]: A list of Pydantic model instances.\n\nExample:\n ```python\n users = await repo.get_many(\n filter={\"role\": \"admin\"},\n limit=10,\n sort=[(\"username\", 1)]\n )\n ```" }, "update": { "name": "update", "kind": "function", "path": "mongo_ops.repository.CRUDMixin.update", - "signature": "", - "docstring": "Update a document by its ID using the $set operator.\n\nArgs:\n id: The document ID (string or ObjectId).\n data: A dictionary of fields and values to update.\n\nReturns:\n Optional[T]: The updated Pydantic model instance if found, else None.\n\nUsage:\n ```python\n updated_user = await repo.update(user_id, {\"email\": \"new@example.com\"})\n ```" + "signature": "update(id: str | ObjectId, data: dict[str, Any]) -> T | None", + "docstring": "Update a document by its ID using the $set operator.\n\nArgs:\n id (Union[str, ObjectId]): The document ID (string or ObjectId).\n data (Dict[str, Any]): A dictionary of fields and values to update.\n\nReturns:\n Optional[T]: The updated Pydantic model instance if found, else None.\n\nExample:\n ```python\n updated_user = await repo.update(user_id, {\"email\": \"new@example.com\"})\n ```" }, "patch": { "name": "patch", "kind": "function", "path": "mongo_ops.repository.CRUDMixin.patch", - "signature": "", - "docstring": "Partially update a document using $set (REST PATCH semantics).\n\nUnlike update(), patch() takes a partial dict and applies only those\nfields. PopulatingRepository overrides this to prevent patching FK fields.\n\nArgs:\n id: The document ID (string or ObjectId).\n data: A partial dictionary of fields and values to update.\n\nReturns:\n Optional[T]: The updated Pydantic model instance if found, else None." + "signature": "patch(id: str | ObjectId, data: dict[str, Any]) -> T | None", + "docstring": "Partially update a document using $set (REST PATCH semantics).\n\nUnlike update(), patch() takes a partial dict and applies only those\nfields. PopulatingRepository overrides this to prevent patching FK fields.\n\nArgs:\n id (Union[str, ObjectId]): The document ID (string or ObjectId).\n data (Dict[str, Any]): A partial dictionary of fields and values to update.\n\nReturns:\n Optional[T]: The updated Pydantic model instance if found, else None." }, "delete": { "name": "delete", "kind": "function", "path": "mongo_ops.repository.CRUDMixin.delete", - "signature": "", - "docstring": "Delete a document by its ID.\n\nArgs:\n id: The document ID (string or ObjectId).\n\nReturns:\n bool: True if a document was deleted, False otherwise." + "signature": "delete(id: str | ObjectId) -> bool", + "docstring": "Delete a document by its ID.\n\nArgs:\n id (Union[str, ObjectId]): The document ID (string or ObjectId).\n\nReturns:\n bool: True if a document was deleted, False otherwise." }, "count": { "name": "count", "kind": "function", "path": "mongo_ops.repository.CRUDMixin.count", - "signature": "", - "docstring": "Count documents matching a filter.\n\nArgs:\n filter: MongoDB filter dictionary.\n\nReturns:\n int: The number of matching documents." + "signature": "count(filter: dict[str, Any] | None = None) -> int", + "docstring": "Count documents matching a filter.\n\nArgs:\n filter (Optional[Dict[str, Any]]): MongoDB filter dictionary.\n\nReturns:\n int: The number of matching documents." } } }, @@ -356,8 +286,8 @@ "name": "BaseRepository", "kind": "class", "path": "mongo_ops.repository.BaseRepository", - "signature": "", - "docstring": "Base repository class combining CRUD operations and collection management.\n\nThis class simplifies repository creation by automatically obtaining the \ndatabase connection and collection instance.\n\nAttributes:\n collection_name: The name of the collection managed by this repository.", + "signature": "BaseRepository(collection_name: str, model: type[T])", + "docstring": "Base repository class combining CRUD operations and collection management.\n\nThis class simplifies repository creation by automatically obtaining the\ndatabase connection and collection instance.\n\nAttributes:\n collection_name: The name of the collection managed by this repository.", "members": { "collection_name": { "name": "collection_name", @@ -372,8 +302,8 @@ "name": "PopulatingRepository", "kind": "class", "path": "mongo_ops.repository.PopulatingRepository", - "signature": "", - "docstring": null, + "signature": "PopulatingRepository(collection_name: str, model: type[T], population_engine: PopulationEngine | None = None, populate_rules: list[PopulateRule] | None = None)", + "docstring": "Repository that auto-populates and depopulates FK references.\n\nOn read, ObjectId FK fields are resolved to model instances according to\nthe configured PopulateRules. On write, populated model references are\ncollapsed back to ObjectIds before hitting MongoDB.\n\nNotes:\n **Guarantees:**\n\n - Populate rules apply on both read (data_to_model) and write\n (create/update) paths.\n - Patching FK fields via patch() is rejected.\n - Class attributes `population_engine` and `_populate_rules` must\n be set (see set_population_engine/set_populate_rules) for any\n population to occur.", "members": { "population_engine": { "name": "population_engine", @@ -386,43 +316,43 @@ "name": "set_population_engine", "kind": "function", "path": "mongo_ops.repository.PopulatingRepository.set_population_engine", - "signature": "", - "docstring": null + "signature": "set_population_engine(engine: PopulationEngine) -> None", + "docstring": "Attach (or replace) the population engine.\n\nArgs:\n engine (PopulationEngine):\n Engine used to resolve references." }, "set_populate_rules": { "name": "set_populate_rules", "kind": "function", "path": "mongo_ops.repository.PopulatingRepository.set_populate_rules", - "signature": "", - "docstring": null + "signature": "set_populate_rules(rules: list[PopulateRule]) -> None", + "docstring": "Set the FK resolution rules.\n\nArgs:\n rules (list[PopulateRule]):\n Rules describing which fields resolve and how deep." }, "data_to_model": { "name": "data_to_model", "kind": "function", "path": "mongo_ops.repository.PopulatingRepository.data_to_model", - "signature": "", - "docstring": null + "signature": "data_to_model(data: dict) -> T", + "docstring": "Convert a raw dict to a model, resolving FK references first.\n\nArgs:\n data (dict):\n Raw document dictionary.\n\nReturns:\n T: The populated model instance.\n\nRaises:\n ValueError: If a FK field holds an embedded dict instead of an\n ObjectId." }, "create": { "name": "create", "kind": "function", "path": "mongo_ops.repository.PopulatingRepository.create", - "signature": "", - "docstring": null + "signature": "create(data: T) -> T", + "docstring": "Depopulate, insert, and re-populate a new document.\n\nArgs:\n data (T):\n The model instance to insert (FK fields may hold models).\n\nReturns:\n T: The created model instance, including its ID." }, "update": { "name": "update", "kind": "function", "path": "mongo_ops.repository.PopulatingRepository.update", - "signature": "", - "docstring": null + "signature": "update(id: str | ObjectId, data: T) -> T | None", + "docstring": "Depopulate and update a document by ID.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID.\n data (T):\n The model instance holding the updated fields.\n\nReturns:\n Optional[T]: The updated model instance, or None when not found." }, "patch": { "name": "patch", "kind": "function", "path": "mongo_ops.repository.PopulatingRepository.patch", - "signature": "", - "docstring": null + "signature": "patch(id: str | ObjectId, data: dict[str, Any]) -> T | None", + "docstring": "Partially update a document, rejecting FK field changes.\n\nArgs:\n id (Union[str, ObjectId]):\n The document ID.\n data (dict[str, Any]):\n Partial dictionary of fields to update.\n\nReturns:\n Optional[T]: The updated model instance, or None when not found.\n\nRaises:\n ValueError: If any FK field is present in the patch payload." } } } diff --git a/mongo-ops/mcp/modules/mongo_ops.transactions.json b/mongo-ops/mcp/modules/mongo_ops.transactions.json index 9b7002d..2b9fa3e 100644 --- a/mongo-ops/mcp/modules/mongo_ops.transactions.json +++ b/mongo-ops/mcp/modules/mongo_ops.transactions.json @@ -2,91 +2,49 @@ "module": "mongo_ops.transactions", "content": { "path": "mongo_ops.transactions", - "docstring": "Transaction management helpers for MongoDB.", + "docstring": "# Summary\n\nTransaction management helpers for MongoDB.", "objects": { - "Awaitable": { - "name": "Awaitable", - "kind": "alias", - "path": "mongo_ops.transactions.Awaitable", - "signature": "", - "docstring": null - }, - "asynccontextmanager": { - "name": "asynccontextmanager", - "kind": "alias", - "path": "mongo_ops.transactions.asynccontextmanager", - "signature": "", - "docstring": null - }, - "Any": { - "name": "Any", - "kind": "alias", - "path": "mongo_ops.transactions.Any", - "signature": "", - "docstring": null - }, - "Callable": { - "name": "Callable", - "kind": "alias", - "path": "mongo_ops.transactions.Callable", - "signature": "", - "docstring": null - }, - "List": { - "name": "List", - "kind": "alias", - "path": "mongo_ops.transactions.List", - "signature": "", - "docstring": null - }, - "AsyncIOMotorClientSession": { - "name": "AsyncIOMotorClientSession", - "kind": "alias", - "path": "mongo_ops.transactions.AsyncIOMotorClientSession", - "signature": "", - "docstring": null - }, "MongoConnectionManager": { "name": "MongoConnectionManager", "kind": "class", "path": "mongo_ops.transactions.MongoConnectionManager", - "signature": "", - "docstring": "Manages MongoDB connections with async lifecycle.\n\nThis class provides a singleton-like manager for the MongoDB client and \ndatabase instances, ensuring they are properly initialized and closed \nacross the application lifecycle.", + "signature": null, + "docstring": "Manages MongoDB connections with async lifecycle.\n\nThis class provides a singleton-like manager for the MongoDB client and\ndatabase instances, ensuring they are properly initialized and closed\nacross the application lifecycle.", "members": { "connect": { "name": "connect", "kind": "function", "path": "mongo_ops.transactions.MongoConnectionManager.connect", - "signature": "", - "docstring": "Connect to MongoDB and initialize the shared client.\n\nArgs:\n uri: MongoDB connection URI (e.g., \"mongodb://localhost:27017\").\n db_name: Name of the database to use.\n **kwargs: Additional Motor client options (e.g., maxPoolSize).\n\nReturns:\n AsyncIOMotorDatabase: The initialized database instance." + "signature": "connect(uri: str, db_name: str, **kwargs: Any)", + "docstring": "Connect to MongoDB and initialize the shared client.\n\nArgs:\n uri (str): MongoDB connection URI (e.g., \"mongodb://localhost:27017\").\n db_name (str): Name of the database to use.\n **kwargs: Additional Motor client options (e.g., maxPoolSize).\n\nReturns:\n AsyncIOMotorDatabase: The initialized database instance." }, "disconnect": { "name": "disconnect", "kind": "function", "path": "mongo_ops.transactions.MongoConnectionManager.disconnect", - "signature": "", + "signature": "disconnect()", "docstring": "Close the active MongoDB connection and cleanup resources." }, "get_database": { "name": "get_database", "kind": "function", "path": "mongo_ops.transactions.MongoConnectionManager.get_database", - "signature": "", + "signature": "get_database()", "docstring": "Retrieve the current database instance.\n\nReturns:\n AsyncIOMotorDatabase: The active database instance.\n\nRaises:\n RuntimeError: If connect() has not been called yet." }, "get_client": { "name": "get_client", "kind": "function", "path": "mongo_ops.transactions.MongoConnectionManager.get_client", - "signature": "", + "signature": "get_client()", "docstring": "Retrieve the current client instance.\n\nReturns:\n AsyncIOMotorClient: The active Motor client instance.\n\nRaises:\n RuntimeError: If connect() has not been called yet." }, "lifespan": { "name": "lifespan", "kind": "function", "path": "mongo_ops.transactions.MongoConnectionManager.lifespan", - "signature": "", - "docstring": "Async context manager for managing connection lifecycle.\n\nDesigned for use with FastAPI or other frameworks supporting \nlifespan management.\n\nArgs:\n uri: MongoDB connection URI.\n db_name: Name of the database.\n **kwargs: Additional Motor client options.\n\nYields:\n AsyncIOMotorDatabase: The active database instance.\n\nUsage:\n @asynccontextmanager\n async def lifespan(app: FastAPI):\n async with MongoConnectionManager.lifespan(uri, db_name):\n yield" + "signature": "lifespan(uri: str, db_name: str, **kwargs: Any)", + "docstring": "Async context manager for managing connection lifecycle.\n\nDesigned for use with FastAPI or other frameworks supporting\nlifespan management.\n\nNotes:\n Connects to MongoDB on entry, yields the active database instance\n to the context body, and disconnects on exit.\n\nUsage:\n @asynccontextmanager\n async def lifespan(app: FastAPI):\n async with MongoConnectionManager.lifespan(uri, db_name):\n yield" } } }, @@ -94,22 +52,22 @@ "name": "TransactionManager", "kind": "class", "path": "mongo_ops.transactions.TransactionManager", - "signature": "", - "docstring": "Simplified multi-document transaction handling.\n\nThis class provides helpers for executing operations within a MongoDB \ntransaction, ensuring ACID compliance for multi-document updates.", + "signature": null, + "docstring": "Simplified multi-document transaction handling.\n\nThis class provides helpers for executing operations within a MongoDB\ntransaction, ensuring ACID compliance for multi-document updates.", "members": { "start_session": { "name": "start_session", "kind": "function", "path": "mongo_ops.transactions.TransactionManager.start_session", - "signature": "", - "docstring": "Start a transaction session as an async context manager.\n\nArgs:\n **kwargs: Transaction options (e.g., read_concern, write_concern).\n\nYields:\n AsyncIOMotorClientSession: The active session with a started transaction.\n\nUsage:\n async with TransactionManager.start_session() as session:\n await collection.insert_one(doc, session=session)\n await other_collection.update_one(filter, update, session=session)" + "signature": "start_session(**kwargs: Any) -> AbstractAsyncContextManager[AsyncIOMotorClientSession]", + "docstring": "Start a transaction session as an async context manager.\n\nNotes:\n Yields the active session with a started transaction; callers\n run their operations against the session inside the with-block.\n\nUsage:\n async with TransactionManager.start_session() as session:\n await collection.insert_one(doc, session=session)\n await other_collection.update_one(filter, update, session=session)" }, "execute_transaction": { "name": "execute_transaction", "kind": "function", "path": "mongo_ops.transactions.TransactionManager.execute_transaction", - "signature": "", - "docstring": "Execute multiple operations within a single transaction.\n\nArgs:\n operations: A list of async callables that accept a session \n parameter and return a result.\n **kwargs: Transaction options.\n\nReturns:\n List[Any]: A list containing the results of each operation.\n\nUsage:\n results = await TransactionManager.execute_transaction([\n lambda s: repo1.create(data1, session=s),\n lambda s: repo2.update(id, data2, session=s),\n ])" + "signature": "execute_transaction(operations: list[Callable[[AsyncIOMotorClientSession], Awaitable[Any]]], **kwargs: Any) -> list[Any]", + "docstring": "Execute multiple operations within a single transaction.\n\nArgs:\n operations (List[Callable[[AsyncIOMotorClientSession], Awaitable[Any]]]): A list of async\n callables that accept a session parameter and return a result.\n **kwargs: Transaction options.\n\nReturns:\n List[Any]: A list containing the results of each operation.\n\nExample:\n results = await TransactionManager.execute_transaction([\n lambda s: repo1.create(data1, session=s),\n lambda s: repo2.update(id, data2, session=s),\n ])" } } } diff --git a/mongo-ops/mcp/nav.json b/mongo-ops/mcp/nav.json index de447a3..501c810 100644 --- a/mongo-ops/mcp/nav.json +++ b/mongo-ops/mcp/nav.json @@ -1,62 +1,62 @@ [ { "module": "mongo_ops", - "resource": "doc://modules/mongo_ops" + "resource": "docs://modules/mongo_ops" }, { "module": "mongo_ops.cache", - "resource": "doc://modules/mongo_ops.cache" + "resource": "docs://modules/mongo_ops.cache" }, { "module": "mongo_ops.cache.backend", - "resource": "doc://modules/mongo_ops.cache.backend" + "resource": "docs://modules/mongo_ops.cache.backend" }, { "module": "mongo_ops.cache.config", - "resource": "doc://modules/mongo_ops.cache.config" + "resource": "docs://modules/mongo_ops.cache.config" }, { "module": "mongo_ops.cache.in_memory", - "resource": "doc://modules/mongo_ops.cache.in_memory" + "resource": "docs://modules/mongo_ops.cache.in_memory" }, { "module": "mongo_ops.cache.redis_backend", - "resource": "doc://modules/mongo_ops.cache.redis_backend" + "resource": "docs://modules/mongo_ops.cache.redis_backend" }, { "module": "mongo_ops.cache.repository", - "resource": "doc://modules/mongo_ops.cache.repository" + "resource": "docs://modules/mongo_ops.cache.repository" }, { "module": "mongo_ops.connection", - "resource": "doc://modules/mongo_ops.connection" + "resource": "docs://modules/mongo_ops.connection" }, { "module": "mongo_ops.models", - "resource": "doc://modules/mongo_ops.models" + "resource": "docs://modules/mongo_ops.models" }, { "module": "mongo_ops.populate", - "resource": "doc://modules/mongo_ops.populate" + "resource": "docs://modules/mongo_ops.populate" }, { "module": "mongo_ops.populate.engine", - "resource": "doc://modules/mongo_ops.populate.engine" + "resource": "docs://modules/mongo_ops.populate.engine" }, { "module": "mongo_ops.populate.rules", - "resource": "doc://modules/mongo_ops.populate.rules" + "resource": "docs://modules/mongo_ops.populate.rules" }, { "module": "mongo_ops.registry", - "resource": "doc://modules/mongo_ops.registry" + "resource": "docs://modules/mongo_ops.registry" }, { "module": "mongo_ops.repository", - "resource": "doc://modules/mongo_ops.repository" + "resource": "docs://modules/mongo_ops.repository" }, { "module": "mongo_ops.transactions", - "resource": "doc://modules/mongo_ops.transactions" + "resource": "docs://modules/mongo_ops.transactions" } ] \ No newline at end of file diff --git a/openapi-first/lib/app/index.html b/openapi-first/lib/app/index.html index a62ecff..b524ccd 100644 --- a/openapi-first/lib/app/index.html +++ b/openapi-first/lib/app/index.html @@ -761,7 +761,7 @@ that enforces OpenAPI as the single source of truth for a FastAPI service.

Python handler function. - Handlers are plain Python callables (no FastAPI decorators). - FastAPI route registration is derived exclusively from the spec. -- FastAPI's autogenerated OpenAPI schema is fully overridden. +- FastAPI's autogenerated OpenAPI schema is fully overridden.

Responsibilities:

1
@@ -924,7 +924,7 @@ handlers).

- All routes are derived from the provided OpenAPI specification, and each ``operationId`` is mapped to a Python function in the supplied routes module. -- Auth dependencies are auto-injected from the spec's +- Auth dependencies are auto-injected from the spec's ``securitySchemes`` and per-operation ``security`` fields.

Guarantees:

@@ -955,9 +955,9 @@ handlers).

import app.routes as routes app = OpenAPIFirstApp( - openapi_path="app/openapi.json", + openapi_path="app/openapi.json", routes_module=routes, - title="Example Service", + title="Example Service", ) diff --git a/openapi-first/lib/client/index.html b/openapi-first/lib/client/index.html index 5672607..9e300c4 100644 --- a/openapi-first/lib/client/index.html +++ b/openapi-first/lib/client/index.html @@ -937,16 +937,16 @@ missing/duplicate, or required parameters are missing.

15 16
from openapi_first import loader, client
 
-spec = loader.load_openapi("openapi.yaml")
+spec = loader.load_openapi("openapi.yaml")
 
 api = client.OpenAPIClient(
     spec=spec,
-    base_url="http://localhost:8000",
+    base_url="http://localhost:8000",
 )
 
 # Call operationId: getUser
 response = api.getUser(
-    path_params={"user_id": 123}
+    path_params={"user_id": 123}
 )
 
 print(response.status_code)
diff --git a/openapi-first/lib/codegen/index.html b/openapi-first/lib/codegen/index.html
index b0cde69..ccae7fb 100644
--- a/openapi-first/lib/codegen/index.html
+++ b/openapi-first/lib/codegen/index.html
@@ -762,21 +762,25 @@ definitions.

4 5 6 -7
generate_routes(
+7
+8
+9
generate_routes(
     spec_path: Path,
     output_dir: Path,
     *,
     use_models: bool = False,
-    models_module: str = "models"
-) -> list[Path]
+    models_module: str = "models",
+    inject_request: bool = False,
+    package: bool = False
+) -> list[Path]
 

Generate route handler stubs from an OpenAPI specification.

-

Creates one <resource>.py file per resource in output_dir. -Resources are derived from the first path segment (e.g. /pets -and /pets/{id} both group under pets).

+

Creates one route module per resource in output_dir. Resources are +derived from the first path segment (e.g. /pets and /pets/{id} +both group under pets) and sanitized to valid module identifiers.

Parameters:

@@ -849,6 +853,36 @@ for request-body schemas referenced via $ref.

'models' + + inject_request + + bool + + +
+

If True, prepend request: Request to every handler so the +stub can later resolve services from request.app.state.

+
+ + + False + + + + package + + bool + + +
+

If True, emit <resource>/routes.py packages (each with an +__init__.py) instead of flat <resource>.py modules.

+
+ + + False + + diff --git a/openapi-first/lib/codegen_routes/index.html b/openapi-first/lib/codegen_routes/index.html index ca4285a..42f8bb8 100644 --- a/openapi-first/lib/codegen_routes/index.html +++ b/openapi-first/lib/codegen_routes/index.html @@ -641,19 +641,39 @@ must define an operationId, which becomes the handler function name
Notes

Design constraints:

-
1
-2
-3
-4
-5
-6
-7
- ``operationId`` is required on every operation (matching
+
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
- ``operationId`` is required on every operation (matching
   ``binder.bind_routes``).
 - Handlers are stubs raising ``NotImplementedError``.
 - Sub-resources (e.g. ``/pets/{id}/photo``) are grouped with their
   parent resource (``pets``).
 - Parameter types and defaults are inferred from the spec.
 - ``response: Response`` is injected for non-200 success codes.
+- Resource names are sanitized to valid Python module identifiers,
+  so a path such as ``/fetch-requests`` yields ``fetch_requests.py``.
+- Passing ``inject_request=True`` prepends ``request: Request`` to
+  every handler (the composition-root / DI convention).
+- ``multipart/form-data`` and
+  ``application/x-www-form-urlencoded`` request bodies expand to one
+  parameter per schema property (``UploadFile`` / ``File`` / ``Form``)
+  instead of an opaque ``payload: dict``.
+- Passing ``package=True`` writes ``<resource>/routes.py`` (with
+  ``__init__.py`` sidecars) instead of a flat ``<resource>.py``.
 
@@ -682,21 +702,25 @@ must define an operationId, which becomes the handler function name 4 5 6 -7
generate_routes(
+7
+8
+9
generate_routes(
     spec_path: Path,
     output_dir: Path,
     *,
     use_models: bool = False,
-    models_module: str = "models"
-) -> list[Path]
+    models_module: str = "models",
+    inject_request: bool = False,
+    package: bool = False
+) -> list[Path]
 

Generate route handler stubs from an OpenAPI specification.

-

Creates one <resource>.py file per resource in output_dir. -Resources are derived from the first path segment (e.g. /pets -and /pets/{id} both group under pets).

+

Creates one route module per resource in output_dir. Resources are +derived from the first path segment (e.g. /pets and /pets/{id} +both group under pets) and sanitized to valid module identifiers.

Parameters:

@@ -769,6 +793,36 @@ for request-body schemas referenced via $ref.

'models' + + inject_request + + bool + + +
+

If True, prepend request: Request to every handler so the +stub can later resolve services from request.app.state.

+
+ + + False + + + + package + + bool + + +
+

If True, emit <resource>/routes.py packages (each with an +__init__.py) instead of flat <resource>.py modules.

+
+ + + False + + diff --git a/openapi-first/lib/index.html b/openapi-first/lib/index.html index f17d2b9..cfb5fc7 100644 --- a/openapi-first/lib/index.html +++ b/openapi-first/lib/index.html @@ -795,10 +795,10 @@ handlers explicitly via operationId.

import my_service.routes as routes api = app.OpenAPIFirstApp( - openapi_path="openapi.yaml", + openapi_path="openapi.yaml", routes_module=routes, - title="My Service", - version="1.0.0", + title="My Service", + version="1.0.0", )

OperationId-driven HTTP client:

@@ -811,7 +811,7 @@ handlers explicitly via operationId.

7
from openapi_first.loader import load_openapi
 from openapi_first.client import OpenAPIClient
 
-spec = load_openapi("openapi.yaml")
+spec = load_openapi("openapi.yaml")
 client = OpenAPIClient(spec)
 
 response = client.get_health()
@@ -866,7 +866,9 @@ handlers explicitly via operationId.

- + diff --git a/openapi-first/lib/openapi_first/app/index.html b/openapi-first/lib/openapi_first/app/index.html index 3c8caa9..80efb01 100644 --- a/openapi-first/lib/openapi_first/app/index.html +++ b/openapi-first/lib/openapi_first/app/index.html @@ -658,7 +658,7 @@ that enforces OpenAPI as the single source of truth for a FastAPI service.

Python handler function. - Handlers are plain Python callables (no FastAPI decorators). - FastAPI route registration is derived exclusively from the spec. -- FastAPI's autogenerated OpenAPI schema is fully overridden. +- FastAPI's autogenerated OpenAPI schema is fully overridden.

Responsibilities:

1
@@ -821,7 +821,7 @@ handlers).

- All routes are derived from the provided OpenAPI specification, and each ``operationId`` is mapped to a Python function in the supplied routes module. -- Auth dependencies are auto-injected from the spec's +- Auth dependencies are auto-injected from the spec's ``securitySchemes`` and per-operation ``security`` fields.

Guarantees:

@@ -852,9 +852,9 @@ handlers).

import app.routes as routes app = OpenAPIFirstApp( - openapi_path="app/openapi.json", + openapi_path="app/openapi.json", routes_module=routes, - title="Example Service", + title="Example Service", )
diff --git a/openapi-first/lib/openapi_first/client/index.html b/openapi-first/lib/openapi_first/client/index.html index ab07556..5b7b043 100644 --- a/openapi-first/lib/openapi_first/client/index.html +++ b/openapi-first/lib/openapi_first/client/index.html @@ -836,16 +836,16 @@ missing/duplicate, or required parameters are missing.

15 16
from openapi_first import loader, client
 
-spec = loader.load_openapi("openapi.yaml")
+spec = loader.load_openapi("openapi.yaml")
 
 api = client.OpenAPIClient(
     spec=spec,
-    base_url="http://localhost:8000",
+    base_url="http://localhost:8000",
 )
 
 # Call operationId: getUser
 response = api.getUser(
-    path_params={"user_id": 123}
+    path_params={"user_id": 123}
 )
 
 print(response.status_code)
diff --git a/openapi-first/lib/openapi_first/codegen/index.html b/openapi-first/lib/openapi_first/codegen/index.html
index 5815d6f..3f771f2 100644
--- a/openapi-first/lib/openapi_first/codegen/index.html
+++ b/openapi-first/lib/openapi_first/codegen/index.html
@@ -762,21 +762,25 @@ definitions.

4 5 6 -7
generate_routes(
+7
+8
+9
generate_routes(
     spec_path: Path,
     output_dir: Path,
     *,
     use_models: bool = False,
-    models_module: str = "models"
-) -> list[Path]
+    models_module: str = "models",
+    inject_request: bool = False,
+    package: bool = False
+) -> list[Path]
 

Generate route handler stubs from an OpenAPI specification.

-

Creates one <resource>.py file per resource in output_dir. -Resources are derived from the first path segment (e.g. /pets -and /pets/{id} both group under pets).

+

Creates one route module per resource in output_dir. Resources are +derived from the first path segment (e.g. /pets and /pets/{id} +both group under pets) and sanitized to valid module identifiers.

Parameters:

@@ -849,6 +853,36 @@ for request-body schemas referenced via $ref.

'models' + + inject_request + + bool + + +
+

If True, prepend request: Request to every handler so the +stub can later resolve services from request.app.state.

+
+ + + False + + + + package + + bool + + +
+

If True, emit <resource>/routes.py packages (each with an +__init__.py) instead of flat <resource>.py modules.

+
+ + + False + + diff --git a/openapi-first/lib/openapi_first/codegen_routes/index.html b/openapi-first/lib/openapi_first/codegen_routes/index.html index d93ac56..b803182 100644 --- a/openapi-first/lib/openapi_first/codegen_routes/index.html +++ b/openapi-first/lib/openapi_first/codegen_routes/index.html @@ -641,19 +641,39 @@ must define an operationId, which becomes the handler function name
Notes

Design constraints:

-
1
-2
-3
-4
-5
-6
-7
- ``operationId`` is required on every operation (matching
+
 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
- ``operationId`` is required on every operation (matching
   ``binder.bind_routes``).
 - Handlers are stubs raising ``NotImplementedError``.
 - Sub-resources (e.g. ``/pets/{id}/photo``) are grouped with their
   parent resource (``pets``).
 - Parameter types and defaults are inferred from the spec.
 - ``response: Response`` is injected for non-200 success codes.
+- Resource names are sanitized to valid Python module identifiers,
+  so a path such as ``/fetch-requests`` yields ``fetch_requests.py``.
+- Passing ``inject_request=True`` prepends ``request: Request`` to
+  every handler (the composition-root / DI convention).
+- ``multipart/form-data`` and
+  ``application/x-www-form-urlencoded`` request bodies expand to one
+  parameter per schema property (``UploadFile`` / ``File`` / ``Form``)
+  instead of an opaque ``payload: dict``.
+- Passing ``package=True`` writes ``<resource>/routes.py`` (with
+  ``__init__.py`` sidecars) instead of a flat ``<resource>.py``.
 
@@ -682,21 +702,25 @@ must define an operationId, which becomes the handler function name 4 5 6 -7
generate_routes(
+7
+8
+9
generate_routes(
     spec_path: Path,
     output_dir: Path,
     *,
     use_models: bool = False,
-    models_module: str = "models"
-) -> list[Path]
+    models_module: str = "models",
+    inject_request: bool = False,
+    package: bool = False
+) -> list[Path]
 

Generate route handler stubs from an OpenAPI specification.

-

Creates one <resource>.py file per resource in output_dir. -Resources are derived from the first path segment (e.g. /pets -and /pets/{id} both group under pets).

+

Creates one route module per resource in output_dir. Resources are +derived from the first path segment (e.g. /pets and /pets/{id} +both group under pets) and sanitized to valid module identifiers.

Parameters:

@@ -769,6 +793,36 @@ for request-body schemas referenced via $ref.

'models' + + inject_request + + bool + + +
+

If True, prepend request: Request to every handler so the +stub can later resolve services from request.app.state.

+
+ + + False + + + + package + + bool + + +
+

If True, emit <resource>/routes.py packages (each with an +__init__.py) instead of flat <resource>.py modules.

+
+ + + False + + diff --git a/openapi-first/lib/openapi_first/index.html b/openapi-first/lib/openapi_first/index.html index ec5d230..00a5134 100644 --- a/openapi-first/lib/openapi_first/index.html +++ b/openapi-first/lib/openapi_first/index.html @@ -682,10 +682,10 @@ handlers explicitly via operationId.

import my_service.routes as routes api = app.OpenAPIFirstApp( - openapi_path="openapi.yaml", + openapi_path="openapi.yaml", routes_module=routes, - title="My Service", - version="1.0.0", + title="My Service", + version="1.0.0", )

OperationId-driven HTTP client:

@@ -698,7 +698,7 @@ handlers explicitly via operationId.

7
from openapi_first.loader import load_openapi
 from openapi_first.client import OpenAPIClient
 
-spec = load_openapi("openapi.yaml")
+spec = load_openapi("openapi.yaml")
 client = OpenAPIClient(spec)
 
 response = client.get_health()
diff --git a/openapi-first/lib/openapi_first/templates/crud_app/index.html b/openapi-first/lib/openapi_first/templates/crud_app/index.html
index 37b4dcf..c30b325 100644
--- a/openapi-first/lib/openapi_first/templates/crud_app/index.html
+++ b/openapi-first/lib/openapi_first/templates/crud_app/index.html
@@ -688,7 +688,7 @@ construct a strict, operationId-driven HTTP client.

29
from openapi_first.loader import load_openapi
 from openapi_first.client import OpenAPIClient
 
-spec = load_openapi("openapi.yaml")
+spec = load_openapi("openapi.yaml")
 client = OpenAPIClient(spec)
 
 # List items
@@ -696,23 +696,23 @@ response = client.list_items()
 
 # Get item by ID
 response = client.get_item(
-    path_params={"item_id": 1}
+    path_params={"item_id": 1}
 )
 
 # Create item
 response = client.create_item(
-    body={"name": "Orange", "price": 0.8}
+    body={"name": "Orange", "price": 0.8}
 )
 
 # Update item
 response = client.update_item(
-    path_params={"item_id": 1},
-    body={"name": "Green Apple", "price": 0.6},
+    path_params={"item_id": 1},
+    body={"name": "Green Apple", "price": 0.6},
 )
 
 # Delete item
 response = client.delete_item(
-    path_params={"item_id": 1}
+    path_params={"item_id": 1}
 )
 

Client guarantees: diff --git a/openapi-first/lib/openapi_first/templates/health_app/index.html b/openapi-first/lib/openapi_first/templates/health_app/index.html index 11a222d..d009356 100644 --- a/openapi-first/lib/openapi_first/templates/health_app/index.html +++ b/openapi-first/lib/openapi_first/templates/health_app/index.html @@ -659,13 +659,13 @@ construct a strict, operationId-driven HTTP client.

10
from openapi_first.loader import load_openapi
 from openapi_first.client import OpenAPIClient
 
-spec = load_openapi("openapi.yaml")
+spec = load_openapi("openapi.yaml")
 client = OpenAPIClient(spec)
 
 response = client.get_health()
 
 assert response.status_code == 200
-assert response.json() == {"status": "ok"}
+assert response.json() == {"status": "ok"}
 

Client guarantees: - One callable per OpenAPI operationId diff --git a/openapi-first/lib/openapi_first/templates/model_app/index.html b/openapi-first/lib/openapi_first/templates/model_app/index.html index b495e25..6b44881 100644 --- a/openapi-first/lib/openapi_first/templates/model_app/index.html +++ b/openapi-first/lib/openapi_first/templates/model_app/index.html @@ -691,7 +691,7 @@ construct a strict, operationId-driven HTTP client.

29
from openapi_first.loader import load_openapi
 from openapi_first.client import OpenAPIClient
 
-spec = load_openapi("openapi.yaml")
+spec = load_openapi("openapi.yaml")
 client = OpenAPIClient(spec)
 
 # List items
@@ -699,23 +699,23 @@ response = client.list_items()
 
 # Get item by ID
 response = client.get_item(
-    path_params={"item_id": 1}
+    path_params={"item_id": 1}
 )
 
 # Create item
 response = client.create_item(
-    body={"name": "Orange", "price": 0.8}
+    body={"name": "Orange", "price": 0.8}
 )
 
 # Update item
 response = client.update_item(
-    path_params={"item_id": 1},
-    body={"name": "Green Apple", "price": 0.6},
+    path_params={"item_id": 1},
+    body={"name": "Green Apple", "price": 0.6},
 )
 
 # Delete item
 response = client.delete_item(
-    path_params={"item_id": 1}
+    path_params={"item_id": 1}
 )
 

Client guarantees: diff --git a/openapi-first/lib/openapi_first/templates/vet_app/index.html b/openapi-first/lib/openapi_first/templates/vet_app/index.html index 9eb7f5e..d437baa 100644 --- a/openapi-first/lib/openapi_first/templates/vet_app/index.html +++ b/openapi-first/lib/openapi_first/templates/vet_app/index.html @@ -699,21 +699,21 @@ library API surface.

19
from openapi_first.loader import load_openapi
 from openapi_first.client import OpenAPIClient
 
-spec = load_openapi("openapi.yaml")
+spec = load_openapi("openapi.yaml")
 client = OpenAPIClient(spec)
 
 # List pets with pagination
-response = client.list_pets(query_params={"limit": 10, "offset": 0})
+response = client.list_pets(query_params={"limit": 10, "offset": 0})
 
 # Create a pet with FK references
 response = client.create_pet(
-    body={"name": "Fido", "species": "dog", "parents": [1, 2]}
+    body={"name": "Fido", "species": "dog", "parents": [1, 2]}
 )
 
 # Upload a pet photo
 response = client.upload_pet_photo(
-    path_params={"id": 1},
-    body={"file": open("photo.jpg", "rb")},
+    path_params={"id": 1},
+    body={"file": open("photo.jpg", "rb")},
 )
 

diff --git a/openapi-first/lib/search/search_index.json b/openapi-first/lib/search/search_index.json index 4920db0..bc3a675 100644 --- a/openapi-first/lib/search/search_index.json +++ b/openapi-first/lib/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"openapi_first","text":""},{"location":"#openapi_first","title":"openapi_first","text":""},{"location":"#openapi_first--summary","title":"Summary","text":"

FastAPI OpenAPI First \u2014 strict OpenAPI-first application bootstrap for FastAPI.

FastAPI OpenAPI First is a contract-first infrastructure library that enforces OpenAPI as the single source of truth for FastAPI services.

The library removes decorator-driven routing and replaces it with deterministic, spec-driven application assembly. Every HTTP route, method, and operation is defined in OpenAPI first and bound to Python handlers explicitly via operationId.

"},{"location":"#openapi_first--installation","title":"Installation","text":"

Install using pip:

pip install openapi-first\n

Or with Poetry:

poetry add openapi-first\n
"},{"location":"#openapi_first--quick-start","title":"Quick Start","text":"

Minimal OpenAPI-first FastAPI application:

from openapi_first import app\nimport my_service.routes as routes\n\napi = app.OpenAPIFirstApp(\n    openapi_path=\"openapi.yaml\",\n    routes_module=routes,\n    title=\"My Service\",\n    version=\"1.0.0\",\n)\n

OperationId-driven HTTP client:

from openapi_first.loader import load_openapi\nfrom openapi_first.client import OpenAPIClient\n\nspec = load_openapi(\"openapi.yaml\")\nclient = OpenAPIClient(spec)\n\nresponse = client.get_health()\n
"},{"location":"#openapi_first--architecture","title":"Architecture","text":"

The library is structured around four core responsibilities:

  • loader: Load and validate OpenAPI 3.x specifications (JSON/YAML).
  • binder: Bind OpenAPI operations to FastAPI routes via operationId.
  • app: OpenAPI-first FastAPI application bootstrap.
  • client: OpenAPI-first HTTP client driven by the same specification.
  • errors: Explicit error hierarchy for contract violations.
"},{"location":"#openapi_first--public-api","title":"Public API","text":"

The supported public API consists of the following top-level modules:

  • openapi_first.app
  • openapi_first.binder
  • openapi_first.loader
  • openapi_first.client
  • openapi_first.errors
"},{"location":"#openapi_first--design-guarantees","title":"Design Guarantees","text":"
  • OpenAPI is the single source of truth.
  • No undocumented routes can exist.
  • No OpenAPI operation can exist without a handler or client callable.
  • All contract violations fail at application startup or client creation.
  • No hidden FastAPI magic or implicit behavior.
  • Deterministic, testable application assembly.
"},{"location":"app/","title":"App","text":""},{"location":"app/#openapi_first.app","title":"openapi_first.app","text":""},{"location":"app/#openapi_first.app--summary","title":"Summary","text":"

OpenAPI-first application bootstrap for FastAPI.

This module provides OpenAPIFirstApp, a thin but strict abstraction that enforces OpenAPI as the single source of truth for a FastAPI service.

Notes

Core Principles:

- The OpenAPI specification (JSON or YAML) defines the entire API surface.\n- Every `operationId` in the OpenAPI spec must have a corresponding\n  Python handler function.\n- Handlers are plain Python callables (no FastAPI decorators).\n- FastAPI route registration is derived exclusively from the spec.\n- FastAPI's autogenerated OpenAPI schema is fully overridden.\n

Responsibilities:

- Loads and validates an OpenAPI 3.x specification.\n- Dynamically binds HTTP routes to handler functions using `operationId`.\n- Registers routes with FastAPI at application startup.\n- Ensures runtime behavior matches the OpenAPI contract exactly.\n

Constraints:

- This module intentionally does NOT:\n    - Generate OpenAPI specs.\n    - Generate client code.\n    - Introduce a new framework or lifecycle.\n    - Alter FastAPI dependency injection semantics.\n
"},{"location":"app/#openapi_first.app-classes","title":"Classes","text":""},{"location":"app/#openapi_first.app.OpenAPIFirstApp","title":"OpenAPIFirstApp","text":"
OpenAPIFirstApp(\n    *,\n    openapi_path: str,\n    routes_module: Any,\n    **fastapi_kwargs: Any\n)\n

Bases: FastAPI

FastAPI application enforcing OpenAPI-first design.

Parameters:

Name Type Description Default openapi_path str

Filesystem path to the OpenAPI 3.x specification file. This specification is treated as the authoritative API contract.

required routes_module ModuleType

Python module containing handler functions whose names correspond exactly to OpenAPI operationId values.

required **fastapi_kwargs Any

Additional keyword arguments passed directly to fastapi.FastAPI (e.g., title, version, middleware, lifespan handlers).

{}

Raises:

Type Description OpenAPIFirstError

If the OpenAPI specification is invalid, or if any declared operationId does not have a corresponding handler function.

Notes

Responsibilities:

- `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n  registration with OpenAPI-driven binding.\n- All routes are derived from the provided OpenAPI specification,\n  and each ``operationId`` is mapped to a Python function in the\n  supplied routes module.\n- Auth dependencies are auto-injected from the spec's\n  ``securitySchemes`` and per-operation ``security`` fields.\n

Guarantees:

- No route can exist without an OpenAPI declaration.\n- No OpenAPI operation can exist without a handler.\n- Swagger UI and ``/openapi.json`` always reflect the provided spec.\n- Handler functions remain framework-agnostic and testable.\n- Auth enforcement is driven entirely by the spec \u2014 no manual\n  middleware or decorators required.\n
Example
from openapi_first.app import OpenAPIFirstApp\nimport app.routes as routes\n\napp = OpenAPIFirstApp(\n    openapi_path=\"app/openapi.json\",\n    routes_module=routes,\n    title=\"Example Service\",\n)\n
"},{"location":"app/#openapi_first.app-functions","title":"Functions","text":""},{"location":"binder/","title":"Binder","text":""},{"location":"binder/#openapi_first.binder","title":"openapi_first.binder","text":""},{"location":"binder/#openapi_first.binder--summary","title":"Summary","text":"

OpenAPI-driven route binding for FastAPI.

This module is responsible for translating an OpenAPI 3.x specification into concrete FastAPI routes. It enforces a strict one-to-one mapping between OpenAPI operations and Python handler functions using operationId.

Notes

Core Responsibility:

- Read path + method definitions from an OpenAPI specification.\n- Resolve each `operationId` to a Python callable.\n- Register routes with FastAPI using `APIRoute`.\n- Fail fast when contract violations are detected.\n

Design Constraints:

- All routes MUST be declared in the OpenAPI specification.\n- All OpenAPI operations MUST define an `operationId`.\n- Every `operationId` MUST resolve to a handler function.\n- Handlers are plain Python callables (no decorators required).\n- No implicit route creation or inference is allowed.\n

Constraints:

- This module intentionally does NOT:\n    - Perform request or response validation.\n    - Generate Pydantic models.\n    - Modify FastAPI dependency injection.\n    - Interpret OpenAPI semantics beyond routing metadata.\n
"},{"location":"binder/#openapi_first.binder-classes","title":"Classes","text":""},{"location":"binder/#openapi_first.binder-functions","title":"Functions","text":""},{"location":"binder/#openapi_first.binder.bind_routes","title":"bind_routes","text":"
bind_routes(\n    app: FastAPI,\n    spec: dict[str, Any],\n    routes_module: ModuleType,\n    security_deps: dict[str, list[Any]] | None = None,\n) -> None\n

Bind OpenAPI operations to FastAPI routes.

Parameters:

Name Type Description Default app FastAPI

The FastAPI application instance to which routes will be added.

required spec dict[str, Any]

Parsed OpenAPI 3.x specification dictionary.

required routes_module ModuleType

Python module containing handler functions. Each handler's name MUST exactly match an OpenAPI operationId.

required security_deps dict[str, list[Any]] | None

Optional mapping of METHOD:/path \u2192 list[Depends(...)] generated from the spec's securitySchemes and per-operation security fields.

None

Raises:

Type Description MissingOperationHandler

If an operationId is missing from the spec or if no corresponding handler function exists in the routes module.

Notes

Responsibilities:

- Iterates through the OpenAPI specification paths and methods.\n- Resolves each ``operationId`` to a handler function, and registers\n  a corresponding ``APIRoute`` on the FastAPI application.\n- Injects FastAPI ``Depends()`` for each security requirement found\n  on the operation or inherited from the top-level ``security`` field.\n

Guarantees:

- Route registration is deterministic and spec-driven. No route\n  decorators are required or supported. Handler resolution errors\n  surface at application startup.\n
"},{"location":"cli/","title":"Cli","text":""},{"location":"cli/#openapi_first.cli","title":"openapi_first.cli","text":""},{"location":"cli/#openapi_first.cli--summary","title":"Summary","text":"

Command-line interface for FastAPI OpenAPI-first scaffolding utilities.

This CLI bootstraps OpenAPI-first FastAPI applications from versioned, bundled templates packaged with the library.

"},{"location":"cli/#openapi_first.cli-functions","title":"Functions","text":""},{"location":"cli/#openapi_first.cli.available_templates","title":"available_templates","text":"
available_templates() -> list[str]\n

Return a list of available application templates.

Returns:

Type Description list[str]

list[str]: Sorted list of template names found in the internal templates directory.

"},{"location":"cli/#openapi_first.cli.copy_template","title":"copy_template","text":"
copy_template(template: str, target_dir: Path) -> None\n

Copy a bundled OpenAPI-first application template into a directory.

Parameters:

Name Type Description Default template str

Name of the template to copy.

required target_dir Path

Filesystem path where the template should be copied.

required

Raises:

Type Description FileNotFoundError

If the requested template does not exist.

"},{"location":"client/","title":"Client","text":""},{"location":"client/#openapi_first.client","title":"openapi_first.client","text":""},{"location":"client/#openapi_first.client--summary","title":"Summary","text":"

OpenAPI-first HTTP client for contract-driven services.

This module provides OpenAPIClient, a thin, strict HTTP client that derives all callable operations directly from an OpenAPI 3.x specification.

It is the client counterpart to OpenAPIFirstApp.

Notes

Core Principles:

- The OpenAPI specification is the single source of truth\n- Each operationId becomes a callable Python method\n- No implicit schema mutation or inference\n- No code generation step\n- Minimal abstraction over httpx\n

Responsibilities:

- Parses an OpenAPI 3.x specification\n- Dynamically creates one callable per operationId\n- Enforces presence of servers, paths, and operationId\n- Formats path parameters safely\n- Handles JSON request bodies explicitly\n- Returns raw `httpx.Response` objects\n

Constraints:

- This module intentionally does NOT: Generate client code, validate request/response schemas, deserialize responses, retry requests, implement authentication helpers, or assume non-2xx responses are failures.\n
"},{"location":"client/#openapi_first.client-classes","title":"Classes","text":""},{"location":"client/#openapi_first.client.OpenAPIClient","title":"OpenAPIClient","text":"
OpenAPIClient(\n    spec: dict[str, Any],\n    base_url: str | None = None,\n    client: httpx.Client | None = None,\n)\n

OpenAPI-first HTTP client (httpx-based).

Parameters:

Name Type Description Default spec dict[str, Any]

Parsed OpenAPI 3.x specification.

required base_url str | None

Base URL of the target service. If omitted, the first entry in the OpenAPI servers list is used.

None client Client | None

Optional preconfigured httpx client instance.

None

Raises:

Type Description OpenAPIClientError

If no servers are defined, spec has no paths, operationIds are missing/duplicate, or required parameters are missing.

Notes

Responsibilities:

- This client derives all callable methods directly from an\n  OpenAPI 3.x specification. Each `operationId` becomes a method\n  on the client instance.\n

Guarantees:

- One callable per `operationId`.\n- Explicit parameters (path, query, headers, body).\n- No implicit schema inference or mutation.\n- Returns raw `httpx.Response` objects.\n- No response validation or deserialization.\n
Example
from openapi_first import loader, client\n\nspec = loader.load_openapi(\"openapi.yaml\")\n\napi = client.OpenAPIClient(\n    spec=spec,\n    base_url=\"http://localhost:8000\",\n)\n\n# Call operationId: getUser\nresponse = api.getUser(\n    path_params={\"user_id\": 123}\n)\n\nprint(response.status_code)\nprint(response.json())\n
"},{"location":"client/#openapi_first.client.OpenAPIClientError","title":"OpenAPIClientError","text":"

Bases: OpenAPIFirstError

Raised when an OpenAPI client operation fails.

"},{"location":"codegen/","title":"Codegen","text":""},{"location":"codegen/#openapi_first.codegen","title":"openapi_first.codegen","text":""},{"location":"codegen/#openapi_first.codegen--summary","title":"Summary","text":"

Core logic for generating Python source code from OpenAPI specifications.

This module provides reusable utilities for code generation, specifically generating Pydantic models and route handler stubs from OpenAPI 3.x schema definitions.

"},{"location":"codegen/#openapi_first.codegen-functions","title":"Functions","text":""},{"location":"codegen/#openapi_first.codegen.generate_models","title":"generate_models","text":"
generate_models(\n    spec_path: Path,\n    output_path: Path,\n    pydantic_version: int = 2,\n) -> None\n

Generate Pydantic models from an OpenAPI specification.

Parameters:

Name Type Description Default spec_path Path

Path to the OpenAPI specification file (YAML or JSON).

required output_path Path

Path where the generated Python code should be written.

required pydantic_version int

The Pydantic version to target (1 or 2). Defaults to 2.

2 Notes

Reusability: This function is designed to be used by the CLI and can be exposed as an MCP tool without modification.

"},{"location":"codegen/#openapi_first.codegen.generate_routes","title":"generate_routes","text":"
generate_routes(\n    spec_path: Path,\n    output_dir: Path,\n    *,\n    use_models: bool = False,\n    models_module: str = \"models\"\n) -> list[Path]\n

Generate route handler stubs from an OpenAPI specification.

Creates one <resource>.py file per resource in output_dir. Resources are derived from the first path segment (e.g. /pets and /pets/{id} both group under pets).

Parameters:

Name Type Description Default spec_path Path

Path to the OpenAPI specification file (YAML or JSON).

required output_dir Path

Directory where the generated route files are written. Created automatically if it does not exist.

required use_models bool

If True, import Pydantic models from models_module for request-body schemas referenced via $ref.

False models_module str

Dotted Python module path from which to import models (e.g. \"models\", \"app.models\").

'models'

Returns:

Type Description list[Path]

list[Path]: Absolute paths of every generated route file.

Raises:

Type Description OpenAPISpecLoadError

If the spec cannot be loaded or validated.

ValueError

If any operation is missing operationId.

"},{"location":"codegen_routes/","title":"Codegen Routes","text":""},{"location":"codegen_routes/#openapi_first.codegen_routes","title":"openapi_first.codegen_routes","text":""},{"location":"codegen_routes/#openapi_first.codegen_routes--summary","title":"Summary","text":"

Route handler code generation from OpenAPI specifications.

This module generates Python route handler stubs from an OpenAPI 3.x specification. Each resource (derived from the first path segment) gets its own file under the output directory. Every OpenAPI operation must define an operationId, which becomes the handler function name.

Notes

Design constraints:

- ``operationId`` is required on every operation (matching\n  ``binder.bind_routes``).\n- Handlers are stubs raising ``NotImplementedError``.\n- Sub-resources (e.g. ``/pets/{id}/photo``) are grouped with their\n  parent resource (``pets``).\n- Parameter types and defaults are inferred from the spec.\n- ``response: Response`` is injected for non-200 success codes.\n
"},{"location":"codegen_routes/#openapi_first.codegen_routes-functions","title":"Functions","text":""},{"location":"codegen_routes/#openapi_first.codegen_routes.generate_routes","title":"generate_routes","text":"
generate_routes(\n    spec_path: Path,\n    output_dir: Path,\n    *,\n    use_models: bool = False,\n    models_module: str = \"models\"\n) -> list[Path]\n

Generate route handler stubs from an OpenAPI specification.

Creates one <resource>.py file per resource in output_dir. Resources are derived from the first path segment (e.g. /pets and /pets/{id} both group under pets).

Parameters:

Name Type Description Default spec_path Path

Path to the OpenAPI specification file (YAML or JSON).

required output_dir Path

Directory where the generated route files are written. Created automatically if it does not exist.

required use_models bool

If True, import Pydantic models from models_module for request-body schemas referenced via $ref.

False models_module str

Dotted Python module path from which to import models (e.g. \"models\", \"app.models\").

'models'

Returns:

Type Description list[Path]

list[Path]: Absolute paths of every generated route file.

Raises:

Type Description OpenAPISpecLoadError

If the spec cannot be loaded or validated.

ValueError

If any operation is missing operationId.

"},{"location":"errors/","title":"Errors","text":""},{"location":"errors/#openapi_first.errors","title":"openapi_first.errors","text":""},{"location":"errors/#openapi_first.errors--summary","title":"Summary","text":"

Exceptions for OpenAPI-first FastAPI applications.

This module defines a small hierarchy of explicit, intention-revealing exceptions used to signal contract violations between an OpenAPI specification and its Python implementation.

Notes

Design Principles:

- Errors represent programmer mistakes, not runtime conditions.\n- All errors are raised during application startup.\n- Messages are actionable and suitable for CI/CD output.\n- Exceptions are explicit rather than reused from generic built-ins.\n

These errors should normally cause immediate application failure.

"},{"location":"errors/#openapi_first.errors-classes","title":"Classes","text":""},{"location":"errors/#openapi_first.errors.MissingOperationHandler","title":"MissingOperationHandler","text":"
MissingOperationHandler(\n    *,\n    path: str,\n    method: str,\n    operation_id: str | None = None\n)\n

Bases: OpenAPIFirstError

Raised when an OpenAPI operation cannot be resolved to a handler.

Parameters:

Name Type Description Default path str

The HTTP path declared in the OpenAPI specification.

required method str

The HTTP method (as declared in the OpenAPI spec).

required operation_id str | None

The operationId declared in the OpenAPI spec, if present.

None Notes

Scenarios:

- An OpenAPI operation does not define an `operationId`.\n- An `operationId` is defined but no matching function exists in\n  the provided routes module.\n

Guarantees:

- This represents a violation of the OpenAPI-first contract and\n  indicates that the specification and implementation are out of\n  sync.\n
"},{"location":"errors/#openapi_first.errors.OpenAPIFirstError","title":"OpenAPIFirstError","text":"

Bases: Exception

Base exception for all OpenAPI-first enforcement errors.

Notes

Responsibilities:

- This exception exists to allow callers, test suites, and CI\n  pipelines to catch and distinguish OpenAPI contract violations\n  from unrelated runtime errors.\n- All exceptions raised by the OpenAPI-first core should inherit\n  from this type.\n
"},{"location":"loader/","title":"Loader","text":""},{"location":"loader/#openapi_first.loader","title":"openapi_first.loader","text":""},{"location":"loader/#openapi_first.loader--summary","title":"Summary","text":"

OpenAPI specification loading and validation utilities.

This module is responsible for loading an OpenAPI 3.x specification from disk and validating it before it is used by the application.

It enforces the principle that an invalid or malformed OpenAPI document must never reach the routing or runtime layers.

Notes

Design Principles:

- OpenAPI is treated as an authoritative contract.\n- Invalid specifications fail fast at application startup.\n- Supported formats are JSON and YAML.\n- Validation errors are surfaced clearly and early.\n

Constraints:

- This module intentionally does NOT:\n    - Modify the OpenAPI document.\n    - Infer missing fields.\n    - Generate models or code.\n    - Perform request/response validation at runtime.\n
"},{"location":"loader/#openapi_first.loader-classes","title":"Classes","text":""},{"location":"loader/#openapi_first.loader.OpenAPISpecLoadError","title":"OpenAPISpecLoadError","text":"

Bases: OpenAPIFirstError

Raised when an OpenAPI specification cannot be loaded or validated.

Notes

Guarantees:

- This error indicates that the OpenAPI document is unreadable,\n  malformed, or violates the OpenAPI 3.x specification.\n
"},{"location":"loader/#openapi_first.loader-functions","title":"Functions","text":""},{"location":"loader/#openapi_first.loader.load_openapi","title":"load_openapi","text":"
load_openapi(path: str | Path) -> dict[str, Any]\n

Load and validate an OpenAPI 3.x specification from disk.

Parameters:

Name Type Description Default path str | Path

Filesystem path to an OpenAPI specification file. Supported extensions: .json, .yaml, .yml.

required

Returns:

Type Description dict[str, Any]

dict[str, Any]: Parsed and validated OpenAPI specification.

Raises:

Type Description OpenAPISpecLoadError

If the file does not exist, cannot be parsed, or fails OpenAPI schema validation.

Notes

Guarantees:

- The specification is parsed based on file extension and validated\n  using a strict OpenAPI schema validator.\n- Any error results in an immediate exception, preventing\n  application startup.\n
"},{"location":"security/","title":"Security","text":""},{"location":"security/#openapi_first.security","title":"openapi_first.security","text":""},{"location":"security/#openapi_first.security--summary","title":"Summary","text":"

OpenAPI security scheme parsing and auto-generated auth dependencies.

This module reads securitySchemes and per-operation security from an OpenAPI spec, resolves {ENV_VAR} placeholders in x- extension fields, and generates FastAPI dependencies for token validation (e.g., Bearer JWT introspection).

"},{"location":"security/#openapi_first.security-functions","title":"Functions","text":""},{"location":"security/#openapi_first.security.make_security_dependencies","title":"make_security_dependencies","text":"
make_security_dependencies(\n    spec: dict[str, Any],\n    security_schemes: dict[str, dict[str, Any]],\n) -> dict[str, list[Any]]\n

Build a mapping of METHOD:/path \u2192 list of Depends(...).

The effective security for each operation is resolved by:

  1. Using the operation-level security field if present.
  2. Falling back to the top-level security field.
  3. An empty list means no auth required for that operation.
"},{"location":"security/#openapi_first.security.parse_security_schemes","title":"parse_security_schemes","text":"
parse_security_schemes(\n    spec: dict[str, Any],\n) -> dict[str, dict[str, Any]]\n

Extract and resolve environment variables in security schemes.

"},{"location":"openapi_first/","title":"Openapi First","text":"
  • App
  • Binder
  • Cli
  • Client
  • Codegen
  • Codegen Routes
  • Errors
  • Loader
  • Security
  • Templates
"},{"location":"openapi_first/#openapi_first","title":"openapi_first","text":""},{"location":"openapi_first/#openapi_first--summary","title":"Summary","text":"

FastAPI OpenAPI First \u2014 strict OpenAPI-first application bootstrap for FastAPI.

FastAPI OpenAPI First is a contract-first infrastructure library that enforces OpenAPI as the single source of truth for FastAPI services.

The library removes decorator-driven routing and replaces it with deterministic, spec-driven application assembly. Every HTTP route, method, and operation is defined in OpenAPI first and bound to Python handlers explicitly via operationId.

"},{"location":"openapi_first/#openapi_first--installation","title":"Installation","text":"

Install using pip:

pip install openapi-first\n

Or with Poetry:

poetry add openapi-first\n
"},{"location":"openapi_first/#openapi_first--quick-start","title":"Quick Start","text":"

Minimal OpenAPI-first FastAPI application:

from openapi_first import app\nimport my_service.routes as routes\n\napi = app.OpenAPIFirstApp(\n    openapi_path=\"openapi.yaml\",\n    routes_module=routes,\n    title=\"My Service\",\n    version=\"1.0.0\",\n)\n

OperationId-driven HTTP client:

from openapi_first.loader import load_openapi\nfrom openapi_first.client import OpenAPIClient\n\nspec = load_openapi(\"openapi.yaml\")\nclient = OpenAPIClient(spec)\n\nresponse = client.get_health()\n
"},{"location":"openapi_first/#openapi_first--architecture","title":"Architecture","text":"

The library is structured around four core responsibilities:

  • loader: Load and validate OpenAPI 3.x specifications (JSON/YAML).
  • binder: Bind OpenAPI operations to FastAPI routes via operationId.
  • app: OpenAPI-first FastAPI application bootstrap.
  • client: OpenAPI-first HTTP client driven by the same specification.
  • errors: Explicit error hierarchy for contract violations.
"},{"location":"openapi_first/#openapi_first--public-api","title":"Public API","text":"

The supported public API consists of the following top-level modules:

  • openapi_first.app
  • openapi_first.binder
  • openapi_first.loader
  • openapi_first.client
  • openapi_first.errors
"},{"location":"openapi_first/#openapi_first--design-guarantees","title":"Design Guarantees","text":"
  • OpenAPI is the single source of truth.
  • No undocumented routes can exist.
  • No OpenAPI operation can exist without a handler or client callable.
  • All contract violations fail at application startup or client creation.
  • No hidden FastAPI magic or implicit behavior.
  • Deterministic, testable application assembly.
"},{"location":"openapi_first/app/","title":"App","text":""},{"location":"openapi_first/app/#openapi_first.app","title":"openapi_first.app","text":""},{"location":"openapi_first/app/#openapi_first.app--summary","title":"Summary","text":"

OpenAPI-first application bootstrap for FastAPI.

This module provides OpenAPIFirstApp, a thin but strict abstraction that enforces OpenAPI as the single source of truth for a FastAPI service.

Notes

Core Principles:

- The OpenAPI specification (JSON or YAML) defines the entire API surface.\n- Every `operationId` in the OpenAPI spec must have a corresponding\n  Python handler function.\n- Handlers are plain Python callables (no FastAPI decorators).\n- FastAPI route registration is derived exclusively from the spec.\n- FastAPI's autogenerated OpenAPI schema is fully overridden.\n

Responsibilities:

- Loads and validates an OpenAPI 3.x specification.\n- Dynamically binds HTTP routes to handler functions using `operationId`.\n- Registers routes with FastAPI at application startup.\n- Ensures runtime behavior matches the OpenAPI contract exactly.\n

Constraints:

- This module intentionally does NOT:\n    - Generate OpenAPI specs.\n    - Generate client code.\n    - Introduce a new framework or lifecycle.\n    - Alter FastAPI dependency injection semantics.\n
"},{"location":"openapi_first/app/#openapi_first.app-classes","title":"Classes","text":""},{"location":"openapi_first/app/#openapi_first.app.OpenAPIFirstApp","title":"OpenAPIFirstApp","text":"
OpenAPIFirstApp(\n    *,\n    openapi_path: str,\n    routes_module: Any,\n    **fastapi_kwargs: Any\n)\n

Bases: FastAPI

FastAPI application enforcing OpenAPI-first design.

Parameters:

Name Type Description Default openapi_path str

Filesystem path to the OpenAPI 3.x specification file. This specification is treated as the authoritative API contract.

required routes_module ModuleType

Python module containing handler functions whose names correspond exactly to OpenAPI operationId values.

required **fastapi_kwargs Any

Additional keyword arguments passed directly to fastapi.FastAPI (e.g., title, version, middleware, lifespan handlers).

{}

Raises:

Type Description OpenAPIFirstError

If the OpenAPI specification is invalid, or if any declared operationId does not have a corresponding handler function.

Notes

Responsibilities:

- `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n  registration with OpenAPI-driven binding.\n- All routes are derived from the provided OpenAPI specification,\n  and each ``operationId`` is mapped to a Python function in the\n  supplied routes module.\n- Auth dependencies are auto-injected from the spec's\n  ``securitySchemes`` and per-operation ``security`` fields.\n

Guarantees:

- No route can exist without an OpenAPI declaration.\n- No OpenAPI operation can exist without a handler.\n- Swagger UI and ``/openapi.json`` always reflect the provided spec.\n- Handler functions remain framework-agnostic and testable.\n- Auth enforcement is driven entirely by the spec \u2014 no manual\n  middleware or decorators required.\n
Example
from openapi_first.app import OpenAPIFirstApp\nimport app.routes as routes\n\napp = OpenAPIFirstApp(\n    openapi_path=\"app/openapi.json\",\n    routes_module=routes,\n    title=\"Example Service\",\n)\n
"},{"location":"openapi_first/app/#openapi_first.app-functions","title":"Functions","text":""},{"location":"openapi_first/binder/","title":"Binder","text":""},{"location":"openapi_first/binder/#openapi_first.binder","title":"openapi_first.binder","text":""},{"location":"openapi_first/binder/#openapi_first.binder--summary","title":"Summary","text":"

OpenAPI-driven route binding for FastAPI.

This module is responsible for translating an OpenAPI 3.x specification into concrete FastAPI routes. It enforces a strict one-to-one mapping between OpenAPI operations and Python handler functions using operationId.

Notes

Core Responsibility:

- Read path + method definitions from an OpenAPI specification.\n- Resolve each `operationId` to a Python callable.\n- Register routes with FastAPI using `APIRoute`.\n- Fail fast when contract violations are detected.\n

Design Constraints:

- All routes MUST be declared in the OpenAPI specification.\n- All OpenAPI operations MUST define an `operationId`.\n- Every `operationId` MUST resolve to a handler function.\n- Handlers are plain Python callables (no decorators required).\n- No implicit route creation or inference is allowed.\n

Constraints:

- This module intentionally does NOT:\n    - Perform request or response validation.\n    - Generate Pydantic models.\n    - Modify FastAPI dependency injection.\n    - Interpret OpenAPI semantics beyond routing metadata.\n
"},{"location":"openapi_first/binder/#openapi_first.binder-classes","title":"Classes","text":""},{"location":"openapi_first/binder/#openapi_first.binder-functions","title":"Functions","text":""},{"location":"openapi_first/binder/#openapi_first.binder.bind_routes","title":"bind_routes","text":"
bind_routes(\n    app: FastAPI,\n    spec: dict[str, Any],\n    routes_module: ModuleType,\n    security_deps: dict[str, list[Any]] | None = None,\n) -> None\n

Bind OpenAPI operations to FastAPI routes.

Parameters:

Name Type Description Default app FastAPI

The FastAPI application instance to which routes will be added.

required spec dict[str, Any]

Parsed OpenAPI 3.x specification dictionary.

required routes_module ModuleType

Python module containing handler functions. Each handler's name MUST exactly match an OpenAPI operationId.

required security_deps dict[str, list[Any]] | None

Optional mapping of METHOD:/path \u2192 list[Depends(...)] generated from the spec's securitySchemes and per-operation security fields.

None

Raises:

Type Description MissingOperationHandler

If an operationId is missing from the spec or if no corresponding handler function exists in the routes module.

Notes

Responsibilities:

- Iterates through the OpenAPI specification paths and methods.\n- Resolves each ``operationId`` to a handler function, and registers\n  a corresponding ``APIRoute`` on the FastAPI application.\n- Injects FastAPI ``Depends()`` for each security requirement found\n  on the operation or inherited from the top-level ``security`` field.\n

Guarantees:

- Route registration is deterministic and spec-driven. No route\n  decorators are required or supported. Handler resolution errors\n  surface at application startup.\n
"},{"location":"openapi_first/cli/","title":"Cli","text":""},{"location":"openapi_first/cli/#openapi_first.cli","title":"openapi_first.cli","text":""},{"location":"openapi_first/cli/#openapi_first.cli--summary","title":"Summary","text":"

Command-line interface for FastAPI OpenAPI-first scaffolding utilities.

This CLI bootstraps OpenAPI-first FastAPI applications from versioned, bundled templates packaged with the library.

"},{"location":"openapi_first/cli/#openapi_first.cli-functions","title":"Functions","text":""},{"location":"openapi_first/cli/#openapi_first.cli.available_templates","title":"available_templates","text":"
available_templates() -> list[str]\n

Return a list of available application templates.

Returns:

Type Description list[str]

list[str]: Sorted list of template names found in the internal templates directory.

"},{"location":"openapi_first/cli/#openapi_first.cli.copy_template","title":"copy_template","text":"
copy_template(template: str, target_dir: Path) -> None\n

Copy a bundled OpenAPI-first application template into a directory.

Parameters:

Name Type Description Default template str

Name of the template to copy.

required target_dir Path

Filesystem path where the template should be copied.

required

Raises:

Type Description FileNotFoundError

If the requested template does not exist.

"},{"location":"openapi_first/client/","title":"Client","text":""},{"location":"openapi_first/client/#openapi_first.client","title":"openapi_first.client","text":""},{"location":"openapi_first/client/#openapi_first.client--summary","title":"Summary","text":"

OpenAPI-first HTTP client for contract-driven services.

This module provides OpenAPIClient, a thin, strict HTTP client that derives all callable operations directly from an OpenAPI 3.x specification.

It is the client counterpart to OpenAPIFirstApp.

Notes

Core Principles:

- The OpenAPI specification is the single source of truth\n- Each operationId becomes a callable Python method\n- No implicit schema mutation or inference\n- No code generation step\n- Minimal abstraction over httpx\n

Responsibilities:

- Parses an OpenAPI 3.x specification\n- Dynamically creates one callable per operationId\n- Enforces presence of servers, paths, and operationId\n- Formats path parameters safely\n- Handles JSON request bodies explicitly\n- Returns raw `httpx.Response` objects\n

Constraints:

- This module intentionally does NOT: Generate client code, validate request/response schemas, deserialize responses, retry requests, implement authentication helpers, or assume non-2xx responses are failures.\n
"},{"location":"openapi_first/client/#openapi_first.client-classes","title":"Classes","text":""},{"location":"openapi_first/client/#openapi_first.client.OpenAPIClient","title":"OpenAPIClient","text":"
OpenAPIClient(\n    spec: dict[str, Any],\n    base_url: str | None = None,\n    client: httpx.Client | None = None,\n)\n

OpenAPI-first HTTP client (httpx-based).

Parameters:

Name Type Description Default spec dict[str, Any]

Parsed OpenAPI 3.x specification.

required base_url str | None

Base URL of the target service. If omitted, the first entry in the OpenAPI servers list is used.

None client Client | None

Optional preconfigured httpx client instance.

None

Raises:

Type Description OpenAPIClientError

If no servers are defined, spec has no paths, operationIds are missing/duplicate, or required parameters are missing.

Notes

Responsibilities:

- This client derives all callable methods directly from an\n  OpenAPI 3.x specification. Each `operationId` becomes a method\n  on the client instance.\n

Guarantees:

- One callable per `operationId`.\n- Explicit parameters (path, query, headers, body).\n- No implicit schema inference or mutation.\n- Returns raw `httpx.Response` objects.\n- No response validation or deserialization.\n
Example
from openapi_first import loader, client\n\nspec = loader.load_openapi(\"openapi.yaml\")\n\napi = client.OpenAPIClient(\n    spec=spec,\n    base_url=\"http://localhost:8000\",\n)\n\n# Call operationId: getUser\nresponse = api.getUser(\n    path_params={\"user_id\": 123}\n)\n\nprint(response.status_code)\nprint(response.json())\n
"},{"location":"openapi_first/client/#openapi_first.client.OpenAPIClientError","title":"OpenAPIClientError","text":"

Bases: OpenAPIFirstError

Raised when an OpenAPI client operation fails.

"},{"location":"openapi_first/codegen/","title":"Codegen","text":""},{"location":"openapi_first/codegen/#openapi_first.codegen","title":"openapi_first.codegen","text":""},{"location":"openapi_first/codegen/#openapi_first.codegen--summary","title":"Summary","text":"

Core logic for generating Python source code from OpenAPI specifications.

This module provides reusable utilities for code generation, specifically generating Pydantic models and route handler stubs from OpenAPI 3.x schema definitions.

"},{"location":"openapi_first/codegen/#openapi_first.codegen-functions","title":"Functions","text":""},{"location":"openapi_first/codegen/#openapi_first.codegen.generate_models","title":"generate_models","text":"
generate_models(\n    spec_path: Path,\n    output_path: Path,\n    pydantic_version: int = 2,\n) -> None\n

Generate Pydantic models from an OpenAPI specification.

Parameters:

Name Type Description Default spec_path Path

Path to the OpenAPI specification file (YAML or JSON).

required output_path Path

Path where the generated Python code should be written.

required pydantic_version int

The Pydantic version to target (1 or 2). Defaults to 2.

2 Notes

Reusability: This function is designed to be used by the CLI and can be exposed as an MCP tool without modification.

"},{"location":"openapi_first/codegen/#openapi_first.codegen.generate_routes","title":"generate_routes","text":"
generate_routes(\n    spec_path: Path,\n    output_dir: Path,\n    *,\n    use_models: bool = False,\n    models_module: str = \"models\"\n) -> list[Path]\n

Generate route handler stubs from an OpenAPI specification.

Creates one <resource>.py file per resource in output_dir. Resources are derived from the first path segment (e.g. /pets and /pets/{id} both group under pets).

Parameters:

Name Type Description Default spec_path Path

Path to the OpenAPI specification file (YAML or JSON).

required output_dir Path

Directory where the generated route files are written. Created automatically if it does not exist.

required use_models bool

If True, import Pydantic models from models_module for request-body schemas referenced via $ref.

False models_module str

Dotted Python module path from which to import models (e.g. \"models\", \"app.models\").

'models'

Returns:

Type Description list[Path]

list[Path]: Absolute paths of every generated route file.

Raises:

Type Description OpenAPISpecLoadError

If the spec cannot be loaded or validated.

ValueError

If any operation is missing operationId.

"},{"location":"openapi_first/codegen_routes/","title":"Codegen Routes","text":""},{"location":"openapi_first/codegen_routes/#openapi_first.codegen_routes","title":"openapi_first.codegen_routes","text":""},{"location":"openapi_first/codegen_routes/#openapi_first.codegen_routes--summary","title":"Summary","text":"

Route handler code generation from OpenAPI specifications.

This module generates Python route handler stubs from an OpenAPI 3.x specification. Each resource (derived from the first path segment) gets its own file under the output directory. Every OpenAPI operation must define an operationId, which becomes the handler function name.

Notes

Design constraints:

- ``operationId`` is required on every operation (matching\n  ``binder.bind_routes``).\n- Handlers are stubs raising ``NotImplementedError``.\n- Sub-resources (e.g. ``/pets/{id}/photo``) are grouped with their\n  parent resource (``pets``).\n- Parameter types and defaults are inferred from the spec.\n- ``response: Response`` is injected for non-200 success codes.\n
"},{"location":"openapi_first/codegen_routes/#openapi_first.codegen_routes-functions","title":"Functions","text":""},{"location":"openapi_first/codegen_routes/#openapi_first.codegen_routes.generate_routes","title":"generate_routes","text":"
generate_routes(\n    spec_path: Path,\n    output_dir: Path,\n    *,\n    use_models: bool = False,\n    models_module: str = \"models\"\n) -> list[Path]\n

Generate route handler stubs from an OpenAPI specification.

Creates one <resource>.py file per resource in output_dir. Resources are derived from the first path segment (e.g. /pets and /pets/{id} both group under pets).

Parameters:

Name Type Description Default spec_path Path

Path to the OpenAPI specification file (YAML or JSON).

required output_dir Path

Directory where the generated route files are written. Created automatically if it does not exist.

required use_models bool

If True, import Pydantic models from models_module for request-body schemas referenced via $ref.

False models_module str

Dotted Python module path from which to import models (e.g. \"models\", \"app.models\").

'models'

Returns:

Type Description list[Path]

list[Path]: Absolute paths of every generated route file.

Raises:

Type Description OpenAPISpecLoadError

If the spec cannot be loaded or validated.

ValueError

If any operation is missing operationId.

"},{"location":"openapi_first/errors/","title":"Errors","text":""},{"location":"openapi_first/errors/#openapi_first.errors","title":"openapi_first.errors","text":""},{"location":"openapi_first/errors/#openapi_first.errors--summary","title":"Summary","text":"

Exceptions for OpenAPI-first FastAPI applications.

This module defines a small hierarchy of explicit, intention-revealing exceptions used to signal contract violations between an OpenAPI specification and its Python implementation.

Notes

Design Principles:

- Errors represent programmer mistakes, not runtime conditions.\n- All errors are raised during application startup.\n- Messages are actionable and suitable for CI/CD output.\n- Exceptions are explicit rather than reused from generic built-ins.\n

These errors should normally cause immediate application failure.

"},{"location":"openapi_first/errors/#openapi_first.errors-classes","title":"Classes","text":""},{"location":"openapi_first/errors/#openapi_first.errors.MissingOperationHandler","title":"MissingOperationHandler","text":"
MissingOperationHandler(\n    *,\n    path: str,\n    method: str,\n    operation_id: str | None = None\n)\n

Bases: OpenAPIFirstError

Raised when an OpenAPI operation cannot be resolved to a handler.

Parameters:

Name Type Description Default path str

The HTTP path declared in the OpenAPI specification.

required method str

The HTTP method (as declared in the OpenAPI spec).

required operation_id str | None

The operationId declared in the OpenAPI spec, if present.

None Notes

Scenarios:

- An OpenAPI operation does not define an `operationId`.\n- An `operationId` is defined but no matching function exists in\n  the provided routes module.\n

Guarantees:

- This represents a violation of the OpenAPI-first contract and\n  indicates that the specification and implementation are out of\n  sync.\n
"},{"location":"openapi_first/errors/#openapi_first.errors.OpenAPIFirstError","title":"OpenAPIFirstError","text":"

Bases: Exception

Base exception for all OpenAPI-first enforcement errors.

Notes

Responsibilities:

- This exception exists to allow callers, test suites, and CI\n  pipelines to catch and distinguish OpenAPI contract violations\n  from unrelated runtime errors.\n- All exceptions raised by the OpenAPI-first core should inherit\n  from this type.\n
"},{"location":"openapi_first/loader/","title":"Loader","text":""},{"location":"openapi_first/loader/#openapi_first.loader","title":"openapi_first.loader","text":""},{"location":"openapi_first/loader/#openapi_first.loader--summary","title":"Summary","text":"

OpenAPI specification loading and validation utilities.

This module is responsible for loading an OpenAPI 3.x specification from disk and validating it before it is used by the application.

It enforces the principle that an invalid or malformed OpenAPI document must never reach the routing or runtime layers.

Notes

Design Principles:

- OpenAPI is treated as an authoritative contract.\n- Invalid specifications fail fast at application startup.\n- Supported formats are JSON and YAML.\n- Validation errors are surfaced clearly and early.\n

Constraints:

- This module intentionally does NOT:\n    - Modify the OpenAPI document.\n    - Infer missing fields.\n    - Generate models or code.\n    - Perform request/response validation at runtime.\n
"},{"location":"openapi_first/loader/#openapi_first.loader-classes","title":"Classes","text":""},{"location":"openapi_first/loader/#openapi_first.loader.OpenAPISpecLoadError","title":"OpenAPISpecLoadError","text":"

Bases: OpenAPIFirstError

Raised when an OpenAPI specification cannot be loaded or validated.

Notes

Guarantees:

- This error indicates that the OpenAPI document is unreadable,\n  malformed, or violates the OpenAPI 3.x specification.\n
"},{"location":"openapi_first/loader/#openapi_first.loader-functions","title":"Functions","text":""},{"location":"openapi_first/loader/#openapi_first.loader.load_openapi","title":"load_openapi","text":"
load_openapi(path: str | Path) -> dict[str, Any]\n

Load and validate an OpenAPI 3.x specification from disk.

Parameters:

Name Type Description Default path str | Path

Filesystem path to an OpenAPI specification file. Supported extensions: .json, .yaml, .yml.

required

Returns:

Type Description dict[str, Any]

dict[str, Any]: Parsed and validated OpenAPI specification.

Raises:

Type Description OpenAPISpecLoadError

If the file does not exist, cannot be parsed, or fails OpenAPI schema validation.

Notes

Guarantees:

- The specification is parsed based on file extension and validated\n  using a strict OpenAPI schema validator.\n- Any error results in an immediate exception, preventing\n  application startup.\n
"},{"location":"openapi_first/security/","title":"Security","text":""},{"location":"openapi_first/security/#openapi_first.security","title":"openapi_first.security","text":""},{"location":"openapi_first/security/#openapi_first.security--summary","title":"Summary","text":"

OpenAPI security scheme parsing and auto-generated auth dependencies.

This module reads securitySchemes and per-operation security from an OpenAPI spec, resolves {ENV_VAR} placeholders in x- extension fields, and generates FastAPI dependencies for token validation (e.g., Bearer JWT introspection).

"},{"location":"openapi_first/security/#openapi_first.security-functions","title":"Functions","text":""},{"location":"openapi_first/security/#openapi_first.security.make_security_dependencies","title":"make_security_dependencies","text":"
make_security_dependencies(\n    spec: dict[str, Any],\n    security_schemes: dict[str, dict[str, Any]],\n) -> dict[str, list[Any]]\n

Build a mapping of METHOD:/path \u2192 list of Depends(...).

The effective security for each operation is resolved by:

  1. Using the operation-level security field if present.
  2. Falling back to the top-level security field.
  3. An empty list means no auth required for that operation.
"},{"location":"openapi_first/security/#openapi_first.security.parse_security_schemes","title":"parse_security_schemes","text":"
parse_security_schemes(\n    spec: dict[str, Any],\n) -> dict[str, dict[str, Any]]\n

Extract and resolve environment variables in security schemes.

"},{"location":"openapi_first/templates/","title":"Templates","text":"
  • Crud App
  • Health App
  • Model App
  • Vet App
"},{"location":"openapi_first/templates/#openapi_first.templates","title":"openapi_first.templates","text":"

Application templates for FastAPI OpenAPI First.

This package contains example and scaffolding templates intended to be copied into user projects via the openapi-first CLI.

Templates in this package are: - Reference implementations of OpenAPI-first services - Not part of the openapi_first public or internal API - Not intended to be imported as runtime dependencies

The presence of this file exists solely to: - Mark the directory as an explicit Python package - Enable deterministic tooling behavior (documentation, packaging) - Avoid accidental traversal of non-package directories

No code in this package should be imported by library consumers.

"},{"location":"openapi_first/templates/crud_app/","title":"Crud App","text":"
  • Data
  • Main
  • Routes
  • Test Crud App
"},{"location":"openapi_first/templates/crud_app/#openapi_first.templates.crud_app","title":"openapi_first.templates.crud_app","text":"

OpenAPI-first CRUD application template.

This package contains a complete, minimal example of an OpenAPI-first CRUD service built using the openapi_first library.

The application is assembled exclusively from: - an OpenAPI specification (openapi.yaml) - a handler namespace implementing CRUD operations (routes) - an in-memory mock data store (data)

All HTTP routes, methods, schemas, and operation bindings are defined in the OpenAPI specification and enforced at application startup. No decorator-driven routing or implicit framework behavior is used.

This template demonstrates: - operationId-driven server-side route binding - explicit HTTP status code control in handlers - operationId-driven client usage against the same OpenAPI contract - end-to-end validation using in-memory data and tests

"},{"location":"openapi_first/templates/crud_app/#openapi_first.templates.crud_app--scaffolding-via-cli","title":"Scaffolding via CLI","text":"

Create a new CRUD example service using the bundled template:

openapi-first scaffold crud_app\n

Create the service in a custom directory:

openapi-first scaffold crud_app my-crud-service\n

List all available application templates:

openapi-first scaffold --list\n

The CLI copies template files verbatim into the target directory. No code is generated or modified beyond the copied scaffold.

"},{"location":"openapi_first/templates/crud_app/#openapi_first.templates.crud_app--client-usage-example","title":"Client Usage Example","text":"

The same OpenAPI specification used by the server can be used to construct a strict, operationId-driven HTTP client.

Example client calls for CRUD operations:

from openapi_first.loader import load_openapi\nfrom openapi_first.client import OpenAPIClient\n\nspec = load_openapi(\"openapi.yaml\")\nclient = OpenAPIClient(spec)\n\n# List items\nresponse = client.list_items()\n\n# Get item by ID\nresponse = client.get_item(\n    path_params={\"item_id\": 1}\n)\n\n# Create item\nresponse = client.create_item(\n    body={\"name\": \"Orange\", \"price\": 0.8}\n)\n\n# Update item\nresponse = client.update_item(\n    path_params={\"item_id\": 1},\n    body={\"name\": \"Green Apple\", \"price\": 0.6},\n)\n\n# Delete item\nresponse = client.delete_item(\n    path_params={\"item_id\": 1}\n)\n

Client guarantees: - One callable per OpenAPI operationId - No hardcoded URLs or HTTP methods in user code - Path and request parameters must match the OpenAPI specification - Invalid or incomplete OpenAPI specs fail at client construction time

"},{"location":"openapi_first/templates/crud_app/#openapi_first.templates.crud_app--non-goals","title":"Non-Goals","text":"

This template is intentionally minimal and is NOT: - production-ready - persistent or concurrency-safe - a reference architecture for data storage

It exists solely as a copyable example for learning, testing, and bootstrapping OpenAPI-first services.

This package is not part of the openapi_first library API surface.

"},{"location":"openapi_first/templates/crud_app/data/","title":"Data","text":""},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data","title":"openapi_first.templates.crud_app.data","text":"

In-memory mock data store for CRUD example.

This module intentionally avoids persistence and concurrency guarantees. It is suitable for demos, tests, and scaffolding only.

It intentionally avoids - persistence - concurrency guarantees - validation - error handling

The implementation is suitable for: - demonstrations - tests - scaffolding and example services

It is explicitly NOT suitable for production use.

This module is not part of the openapi_first library API surface.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data-functions","title":"Functions","text":""},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.create_item","title":"create_item","text":"
create_item(payload: dict) -> dict\n

Create a new item in the data store.

A new integer ID is assigned automatically. No validation is performed on the provided payload.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.create_item--parameters","title":"Parameters","text":"

payload : dict Item attributes excluding the id field.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.create_item--returns","title":"Returns","text":"

dict The newly created item, including its assigned ID.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.delete_item","title":"delete_item","text":"
delete_item(item_id: int)\n

Remove an item from the data store.

This function assumes the item exists and will raise KeyError if the ID is not present.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.delete_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to delete.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.get_item","title":"get_item","text":"
get_item(item_id: int) -> dict\n

Retrieve a single item by ID.

This function assumes the item exists and will raise KeyError if the ID is not present in the store.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.get_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to retrieve.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.get_item--returns","title":"Returns","text":"

dict The stored item representation.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.list_items","title":"list_items","text":"
list_items() -> list[dict]\n

Return all items in the data store.

This function performs no filtering, pagination, or sorting. The returned collection reflects the current in-memory state.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.list_items--returns","title":"Returns","text":"

list[dict] A list of item representations.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.update_item","title":"update_item","text":"
update_item(item_id: int, payload: dict) -> dict\n

Replace an existing item in the data store.

This function overwrites the existing item entirely and does not perform partial updates or validation. If the item does not exist, it will be created implicitly.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.update_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to update. payload : dict Item attributes excluding the id field.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.update_item--returns","title":"Returns","text":"

dict The updated item representation.

"},{"location":"openapi_first/templates/crud_app/main/","title":"Main","text":""},{"location":"openapi_first/templates/crud_app/main/#openapi_first.templates.crud_app.main","title":"openapi_first.templates.crud_app.main","text":"

Application entry point for an OpenAPI-first CRUD example service.

This module constructs a FastAPI application exclusively from an OpenAPI specification and a handler namespace, without using decorator-driven routing.

All HTTP routes, methods, request/response schemas, and operation bindings are defined in the OpenAPI document referenced by openapi_path. Python callables defined in the routes module are bound to OpenAPI operations strictly via operationId.

This module contains no routing logic, persistence concerns, or framework configuration beyond application assembly.

Design guarantees: - OpenAPI is the single source of truth - No undocumented routes can exist - Every OpenAPI operationId must resolve to exactly one handler - All contract violations fail at application startup

This file is intended to be used as the ASGI entry point.

Example

uvicorn main:app

"},{"location":"openapi_first/templates/crud_app/main/#openapi_first.templates.crud_app.main-classes","title":"Classes","text":""},{"location":"openapi_first/templates/crud_app/routes/","title":"Routes","text":""},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes","title":"openapi_first.templates.crud_app.routes","text":"

CRUD route handlers bound via OpenAPI operationId.

These handlers explicitly control HTTP status codes to ensure runtime behavior matches the OpenAPI contract.

This module defines OpenAPI-bound operation handlers for a simple CRUD service. Functions in this module are bound to HTTP routes exclusively via OpenAPI operationId values.

Handlers explicitly control HTTP response status codes to ensure runtime behavior matches the OpenAPI contract. Error conditions are translated into explicit HTTP responses rather than relying on implicit framework behavior.

No routing decorators or path definitions appear in this module. All routing, HTTP methods, and schemas are defined in the OpenAPI specification.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes-functions","title":"Functions","text":""},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.create_item","title":"create_item","text":"
create_item(payload: dict, response: Response) -> dict\n

Create a new item.

Implements the OpenAPI operation identified by operationId: create_item.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.create_item--parameters","title":"Parameters","text":"

payload : dict Item attributes excluding the id field. response : Response Response object used to set the HTTP status code.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.create_item--returns","title":"Returns","text":"

dict The newly created item.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.delete_item","title":"delete_item","text":"
delete_item(item_id: int, response: Response) -> None\n

Delete an existing item.

Implements the OpenAPI operation identified by operationId: delete_item.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.delete_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to delete. response : Response Response object used to set the HTTP status code.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.delete_item--returns","title":"Returns","text":"

None No content.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.delete_item--raises","title":"Raises","text":"

HTTPException 404 if the item does not exist.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.get_item","title":"get_item","text":"
get_item(item_id: int) -> dict\n

Retrieve a single item by ID.

Implements the OpenAPI operation identified by operationId: get_item.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.get_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to retrieve.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.get_item--returns","title":"Returns","text":"

dict The requested item.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.get_item--raises","title":"Raises","text":"

HTTPException 404 if the item does not exist.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.list_items","title":"list_items","text":"
list_items() -> list[dict]\n

List all items.

Implements the OpenAPI operation identified by operationId: list_items.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.list_items--returns","title":"Returns","text":"

list[dict] A list of item representations.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.update_item","title":"update_item","text":"
update_item(item_id: int, payload: dict) -> dict\n

Update an existing item.

Implements the OpenAPI operation identified by operationId: update_item.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.update_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to update. payload : dict Item attributes excluding the id field.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.update_item--returns","title":"Returns","text":"

dict The updated item.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.update_item--raises","title":"Raises","text":"

HTTPException 404 if the item does not exist.

"},{"location":"openapi_first/templates/crud_app/test_crud_app/","title":"Test Crud App","text":""},{"location":"openapi_first/templates/crud_app/test_crud_app/#openapi_first.templates.crud_app.test_crud_app","title":"openapi_first.templates.crud_app.test_crud_app","text":"

End-to-end tests for the OpenAPI-first CRUD example app.

These tests validate that all CRUD operations behave correctly against the in-memory mock data store. - OpenAPI specification loading - OperationId-driven route binding on the server - OperationId-driven client invocation - Correct HTTP status codes and response payloads

The tests exercise all CRUD operations against an in-memory mock data store and assume deterministic behavior within a single process.

The tests assume: - OpenAPI-first route binding - In-memory storage (no persistence guarantees) - Deterministic behavior in a single process - One-to-one correspondence between OpenAPI operationId values and server/client callables

"},{"location":"openapi_first/templates/crud_app/test_crud_app/#openapi_first.templates.crud_app.test_crud_app-classes","title":"Classes","text":""},{"location":"openapi_first/templates/crud_app/test_crud_app/#openapi_first.templates.crud_app.test_crud_app-functions","title":"Functions","text":""},{"location":"openapi_first/templates/crud_app/test_crud_app/#openapi_first.templates.crud_app.test_crud_app.test_create_item","title":"test_create_item","text":"
test_create_item()\n

Creating a new item should return the created entity.

"},{"location":"openapi_first/templates/crud_app/test_crud_app/#openapi_first.templates.crud_app.test_crud_app.test_delete_item","title":"test_delete_item","text":"
test_delete_item()\n

Deleting an item should remove it from the store.

"},{"location":"openapi_first/templates/crud_app/test_crud_app/#openapi_first.templates.crud_app.test_crud_app.test_get_item","title":"test_get_item","text":"
test_get_item()\n

Existing item should be retrievable by ID.

"},{"location":"openapi_first/templates/crud_app/test_crud_app/#openapi_first.templates.crud_app.test_crud_app.test_list_items_initial","title":"test_list_items_initial","text":"
test_list_items_initial()\n

Initial items should be present.

"},{"location":"openapi_first/templates/crud_app/test_crud_app/#openapi_first.templates.crud_app.test_crud_app.test_update_item","title":"test_update_item","text":"
test_update_item()\n

Updating an item should replace its values.

"},{"location":"openapi_first/templates/health_app/","title":"Health App","text":"
  • Main
  • Routes
"},{"location":"openapi_first/templates/health_app/#openapi_first.templates.health_app","title":"openapi_first.templates.health_app","text":"

OpenAPI-first FastAPI application template.

This package contains a minimal, fully working example of an OpenAPI-first FastAPI service built using the openapi_first library.

The application is assembled exclusively from: - an OpenAPI specification (openapi.yaml) - a handler namespace (routes)

No routing decorators, implicit behavior, or framework-specific convenience abstractions are used. All HTTP routes, methods, and operation bindings are defined in OpenAPI and enforced at application startup.

This package is intended to be copied as a starting point for new services via the openapi-first CLI. It is not part of the openapi_first library API surface.

"},{"location":"openapi_first/templates/health_app/#openapi_first.templates.health_app--scaffolding-via-cli","title":"Scaffolding via CLI","text":"

Create a new OpenAPI-first health check service using the bundled template:

openapi-first scaffold health_app\n

Create the service in a custom directory:

openapi-first scaffold health_app my-health-service\n

List all available application templates:

openapi-first scaffold --list\n

The CLI copies template files verbatim into the target directory. No code is generated or modified beyond the copied scaffold.

"},{"location":"openapi_first/templates/health_app/#openapi_first.templates.health_app--client-usage-example","title":"Client Usage Example","text":"

The same OpenAPI specification used by the server can be used to construct a strict, operationId-driven HTTP client.

Example client call for the get_health operation:

from openapi_first.loader import load_openapi\nfrom openapi_first.client import OpenAPIClient\n\nspec = load_openapi(\"openapi.yaml\")\nclient = OpenAPIClient(spec)\n\nresponse = client.get_health()\n\nassert response.status_code == 200\nassert response.json() == {\"status\": \"ok\"}\n

Client guarantees: - One callable per OpenAPI operationId - No hardcoded URLs or HTTP methods in user code - Path and request parameters must match the OpenAPI specification - Invalid or incomplete OpenAPI specs fail at client construction time

"},{"location":"openapi_first/templates/health_app/main/","title":"Main","text":""},{"location":"openapi_first/templates/health_app/main/#openapi_first.templates.health_app.main","title":"openapi_first.templates.health_app.main","text":"

Application entry point for an OpenAPI-first FastAPI service.

This module constructs a FastAPI application exclusively from an OpenAPI specification and a handler namespace, without using decorator-driven routing.

All HTTP routes, methods, and operation bindings are defined in the OpenAPI document referenced by openapi_path. Python callables defined in the routes module are bound to OpenAPI operations strictly via operationId.

This module contains no routing logic, request handling, or framework configuration beyond application assembly.

Design guarantees: - OpenAPI is the single source of truth - No undocumented routes can exist - Every OpenAPI operationId must resolve to exactly one handler - All contract violations fail at application startup

This file is intended to be used as the ASGI entry point.

Example

uvicorn main:app

"},{"location":"openapi_first/templates/health_app/main/#openapi_first.templates.health_app.main-classes","title":"Classes","text":""},{"location":"openapi_first/templates/health_app/routes/","title":"Routes","text":""},{"location":"openapi_first/templates/health_app/routes/#openapi_first.templates.health_app.routes","title":"openapi_first.templates.health_app.routes","text":"

OpenAPI operation handlers.

This module defines pure Python callables that implement OpenAPI operations for this service. Functions in this module are bound to HTTP routes exclusively via OpenAPI operationId values.

No routing decorators, HTTP metadata, or framework-specific logic should appear here. All request/response semantics are defined in the OpenAPI specification.

This module serves solely as an operationId namespace.

"},{"location":"openapi_first/templates/health_app/routes/#openapi_first.templates.health_app.routes-functions","title":"Functions","text":""},{"location":"openapi_first/templates/health_app/routes/#openapi_first.templates.health_app.routes.get_health","title":"get_health","text":"
get_health() -> dict\n

Health check operation handler.

This function implements the OpenAPI operation identified by operationId: get_health.

It contains no routing metadata or framework-specific logic. Request binding, HTTP method, and response semantics are defined exclusively by the OpenAPI specification.

"},{"location":"openapi_first/templates/health_app/routes/#openapi_first.templates.health_app.routes.get_health--returns","title":"Returns","text":"

dict A minimal liveness payload indicating service health.

"},{"location":"openapi_first/templates/model_app/","title":"Model App","text":"
  • Data
  • Main
  • Models
  • Routes
  • Test Model App
"},{"location":"openapi_first/templates/model_app/#openapi_first.templates.model_app","title":"openapi_first.templates.model_app","text":"

OpenAPI-first model-based CRUD application template.

This package contains a complete, minimal example of an OpenAPI-first CRUD service that uses explicit Pydantic domain models for request and response schemas.

The application is assembled exclusively from: - an OpenAPI specification (openapi.yaml) - a handler namespace implementing CRUD operations (routes) - Pydantic domain models (models) - an in-memory mock data store (data)

All HTTP routes, methods, schemas, and operation bindings are defined in the OpenAPI specification and enforced at application startup. No decorator-driven routing or implicit framework behavior is used.

This template demonstrates: - operationId-driven server-side route binding - explicit request and response modeling with Pydantic - explicit HTTP status code control in handlers - operationId-driven client usage against the same OpenAPI contract - end-to-end validation using in-memory data and tests

"},{"location":"openapi_first/templates/model_app/#openapi_first.templates.model_app--scaffolding-via-cli","title":"Scaffolding via CLI","text":"

Create a new model-based CRUD example service using the bundled template:

openapi-first scaffold model_app\n

Create the service in a custom directory:

openapi-first scaffold model_app my-model-service\n

List all available application templates:

openapi-first scaffold --list\n

The CLI copies template files verbatim into the target directory. No code is generated or modified beyond the copied scaffold.

"},{"location":"openapi_first/templates/model_app/#openapi_first.templates.model_app--client-usage-example","title":"Client Usage Example","text":"

The same OpenAPI specification used by the server can be used to construct a strict, operationId-driven HTTP client.

Example client calls for model-based CRUD operations:

from openapi_first.loader import load_openapi\nfrom openapi_first.client import OpenAPIClient\n\nspec = load_openapi(\"openapi.yaml\")\nclient = OpenAPIClient(spec)\n\n# List items\nresponse = client.list_items()\n\n# Get item by ID\nresponse = client.get_item(\n    path_params={\"item_id\": 1}\n)\n\n# Create item\nresponse = client.create_item(\n    body={\"name\": \"Orange\", \"price\": 0.8}\n)\n\n# Update item\nresponse = client.update_item(\n    path_params={\"item_id\": 1},\n    body={\"name\": \"Green Apple\", \"price\": 0.6},\n)\n\n# Delete item\nresponse = client.delete_item(\n    path_params={\"item_id\": 1}\n)\n

Client guarantees: - One callable per OpenAPI operationId - No hardcoded URLs or HTTP methods in user code - Request and response payloads conform to Pydantic models - Invalid or incomplete OpenAPI specs fail at client construction time

"},{"location":"openapi_first/templates/model_app/#openapi_first.templates.model_app--non-goals","title":"Non-Goals","text":"

This template is intentionally minimal and is NOT: - production-ready - persistent or concurrency-safe - a reference architecture for data storage

It exists solely as a copyable example for learning, testing, and bootstrapping OpenAPI-first services.

This package is not part of the openapi_first library API surface.

"},{"location":"openapi_first/templates/model_app/data/","title":"Data","text":""},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data","title":"openapi_first.templates.model_app.data","text":"

In-memory data store using Pydantic models.

This module is NOT thread-safe and is intended for demos and scaffolds only. This module provides a minimal, process-local data store for the model-based CRUD example application. It stores and returns domain objects defined using Pydantic models and is intended solely for demonstration and scaffolding purposes.

The implementation intentionally avoids: - persistence - concurrency guarantees - transactional semantics - validation beyond what Pydantic provides

It is not part of the openapi_first library API surface.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data-functions","title":"Functions","text":""},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.create_item","title":"create_item","text":"
create_item(payload: ItemCreate) -> Item\n

Create a new item in the data store.

A new identifier is assigned automatically. No additional validation is performed beyond Pydantic model validation.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.create_item--parameters","title":"Parameters","text":"

payload : ItemCreate Data required to create a new item.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.create_item--returns","title":"Returns","text":"

Item The newly created item.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.delete_item","title":"delete_item","text":"
delete_item(item_id: int) -> None\n

Remove an item from the data store.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.delete_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to delete.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.delete_item--raises","title":"Raises","text":"

KeyError If the item does not exist.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.get_item","title":"get_item","text":"
get_item(item_id: int) -> Item\n

Retrieve a single item by ID.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.get_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to retrieve.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.get_item--returns","title":"Returns","text":"

Item The requested item.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.get_item--raises","title":"Raises","text":"

KeyError If the item does not exist.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.list_items","title":"list_items","text":"
list_items() -> list[Item]\n

Return all items in the data store.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.list_items--returns","title":"Returns","text":"

list[Item] A list of item domain objects.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.update_item","title":"update_item","text":"
update_item(item_id: int, payload: ItemCreate) -> Item\n

Replace an existing item in the data store.

This function performs a full replacement of the stored item. Partial updates are not supported.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.update_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to update. payload : ItemCreate New item data.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.update_item--returns","title":"Returns","text":"

Item The updated item.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.update_item--raises","title":"Raises","text":"

KeyError If the item does not exist.

"},{"location":"openapi_first/templates/model_app/main/","title":"Main","text":""},{"location":"openapi_first/templates/model_app/main/#openapi_first.templates.model_app.main","title":"openapi_first.templates.model_app.main","text":"

Application entry point for an OpenAPI-first model-based CRUD example service.

This module constructs a FastAPI application exclusively from an OpenAPI specification and a handler namespace, without using decorator-driven routing.

All HTTP routes, methods, request/response schemas, and operation bindings are defined in the OpenAPI document referenced by openapi_path. Python callables defined in the routes module are bound to OpenAPI operations strictly via operationId.

This module contains no routing logic, persistence concerns, or framework configuration beyond application assembly.

Design guarantees: - OpenAPI is the single source of truth - No undocumented routes can exist - Every OpenAPI operationId must resolve to exactly one handler - All contract violations fail at application startup

This file is intended to be used as the ASGI entry point.

Example

uvicorn main:app

"},{"location":"openapi_first/templates/model_app/main/#openapi_first.templates.model_app.main-classes","title":"Classes","text":""},{"location":"openapi_first/templates/model_app/models/","title":"Models","text":""},{"location":"openapi_first/templates/model_app/models/#openapi_first.templates.model_app.models","title":"openapi_first.templates.model_app.models","text":"

Pydantic domain models for the CRUD example.

This module defines Pydantic models that represent the domain entities used by the service. These models are referenced by the OpenAPI specification for request and response schemas.

The models are declarative and framework-agnostic. They contain no persistence logic, validation beyond type constraints, or business behavior.

This module is not part of the openapi_first library API surface. It exists solely to support the example application template.

"},{"location":"openapi_first/templates/model_app/models/#openapi_first.templates.model_app.models-classes","title":"Classes","text":""},{"location":"openapi_first/templates/model_app/models/#openapi_first.templates.model_app.models.Item","title":"Item","text":"

Bases: ItemBase

Domain model for a persisted item.

This model represents the full item state returned in responses, including the server-assigned identifier.

"},{"location":"openapi_first/templates/model_app/models/#openapi_first.templates.model_app.models.ItemBase","title":"ItemBase","text":"

Bases: BaseModel

Base domain model for an item.

Defines fields common to all item representations.

"},{"location":"openapi_first/templates/model_app/models/#openapi_first.templates.model_app.models.ItemCreate","title":"ItemCreate","text":"

Bases: ItemBase

Domain model for item creation requests.

This model is used for request bodies when creating new items. It intentionally excludes the id field, which is assigned by the service.

"},{"location":"openapi_first/templates/model_app/routes/","title":"Routes","text":""},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes","title":"openapi_first.templates.model_app.routes","text":"

CRUD route handlers bound via OpenAPI operationId.

This module defines OpenAPI-bound operation handlers for a model-based CRUD service. Functions in this module are bound to HTTP routes exclusively via OpenAPI operationId values.

Handlers explicitly control HTTP response status codes to ensure runtime behavior matches the OpenAPI contract. Domain models defined using Pydantic are used for request and response payloads.

No routing decorators, path definitions, or implicit framework behavior appear in this module. All routing, HTTP methods, and schemas are defined in the OpenAPI specification.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes-functions","title":"Functions","text":""},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.create_item","title":"create_item","text":"
create_item(\n    payload: ItemCreate, response: Response\n) -> Item\n

Create a new item.

Implements the OpenAPI operation identified by operationId: create_item.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.create_item--parameters","title":"Parameters","text":"

payload : ItemCreate Request body describing the item to create. response : Response Response object used to set the HTTP status code.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.create_item--returns","title":"Returns","text":"

Item The newly created item.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.delete_item","title":"delete_item","text":"
delete_item(item_id: int, response: Response) -> None\n

Delete an existing item.

Implements the OpenAPI operation identified by operationId: delete_item.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.delete_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to delete. response : Response Response object used to set the HTTP status code.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.delete_item--returns","title":"Returns","text":"

None No content.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.delete_item--raises","title":"Raises","text":"

HTTPException 404 if the item does not exist.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.get_item","title":"get_item","text":"
get_item(item_id: int) -> Item\n

Retrieve a single item by ID.

Implements the OpenAPI operation identified by operationId: get_item.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.get_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to retrieve.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.get_item--returns","title":"Returns","text":"

Item The requested item.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.get_item--raises","title":"Raises","text":"

HTTPException 404 if the item does not exist.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.list_items","title":"list_items","text":"
list_items() -> list[Item]\n

List all items.

Implements the OpenAPI operation identified by operationId: list_items.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.list_items--returns","title":"Returns","text":"

list[Item] A list of item domain objects.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.update_item","title":"update_item","text":"
update_item(item_id: int, payload: ItemCreate) -> Item\n

Update an existing item.

Implements the OpenAPI operation identified by operationId: update_item.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.update_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to update. payload : ItemCreate New item data.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.update_item--returns","title":"Returns","text":"

Item The updated item.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.update_item--raises","title":"Raises","text":"

HTTPException 404 if the item does not exist.

"},{"location":"openapi_first/templates/model_app/test_model_app/","title":"Test Model App","text":""},{"location":"openapi_first/templates/model_app/test_model_app/#openapi_first.templates.model_app.test_model_app","title":"openapi_first.templates.model_app.test_model_app","text":"

End-to-end tests for the OpenAPI-first model CRUD example app.

These tests validate that all CRUD operations behave correctly against the in-memory mock data store using Pydantic models. - OpenAPI specification loading - OperationId-driven route binding on the server - OperationId-driven client invocation - Pydantic model-based request and response handling

All CRUD operations are exercised against an in-memory mock data store backed by Pydantic domain models.

The tests assume: - OpenAPI-first route binding - Pydantic model validation - In-memory storage (no persistence guarantees) - Deterministic behavior in a single process

"},{"location":"openapi_first/templates/model_app/test_model_app/#openapi_first.templates.model_app.test_model_app-classes","title":"Classes","text":""},{"location":"openapi_first/templates/model_app/test_model_app/#openapi_first.templates.model_app.test_model_app-functions","title":"Functions","text":""},{"location":"openapi_first/templates/model_app/test_model_app/#openapi_first.templates.model_app.test_model_app.test_create_item","title":"test_create_item","text":"
test_create_item()\n

Creating a new item should return the created entity.

"},{"location":"openapi_first/templates/model_app/test_model_app/#openapi_first.templates.model_app.test_model_app.test_delete_item","title":"test_delete_item","text":"
test_delete_item()\n

Deleting an item should remove it from the store.

"},{"location":"openapi_first/templates/model_app/test_model_app/#openapi_first.templates.model_app.test_model_app.test_get_item","title":"test_get_item","text":"
test_get_item()\n

Existing item should be retrievable by ID.

"},{"location":"openapi_first/templates/model_app/test_model_app/#openapi_first.templates.model_app.test_model_app.test_list_items_initial","title":"test_list_items_initial","text":"
test_list_items_initial()\n

Initial items should be present.

"},{"location":"openapi_first/templates/model_app/test_model_app/#openapi_first.templates.model_app.test_model_app.test_update_item","title":"test_update_item","text":"
test_update_item()\n

Updating an item should replace its values.

"},{"location":"openapi_first/templates/vet_app/","title":"Vet App","text":"
  • Data
  • Main
  • Models
  • Routes
  • Sse
  • Test Vet App
"},{"location":"openapi_first/templates/vet_app/#openapi_first.templates.vet_app","title":"openapi_first.templates.vet_app","text":"

OpenAPI-first Veterinary Clinic application template.

This package contains a complete, runnable example of an OpenAPI-first veterinary clinic management service. It demonstrates all x- extension fields consumed by the react-openapi admin panel renderer.

The application manages five resources:

  • Parents \u2014 pet owners with contact details
  • Vets \u2014 veterinarians with specializations
  • Treatments \u2014 medical procedure catalog
  • Pets \u2014 animals with species, age, weight, and photos
  • Appointments \u2014 scheduled visits linking pets, vets, and treatments

All HTTP routes, methods, schemas, and operation bindings are defined in the OpenAPI specification (openapi.yaml). Every operation has an explicit operationId that maps to a Python handler in routes.py.

This file is a copyable template. It is not part of the openapi_first library API surface.

"},{"location":"openapi_first/templates/vet_app/#openapi_first.templates.vet_app--openapi-x-extension-fields-demonstrated","title":"OpenAPI x- extension fields demonstrated","text":"

Schema-level extensions (display metadata for resource endpoints):

x-primary-key (REQUIRED) Primary key property name x-display-format (REQUIRED) Human-readable label template x-list-columns (REQUIRED) Columns for the datatable

Property-level extensions (control UI rendering):

x-label (REQUIRED) Human-readable field label x-order (REQUIRED) Field ordering in forms/detail x-description (optional) Helper text below form fields x-hidden (optional) Visibility in form / list / detail x-filterable (optional) Allows column filtering x-sortable (optional) Allows column sorting x-fk (optional) Foreign key \u2014 renders as dropdown x-fk.resource (REQUIRED for FK) Target resource name x-fk.prefetch (optional) Preload all FK options on mount x-ui-type (optional) Custom UI type (e.g. image upload) x-upload-url (optional) Upload endpoint for binary fields

"},{"location":"openapi_first/templates/vet_app/#openapi_first.templates.vet_app--scaffolding-via-cli","title":"Scaffolding via CLI","text":"

Create a new vet clinic service using the bundled template:

openapi-first scaffold vet_app\n

Create the service in a custom directory:

openapi-first scaffold vet_app my-vet-clinic\n
"},{"location":"openapi_first/templates/vet_app/#openapi_first.templates.vet_app--client-usage-example","title":"Client Usage Example","text":"
from openapi_first.loader import load_openapi\nfrom openapi_first.client import OpenAPIClient\n\nspec = load_openapi(\"openapi.yaml\")\nclient = OpenAPIClient(spec)\n\n# List pets with pagination\nresponse = client.list_pets(query_params={\"limit\": 10, \"offset\": 0})\n\n# Create a pet with FK references\nresponse = client.create_pet(\n    body={\"name\": \"Fido\", \"species\": \"dog\", \"parents\": [1, 2]}\n)\n\n# Upload a pet photo\nresponse = client.upload_pet_photo(\n    path_params={\"id\": 1},\n    body={\"file\": open(\"photo.jpg\", \"rb\")},\n)\n
"},{"location":"openapi_first/templates/vet_app/#openapi_first.templates.vet_app--non-goals","title":"Non-Goals","text":"

This template is intentionally minimal and is NOT: - production-ready - persistent or concurrency-safe - a reference architecture for data storage

It exists solely as a copyable example for learning, testing, and bootstrapping OpenAPI-first services.

This package is not part of the openapi_first library API surface.

"},{"location":"openapi_first/templates/vet_app/data/","title":"Data","text":""},{"location":"openapi_first/templates/vet_app/data/#openapi_first.templates.vet_app.data","title":"openapi_first.templates.vet_app.data","text":"

In-memory data store for the Veterinary Clinic example.

This module is NOT thread-safe and is intended for demos and scaffolds only.

It provides minimal, process-local data stores for the five veterinary clinic entities. Each store exposes standard CRUD operations backed by a simple dictionary.

This module intentionally avoids: - persistence - concurrency guarantees - transactional semantics - validation beyond what Pydantic provides

This module is not part of the openapi_first library API surface.

"},{"location":"openapi_first/templates/vet_app/main/","title":"Main","text":""},{"location":"openapi_first/templates/vet_app/main/#openapi_first.templates.vet_app.main","title":"openapi_first.templates.vet_app.main","text":"

Application entry point for an OpenAPI-first Veterinary Clinic service.

This module constructs a FastAPI application exclusively from an OpenAPI specification and a handler namespace, without using decorator-driven routing.

All HTTP routes, methods, request/response schemas, and operation bindings are defined in the OpenAPI document referenced by openapi_path. Python callables defined in the routes module are bound to OpenAPI operations strictly via operationId.

This module contains no routing logic, persistence concerns, or framework configuration beyond application assembly.

Design guarantees: - OpenAPI is the single source of truth - No undocumented routes can exist - Every OpenAPI operationId must resolve to exactly one handler - All contract violations fail at application startup

This file is intended to be used as the ASGI entry point.

Example

uvicorn main:app

"},{"location":"openapi_first/templates/vet_app/main/#openapi_first.templates.vet_app.main-classes","title":"Classes","text":""},{"location":"openapi_first/templates/vet_app/models/","title":"Models","text":""},{"location":"openapi_first/templates/vet_app/models/#openapi_first.templates.vet_app.models","title":"openapi_first.templates.vet_app.models","text":""},{"location":"openapi_first/templates/vet_app/routes/","title":"Routes","text":""},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes","title":"openapi_first.templates.vet_app.routes","text":"

Veterinary Clinic route handlers bound via OpenAPI operationId.

Handlers explicitly control HTTP response status codes to ensure runtime behavior matches the OpenAPI contract. Domain models defined using Pydantic are used for request and response payloads.

No routing decorators, path definitions, or implicit framework behavior appear in this module. All routing, HTTP methods, and schemas are defined in the OpenAPI specification.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes-functions","title":"Functions","text":""},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.create_appointment","title":"create_appointment","text":"
create_appointment(\n    payload: AppointmentCreate, response: Response\n)\n

Create an appointment.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.create_parent","title":"create_parent","text":"
create_parent(\n    payload: ParentCreate, response: Response\n) -> Parent\n

Create a parent.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.create_parent--parameters","title":"Parameters","text":"

payload : ParentCreate Parent data excluding the id field. response : Response Response object used to set the HTTP status code.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.create_parent--returns","title":"Returns","text":"

Parent The newly created parent.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.create_pet","title":"create_pet","text":"
create_pet(payload: PetCreate, response: Response)\n

Create a pet.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.create_treatment","title":"create_treatment","text":"
create_treatment(\n    payload: TreatmentCreate, response: Response\n)\n

Add a treatment (admin only).

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.create_vet","title":"create_vet","text":"
create_vet(payload: VetCreate, response: Response)\n

Create a vet.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.delete_appointment","title":"delete_appointment","text":"
delete_appointment(id: int, response: Response)\n

Delete an existing appointment.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.delete_parent","title":"delete_parent","text":"
delete_parent(id: int, response: Response) -> None\n

Delete an existing parent.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.delete_parent--parameters","title":"Parameters","text":"

id : int Identifier of the parent. response : Response Response object used to set the HTTP status code.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.delete_parent--raises","title":"Raises","text":"

HTTPException 404 if the parent does not exist.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.delete_pet","title":"delete_pet","text":"
delete_pet(id: int, response: Response)\n

Delete an existing pet.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.delete_treatment","title":"delete_treatment","text":"
delete_treatment(id: int, response: Response)\n

Delete an existing treatment.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.delete_vet","title":"delete_vet","text":"
delete_vet(id: int, response: Response)\n

Delete an existing vet.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.get_appointment","title":"get_appointment","text":"
get_appointment(id: int)\n

Retrieve a single appointment by ID.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.get_parent","title":"get_parent","text":"
get_parent(id: int) -> Parent\n

Retrieve a single parent by ID.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.get_parent--parameters","title":"Parameters","text":"

id : int Identifier of the parent.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.get_parent--returns","title":"Returns","text":"

Parent The requested parent.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.get_parent--raises","title":"Raises","text":"

HTTPException 404 if the parent does not exist.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.get_pet","title":"get_pet","text":"
get_pet(id: int)\n

Retrieve a single pet by ID.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.get_treatment","title":"get_treatment","text":"
get_treatment(id: int)\n

Retrieve a single treatment by ID.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.get_vet","title":"get_vet","text":"
get_vet(id: int)\n

Retrieve a single vet by ID.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.list_appointments","title":"list_appointments","text":"
list_appointments(\n    limit: int = 20,\n    offset: int = 0,\n    date: str = None,\n    vet: int = None,\n    pet: int = None,\n)\n

List appointments (paginated, filterable).

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.list_parents","title":"list_parents","text":"
list_parents(limit: int = 20, offset: int = 0) -> dict\n

List parents (paginated).

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.list_parents--parameters","title":"Parameters","text":"

limit : int Maximum number of records to return. offset : int Number of records to skip.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.list_parents--returns","title":"Returns","text":"

dict Paginated response with total and items.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.list_pets","title":"list_pets","text":"
list_pets(limit: int = 20, offset: int = 0)\n

List pets (paginated).

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.list_treatments","title":"list_treatments","text":"
list_treatments() -> list[Treatment]\n

List treatments (catalogue).

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.list_treatments--returns","title":"Returns","text":"

list[Treatment] A list of treatment domain objects.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.list_vets","title":"list_vets","text":"
list_vets(limit: int = 20, offset: int = 0)\n

List vets (paginated).

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.stream_actions","title":"stream_actions async","text":"
stream_actions(id: int)\n

Stream animal actions via SSE, scoped to a pet's species.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.update_appointment","title":"update_appointment","text":"
update_appointment(id: int, payload: AppointmentCreate)\n

Update an existing appointment.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.update_parent","title":"update_parent","text":"
update_parent(id: int, payload: ParentCreate) -> Parent\n

Update an existing parent.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.update_parent--parameters","title":"Parameters","text":"

id : int Identifier of the parent. payload : ParentCreate Updated parent data.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.update_parent--returns","title":"Returns","text":"

Parent The updated parent.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.update_parent--raises","title":"Raises","text":"

HTTPException 404 if the parent does not exist.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.update_pet","title":"update_pet","text":"
update_pet(id: int, payload: PetCreate)\n

Update an existing pet.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.update_treatment","title":"update_treatment","text":"
update_treatment(id: int, payload: TreatmentCreate)\n

Update an existing treatment.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.update_vet","title":"update_vet","text":"
update_vet(id: int, payload: VetCreate)\n

Update an existing vet.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.upload_pet_photo","title":"upload_pet_photo","text":"
upload_pet_photo(id: int, file: UploadFile) -> dict\n

Upload a pet photo.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.upload_pet_photo--parameters","title":"Parameters","text":"

id : int Identifier of the pet. file : UploadFile Image file to upload.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.upload_pet_photo--returns","title":"Returns","text":"

dict A confirmation with the pet ID.

"},{"location":"openapi_first/templates/vet_app/sse/","title":"Sse","text":""},{"location":"openapi_first/templates/vet_app/sse/#openapi_first.templates.vet_app.sse","title":"openapi_first.templates.vet_app.sse","text":""},{"location":"openapi_first/templates/vet_app/test_vet_app/","title":"Test Vet App","text":""},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app","title":"openapi_first.templates.vet_app.test_vet_app","text":"

End-to-end tests for the OpenAPI-first Veterinary Clinic example app.

These tests validate that all CRUD operations behave correctly against the in-memory mock data store using Pydantic models.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app-classes","title":"Classes","text":""},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app-functions","title":"Functions","text":""},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_create_parent","title":"test_create_parent","text":"
test_create_parent()\n

Creating a parent returns 201 with the created entity.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_create_pet","title":"test_create_pet","text":"
test_create_pet()\n

Creating a pet links FK references.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_create_treatment","title":"test_create_treatment","text":"
test_create_treatment()\n

Creating a treatment returns 201.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_create_vet","title":"test_create_vet","text":"
test_create_vet()\n

Creating a vet returns 201.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_delete_parent","title":"test_delete_parent","text":"
test_delete_parent()\n

Delete parent returns 204 and removes the entity.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_full_appointment_lifecycle","title":"test_full_appointment_lifecycle","text":"
test_full_appointment_lifecycle()\n

Create a parent, vet, treatment, pet, then an appointment.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_get_parent","title":"test_get_parent","text":"
test_get_parent()\n

Get parent by ID returns the entity.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_list_appointments","title":"test_list_appointments","text":"
test_list_appointments()\n

List appointments returns paginated response with filter params.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_list_parents","title":"test_list_parents","text":"
test_list_parents()\n

List parents returns paginated response.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_list_treatments","title":"test_list_treatments","text":"
test_list_treatments()\n

List treatments returns an array.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_list_vets","title":"test_list_vets","text":"
test_list_vets()\n

List vets returns paginated response.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_update_parent","title":"test_update_parent","text":"
test_update_parent()\n

Update parent replaces its values.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_upload_pet_photo","title":"test_upload_pet_photo","text":"
test_upload_pet_photo()\n

Upload pet photo returns 200.

"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"openapi_first","text":"
  • Openapi First
"},{"location":"#openapi_first","title":"openapi_first","text":""},{"location":"#openapi_first--summary","title":"Summary","text":"

FastAPI OpenAPI First \u2014 strict OpenAPI-first application bootstrap for FastAPI.

FastAPI OpenAPI First is a contract-first infrastructure library that enforces OpenAPI as the single source of truth for FastAPI services.

The library removes decorator-driven routing and replaces it with deterministic, spec-driven application assembly. Every HTTP route, method, and operation is defined in OpenAPI first and bound to Python handlers explicitly via operationId.

"},{"location":"#openapi_first--installation","title":"Installation","text":"

Install using pip:

pip install openapi-first\n

Or with Poetry:

poetry add openapi-first\n
"},{"location":"#openapi_first--quick-start","title":"Quick Start","text":"

Minimal OpenAPI-first FastAPI application:

from openapi_first import app\nimport my_service.routes as routes\n\napi = app.OpenAPIFirstApp(\n    openapi_path=\"openapi.yaml\",\n    routes_module=routes,\n    title=\"My Service\",\n    version=\"1.0.0\",\n)\n

OperationId-driven HTTP client:

from openapi_first.loader import load_openapi\nfrom openapi_first.client import OpenAPIClient\n\nspec = load_openapi(\"openapi.yaml\")\nclient = OpenAPIClient(spec)\n\nresponse = client.get_health()\n
"},{"location":"#openapi_first--architecture","title":"Architecture","text":"

The library is structured around four core responsibilities:

  • loader: Load and validate OpenAPI 3.x specifications (JSON/YAML).
  • binder: Bind OpenAPI operations to FastAPI routes via operationId.
  • app: OpenAPI-first FastAPI application bootstrap.
  • client: OpenAPI-first HTTP client driven by the same specification.
  • errors: Explicit error hierarchy for contract violations.
"},{"location":"#openapi_first--public-api","title":"Public API","text":"

The supported public API consists of the following top-level modules:

  • openapi_first.app
  • openapi_first.binder
  • openapi_first.loader
  • openapi_first.client
  • openapi_first.errors
"},{"location":"#openapi_first--design-guarantees","title":"Design Guarantees","text":"
  • OpenAPI is the single source of truth.
  • No undocumented routes can exist.
  • No OpenAPI operation can exist without a handler or client callable.
  • All contract violations fail at application startup or client creation.
  • No hidden FastAPI magic or implicit behavior.
  • Deterministic, testable application assembly.
"},{"location":"app/","title":"App","text":""},{"location":"app/#openapi_first.app","title":"openapi_first.app","text":""},{"location":"app/#openapi_first.app--summary","title":"Summary","text":"

OpenAPI-first application bootstrap for FastAPI.

This module provides OpenAPIFirstApp, a thin but strict abstraction that enforces OpenAPI as the single source of truth for a FastAPI service.

Notes

Core Principles:

- The OpenAPI specification (JSON or YAML) defines the entire API surface.\n- Every `operationId` in the OpenAPI spec must have a corresponding\n  Python handler function.\n- Handlers are plain Python callables (no FastAPI decorators).\n- FastAPI route registration is derived exclusively from the spec.\n- FastAPI's autogenerated OpenAPI schema is fully overridden.\n

Responsibilities:

- Loads and validates an OpenAPI 3.x specification.\n- Dynamically binds HTTP routes to handler functions using `operationId`.\n- Registers routes with FastAPI at application startup.\n- Ensures runtime behavior matches the OpenAPI contract exactly.\n

Constraints:

- This module intentionally does NOT:\n    - Generate OpenAPI specs.\n    - Generate client code.\n    - Introduce a new framework or lifecycle.\n    - Alter FastAPI dependency injection semantics.\n
"},{"location":"app/#openapi_first.app-classes","title":"Classes","text":""},{"location":"app/#openapi_first.app.OpenAPIFirstApp","title":"OpenAPIFirstApp","text":"
OpenAPIFirstApp(\n    *,\n    openapi_path: str,\n    routes_module: Any,\n    **fastapi_kwargs: Any\n)\n

Bases: FastAPI

FastAPI application enforcing OpenAPI-first design.

Parameters:

Name Type Description Default openapi_path str

Filesystem path to the OpenAPI 3.x specification file. This specification is treated as the authoritative API contract.

required routes_module ModuleType

Python module containing handler functions whose names correspond exactly to OpenAPI operationId values.

required **fastapi_kwargs Any

Additional keyword arguments passed directly to fastapi.FastAPI (e.g., title, version, middleware, lifespan handlers).

{}

Raises:

Type Description OpenAPIFirstError

If the OpenAPI specification is invalid, or if any declared operationId does not have a corresponding handler function.

Notes

Responsibilities:

- `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n  registration with OpenAPI-driven binding.\n- All routes are derived from the provided OpenAPI specification,\n  and each ``operationId`` is mapped to a Python function in the\n  supplied routes module.\n- Auth dependencies are auto-injected from the spec's\n  ``securitySchemes`` and per-operation ``security`` fields.\n

Guarantees:

- No route can exist without an OpenAPI declaration.\n- No OpenAPI operation can exist without a handler.\n- Swagger UI and ``/openapi.json`` always reflect the provided spec.\n- Handler functions remain framework-agnostic and testable.\n- Auth enforcement is driven entirely by the spec \u2014 no manual\n  middleware or decorators required.\n
Example
from openapi_first.app import OpenAPIFirstApp\nimport app.routes as routes\n\napp = OpenAPIFirstApp(\n    openapi_path=\"app/openapi.json\",\n    routes_module=routes,\n    title=\"Example Service\",\n)\n
"},{"location":"app/#openapi_first.app-functions","title":"Functions","text":""},{"location":"binder/","title":"Binder","text":""},{"location":"binder/#openapi_first.binder","title":"openapi_first.binder","text":""},{"location":"binder/#openapi_first.binder--summary","title":"Summary","text":"

OpenAPI-driven route binding for FastAPI.

This module is responsible for translating an OpenAPI 3.x specification into concrete FastAPI routes. It enforces a strict one-to-one mapping between OpenAPI operations and Python handler functions using operationId.

Notes

Core Responsibility:

- Read path + method definitions from an OpenAPI specification.\n- Resolve each `operationId` to a Python callable.\n- Register routes with FastAPI using `APIRoute`.\n- Fail fast when contract violations are detected.\n

Design Constraints:

- All routes MUST be declared in the OpenAPI specification.\n- All OpenAPI operations MUST define an `operationId`.\n- Every `operationId` MUST resolve to a handler function.\n- Handlers are plain Python callables (no decorators required).\n- No implicit route creation or inference is allowed.\n

Constraints:

- This module intentionally does NOT:\n    - Perform request or response validation.\n    - Generate Pydantic models.\n    - Modify FastAPI dependency injection.\n    - Interpret OpenAPI semantics beyond routing metadata.\n
"},{"location":"binder/#openapi_first.binder-classes","title":"Classes","text":""},{"location":"binder/#openapi_first.binder-functions","title":"Functions","text":""},{"location":"binder/#openapi_first.binder.bind_routes","title":"bind_routes","text":"
bind_routes(\n    app: FastAPI,\n    spec: dict[str, Any],\n    routes_module: ModuleType,\n    security_deps: dict[str, list[Any]] | None = None,\n) -> None\n

Bind OpenAPI operations to FastAPI routes.

Parameters:

Name Type Description Default app FastAPI

The FastAPI application instance to which routes will be added.

required spec dict[str, Any]

Parsed OpenAPI 3.x specification dictionary.

required routes_module ModuleType

Python module containing handler functions. Each handler's name MUST exactly match an OpenAPI operationId.

required security_deps dict[str, list[Any]] | None

Optional mapping of METHOD:/path \u2192 list[Depends(...)] generated from the spec's securitySchemes and per-operation security fields.

None

Raises:

Type Description MissingOperationHandler

If an operationId is missing from the spec or if no corresponding handler function exists in the routes module.

Notes

Responsibilities:

- Iterates through the OpenAPI specification paths and methods.\n- Resolves each ``operationId`` to a handler function, and registers\n  a corresponding ``APIRoute`` on the FastAPI application.\n- Injects FastAPI ``Depends()`` for each security requirement found\n  on the operation or inherited from the top-level ``security`` field.\n

Guarantees:

- Route registration is deterministic and spec-driven. No route\n  decorators are required or supported. Handler resolution errors\n  surface at application startup.\n
"},{"location":"cli/","title":"Cli","text":""},{"location":"cli/#openapi_first.cli","title":"openapi_first.cli","text":""},{"location":"cli/#openapi_first.cli--summary","title":"Summary","text":"

Command-line interface for FastAPI OpenAPI-first scaffolding utilities.

This CLI bootstraps OpenAPI-first FastAPI applications from versioned, bundled templates packaged with the library.

"},{"location":"cli/#openapi_first.cli-functions","title":"Functions","text":""},{"location":"cli/#openapi_first.cli.available_templates","title":"available_templates","text":"
available_templates() -> list[str]\n

Return a list of available application templates.

Returns:

Type Description list[str]

list[str]: Sorted list of template names found in the internal templates directory.

"},{"location":"cli/#openapi_first.cli.copy_template","title":"copy_template","text":"
copy_template(template: str, target_dir: Path) -> None\n

Copy a bundled OpenAPI-first application template into a directory.

Parameters:

Name Type Description Default template str

Name of the template to copy.

required target_dir Path

Filesystem path where the template should be copied.

required

Raises:

Type Description FileNotFoundError

If the requested template does not exist.

"},{"location":"client/","title":"Client","text":""},{"location":"client/#openapi_first.client","title":"openapi_first.client","text":""},{"location":"client/#openapi_first.client--summary","title":"Summary","text":"

OpenAPI-first HTTP client for contract-driven services.

This module provides OpenAPIClient, a thin, strict HTTP client that derives all callable operations directly from an OpenAPI 3.x specification.

It is the client counterpart to OpenAPIFirstApp.

Notes

Core Principles:

- The OpenAPI specification is the single source of truth\n- Each operationId becomes a callable Python method\n- No implicit schema mutation or inference\n- No code generation step\n- Minimal abstraction over httpx\n

Responsibilities:

- Parses an OpenAPI 3.x specification\n- Dynamically creates one callable per operationId\n- Enforces presence of servers, paths, and operationId\n- Formats path parameters safely\n- Handles JSON request bodies explicitly\n- Returns raw `httpx.Response` objects\n

Constraints:

- This module intentionally does NOT: Generate client code, validate request/response schemas, deserialize responses, retry requests, implement authentication helpers, or assume non-2xx responses are failures.\n
"},{"location":"client/#openapi_first.client-classes","title":"Classes","text":""},{"location":"client/#openapi_first.client.OpenAPIClient","title":"OpenAPIClient","text":"
OpenAPIClient(\n    spec: dict[str, Any],\n    base_url: str | None = None,\n    client: httpx.Client | None = None,\n)\n

OpenAPI-first HTTP client (httpx-based).

Parameters:

Name Type Description Default spec dict[str, Any]

Parsed OpenAPI 3.x specification.

required base_url str | None

Base URL of the target service. If omitted, the first entry in the OpenAPI servers list is used.

None client Client | None

Optional preconfigured httpx client instance.

None

Raises:

Type Description OpenAPIClientError

If no servers are defined, spec has no paths, operationIds are missing/duplicate, or required parameters are missing.

Notes

Responsibilities:

- This client derives all callable methods directly from an\n  OpenAPI 3.x specification. Each `operationId` becomes a method\n  on the client instance.\n

Guarantees:

- One callable per `operationId`.\n- Explicit parameters (path, query, headers, body).\n- No implicit schema inference or mutation.\n- Returns raw `httpx.Response` objects.\n- No response validation or deserialization.\n
Example
from openapi_first import loader, client\n\nspec = loader.load_openapi(\"openapi.yaml\")\n\napi = client.OpenAPIClient(\n    spec=spec,\n    base_url=\"http://localhost:8000\",\n)\n\n# Call operationId: getUser\nresponse = api.getUser(\n    path_params={\"user_id\": 123}\n)\n\nprint(response.status_code)\nprint(response.json())\n
"},{"location":"client/#openapi_first.client.OpenAPIClientError","title":"OpenAPIClientError","text":"

Bases: OpenAPIFirstError

Raised when an OpenAPI client operation fails.

"},{"location":"codegen/","title":"Codegen","text":""},{"location":"codegen/#openapi_first.codegen","title":"openapi_first.codegen","text":""},{"location":"codegen/#openapi_first.codegen--summary","title":"Summary","text":"

Core logic for generating Python source code from OpenAPI specifications.

This module provides reusable utilities for code generation, specifically generating Pydantic models and route handler stubs from OpenAPI 3.x schema definitions.

"},{"location":"codegen/#openapi_first.codegen-functions","title":"Functions","text":""},{"location":"codegen/#openapi_first.codegen.generate_models","title":"generate_models","text":"
generate_models(\n    spec_path: Path,\n    output_path: Path,\n    pydantic_version: int = 2,\n) -> None\n

Generate Pydantic models from an OpenAPI specification.

Parameters:

Name Type Description Default spec_path Path

Path to the OpenAPI specification file (YAML or JSON).

required output_path Path

Path where the generated Python code should be written.

required pydantic_version int

The Pydantic version to target (1 or 2). Defaults to 2.

2 Notes

Reusability: This function is designed to be used by the CLI and can be exposed as an MCP tool without modification.

"},{"location":"codegen/#openapi_first.codegen.generate_routes","title":"generate_routes","text":"
generate_routes(\n    spec_path: Path,\n    output_dir: Path,\n    *,\n    use_models: bool = False,\n    models_module: str = \"models\",\n    inject_request: bool = False,\n    package: bool = False\n) -> list[Path]\n

Generate route handler stubs from an OpenAPI specification.

Creates one route module per resource in output_dir. Resources are derived from the first path segment (e.g. /pets and /pets/{id} both group under pets) and sanitized to valid module identifiers.

Parameters:

Name Type Description Default spec_path Path

Path to the OpenAPI specification file (YAML or JSON).

required output_dir Path

Directory where the generated route files are written. Created automatically if it does not exist.

required use_models bool

If True, import Pydantic models from models_module for request-body schemas referenced via $ref.

False models_module str

Dotted Python module path from which to import models (e.g. \"models\", \"app.models\").

'models' inject_request bool

If True, prepend request: Request to every handler so the stub can later resolve services from request.app.state.

False package bool

If True, emit <resource>/routes.py packages (each with an __init__.py) instead of flat <resource>.py modules.

False

Returns:

Type Description list[Path]

list[Path]: Absolute paths of every generated route file.

Raises:

Type Description OpenAPISpecLoadError

If the spec cannot be loaded or validated.

ValueError

If any operation is missing operationId.

"},{"location":"codegen_routes/","title":"Codegen Routes","text":""},{"location":"codegen_routes/#openapi_first.codegen_routes","title":"openapi_first.codegen_routes","text":""},{"location":"codegen_routes/#openapi_first.codegen_routes--summary","title":"Summary","text":"

Route handler code generation from OpenAPI specifications.

This module generates Python route handler stubs from an OpenAPI 3.x specification. Each resource (derived from the first path segment) gets its own file under the output directory. Every OpenAPI operation must define an operationId, which becomes the handler function name.

Notes

Design constraints:

- ``operationId`` is required on every operation (matching\n  ``binder.bind_routes``).\n- Handlers are stubs raising ``NotImplementedError``.\n- Sub-resources (e.g. ``/pets/{id}/photo``) are grouped with their\n  parent resource (``pets``).\n- Parameter types and defaults are inferred from the spec.\n- ``response: Response`` is injected for non-200 success codes.\n- Resource names are sanitized to valid Python module identifiers,\n  so a path such as ``/fetch-requests`` yields ``fetch_requests.py``.\n- Passing ``inject_request=True`` prepends ``request: Request`` to\n  every handler (the composition-root / DI convention).\n- ``multipart/form-data`` and\n  ``application/x-www-form-urlencoded`` request bodies expand to one\n  parameter per schema property (``UploadFile`` / ``File`` / ``Form``)\n  instead of an opaque ``payload: dict``.\n- Passing ``package=True`` writes ``<resource>/routes.py`` (with\n  ``__init__.py`` sidecars) instead of a flat ``<resource>.py``.\n
"},{"location":"codegen_routes/#openapi_first.codegen_routes-functions","title":"Functions","text":""},{"location":"codegen_routes/#openapi_first.codegen_routes.generate_routes","title":"generate_routes","text":"
generate_routes(\n    spec_path: Path,\n    output_dir: Path,\n    *,\n    use_models: bool = False,\n    models_module: str = \"models\",\n    inject_request: bool = False,\n    package: bool = False\n) -> list[Path]\n

Generate route handler stubs from an OpenAPI specification.

Creates one route module per resource in output_dir. Resources are derived from the first path segment (e.g. /pets and /pets/{id} both group under pets) and sanitized to valid module identifiers.

Parameters:

Name Type Description Default spec_path Path

Path to the OpenAPI specification file (YAML or JSON).

required output_dir Path

Directory where the generated route files are written. Created automatically if it does not exist.

required use_models bool

If True, import Pydantic models from models_module for request-body schemas referenced via $ref.

False models_module str

Dotted Python module path from which to import models (e.g. \"models\", \"app.models\").

'models' inject_request bool

If True, prepend request: Request to every handler so the stub can later resolve services from request.app.state.

False package bool

If True, emit <resource>/routes.py packages (each with an __init__.py) instead of flat <resource>.py modules.

False

Returns:

Type Description list[Path]

list[Path]: Absolute paths of every generated route file.

Raises:

Type Description OpenAPISpecLoadError

If the spec cannot be loaded or validated.

ValueError

If any operation is missing operationId.

"},{"location":"errors/","title":"Errors","text":""},{"location":"errors/#openapi_first.errors","title":"openapi_first.errors","text":""},{"location":"errors/#openapi_first.errors--summary","title":"Summary","text":"

Exceptions for OpenAPI-first FastAPI applications.

This module defines a small hierarchy of explicit, intention-revealing exceptions used to signal contract violations between an OpenAPI specification and its Python implementation.

Notes

Design Principles:

- Errors represent programmer mistakes, not runtime conditions.\n- All errors are raised during application startup.\n- Messages are actionable and suitable for CI/CD output.\n- Exceptions are explicit rather than reused from generic built-ins.\n

These errors should normally cause immediate application failure.

"},{"location":"errors/#openapi_first.errors-classes","title":"Classes","text":""},{"location":"errors/#openapi_first.errors.MissingOperationHandler","title":"MissingOperationHandler","text":"
MissingOperationHandler(\n    *,\n    path: str,\n    method: str,\n    operation_id: str | None = None\n)\n

Bases: OpenAPIFirstError

Raised when an OpenAPI operation cannot be resolved to a handler.

Parameters:

Name Type Description Default path str

The HTTP path declared in the OpenAPI specification.

required method str

The HTTP method (as declared in the OpenAPI spec).

required operation_id str | None

The operationId declared in the OpenAPI spec, if present.

None Notes

Scenarios:

- An OpenAPI operation does not define an `operationId`.\n- An `operationId` is defined but no matching function exists in\n  the provided routes module.\n

Guarantees:

- This represents a violation of the OpenAPI-first contract and\n  indicates that the specification and implementation are out of\n  sync.\n
"},{"location":"errors/#openapi_first.errors.OpenAPIFirstError","title":"OpenAPIFirstError","text":"

Bases: Exception

Base exception for all OpenAPI-first enforcement errors.

Notes

Responsibilities:

- This exception exists to allow callers, test suites, and CI\n  pipelines to catch and distinguish OpenAPI contract violations\n  from unrelated runtime errors.\n- All exceptions raised by the OpenAPI-first core should inherit\n  from this type.\n
"},{"location":"loader/","title":"Loader","text":""},{"location":"loader/#openapi_first.loader","title":"openapi_first.loader","text":""},{"location":"loader/#openapi_first.loader--summary","title":"Summary","text":"

OpenAPI specification loading and validation utilities.

This module is responsible for loading an OpenAPI 3.x specification from disk and validating it before it is used by the application.

It enforces the principle that an invalid or malformed OpenAPI document must never reach the routing or runtime layers.

Notes

Design Principles:

- OpenAPI is treated as an authoritative contract.\n- Invalid specifications fail fast at application startup.\n- Supported formats are JSON and YAML.\n- Validation errors are surfaced clearly and early.\n

Constraints:

- This module intentionally does NOT:\n    - Modify the OpenAPI document.\n    - Infer missing fields.\n    - Generate models or code.\n    - Perform request/response validation at runtime.\n
"},{"location":"loader/#openapi_first.loader-classes","title":"Classes","text":""},{"location":"loader/#openapi_first.loader.OpenAPISpecLoadError","title":"OpenAPISpecLoadError","text":"

Bases: OpenAPIFirstError

Raised when an OpenAPI specification cannot be loaded or validated.

Notes

Guarantees:

- This error indicates that the OpenAPI document is unreadable,\n  malformed, or violates the OpenAPI 3.x specification.\n
"},{"location":"loader/#openapi_first.loader-functions","title":"Functions","text":""},{"location":"loader/#openapi_first.loader.load_openapi","title":"load_openapi","text":"
load_openapi(path: str | Path) -> dict[str, Any]\n

Load and validate an OpenAPI 3.x specification from disk.

Parameters:

Name Type Description Default path str | Path

Filesystem path to an OpenAPI specification file. Supported extensions: .json, .yaml, .yml.

required

Returns:

Type Description dict[str, Any]

dict[str, Any]: Parsed and validated OpenAPI specification.

Raises:

Type Description OpenAPISpecLoadError

If the file does not exist, cannot be parsed, or fails OpenAPI schema validation.

Notes

Guarantees:

- The specification is parsed based on file extension and validated\n  using a strict OpenAPI schema validator.\n- Any error results in an immediate exception, preventing\n  application startup.\n
"},{"location":"security/","title":"Security","text":""},{"location":"security/#openapi_first.security","title":"openapi_first.security","text":""},{"location":"security/#openapi_first.security--summary","title":"Summary","text":"

OpenAPI security scheme parsing and auto-generated auth dependencies.

This module reads securitySchemes and per-operation security from an OpenAPI spec, resolves {ENV_VAR} placeholders in x- extension fields, and generates FastAPI dependencies for token validation (e.g., Bearer JWT introspection).

"},{"location":"security/#openapi_first.security-functions","title":"Functions","text":""},{"location":"security/#openapi_first.security.make_security_dependencies","title":"make_security_dependencies","text":"
make_security_dependencies(\n    spec: dict[str, Any],\n    security_schemes: dict[str, dict[str, Any]],\n) -> dict[str, list[Any]]\n

Build a mapping of METHOD:/path \u2192 list of Depends(...).

The effective security for each operation is resolved by:

  1. Using the operation-level security field if present.
  2. Falling back to the top-level security field.
  3. An empty list means no auth required for that operation.
"},{"location":"security/#openapi_first.security.parse_security_schemes","title":"parse_security_schemes","text":"
parse_security_schemes(\n    spec: dict[str, Any],\n) -> dict[str, dict[str, Any]]\n

Extract and resolve environment variables in security schemes.

"},{"location":"openapi_first/","title":"Openapi First","text":"
  • App
  • Binder
  • Cli
  • Client
  • Codegen
  • Codegen Routes
  • Errors
  • Loader
  • Security
  • Templates
"},{"location":"openapi_first/#openapi_first","title":"openapi_first","text":""},{"location":"openapi_first/#openapi_first--summary","title":"Summary","text":"

FastAPI OpenAPI First \u2014 strict OpenAPI-first application bootstrap for FastAPI.

FastAPI OpenAPI First is a contract-first infrastructure library that enforces OpenAPI as the single source of truth for FastAPI services.

The library removes decorator-driven routing and replaces it with deterministic, spec-driven application assembly. Every HTTP route, method, and operation is defined in OpenAPI first and bound to Python handlers explicitly via operationId.

"},{"location":"openapi_first/#openapi_first--installation","title":"Installation","text":"

Install using pip:

pip install openapi-first\n

Or with Poetry:

poetry add openapi-first\n
"},{"location":"openapi_first/#openapi_first--quick-start","title":"Quick Start","text":"

Minimal OpenAPI-first FastAPI application:

from openapi_first import app\nimport my_service.routes as routes\n\napi = app.OpenAPIFirstApp(\n    openapi_path=\"openapi.yaml\",\n    routes_module=routes,\n    title=\"My Service\",\n    version=\"1.0.0\",\n)\n

OperationId-driven HTTP client:

from openapi_first.loader import load_openapi\nfrom openapi_first.client import OpenAPIClient\n\nspec = load_openapi(\"openapi.yaml\")\nclient = OpenAPIClient(spec)\n\nresponse = client.get_health()\n
"},{"location":"openapi_first/#openapi_first--architecture","title":"Architecture","text":"

The library is structured around four core responsibilities:

  • loader: Load and validate OpenAPI 3.x specifications (JSON/YAML).
  • binder: Bind OpenAPI operations to FastAPI routes via operationId.
  • app: OpenAPI-first FastAPI application bootstrap.
  • client: OpenAPI-first HTTP client driven by the same specification.
  • errors: Explicit error hierarchy for contract violations.
"},{"location":"openapi_first/#openapi_first--public-api","title":"Public API","text":"

The supported public API consists of the following top-level modules:

  • openapi_first.app
  • openapi_first.binder
  • openapi_first.loader
  • openapi_first.client
  • openapi_first.errors
"},{"location":"openapi_first/#openapi_first--design-guarantees","title":"Design Guarantees","text":"
  • OpenAPI is the single source of truth.
  • No undocumented routes can exist.
  • No OpenAPI operation can exist without a handler or client callable.
  • All contract violations fail at application startup or client creation.
  • No hidden FastAPI magic or implicit behavior.
  • Deterministic, testable application assembly.
"},{"location":"openapi_first/app/","title":"App","text":""},{"location":"openapi_first/app/#openapi_first.app","title":"openapi_first.app","text":""},{"location":"openapi_first/app/#openapi_first.app--summary","title":"Summary","text":"

OpenAPI-first application bootstrap for FastAPI.

This module provides OpenAPIFirstApp, a thin but strict abstraction that enforces OpenAPI as the single source of truth for a FastAPI service.

Notes

Core Principles:

- The OpenAPI specification (JSON or YAML) defines the entire API surface.\n- Every `operationId` in the OpenAPI spec must have a corresponding\n  Python handler function.\n- Handlers are plain Python callables (no FastAPI decorators).\n- FastAPI route registration is derived exclusively from the spec.\n- FastAPI's autogenerated OpenAPI schema is fully overridden.\n

Responsibilities:

- Loads and validates an OpenAPI 3.x specification.\n- Dynamically binds HTTP routes to handler functions using `operationId`.\n- Registers routes with FastAPI at application startup.\n- Ensures runtime behavior matches the OpenAPI contract exactly.\n

Constraints:

- This module intentionally does NOT:\n    - Generate OpenAPI specs.\n    - Generate client code.\n    - Introduce a new framework or lifecycle.\n    - Alter FastAPI dependency injection semantics.\n
"},{"location":"openapi_first/app/#openapi_first.app-classes","title":"Classes","text":""},{"location":"openapi_first/app/#openapi_first.app.OpenAPIFirstApp","title":"OpenAPIFirstApp","text":"
OpenAPIFirstApp(\n    *,\n    openapi_path: str,\n    routes_module: Any,\n    **fastapi_kwargs: Any\n)\n

Bases: FastAPI

FastAPI application enforcing OpenAPI-first design.

Parameters:

Name Type Description Default openapi_path str

Filesystem path to the OpenAPI 3.x specification file. This specification is treated as the authoritative API contract.

required routes_module ModuleType

Python module containing handler functions whose names correspond exactly to OpenAPI operationId values.

required **fastapi_kwargs Any

Additional keyword arguments passed directly to fastapi.FastAPI (e.g., title, version, middleware, lifespan handlers).

{}

Raises:

Type Description OpenAPIFirstError

If the OpenAPI specification is invalid, or if any declared operationId does not have a corresponding handler function.

Notes

Responsibilities:

- `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n  registration with OpenAPI-driven binding.\n- All routes are derived from the provided OpenAPI specification,\n  and each ``operationId`` is mapped to a Python function in the\n  supplied routes module.\n- Auth dependencies are auto-injected from the spec's\n  ``securitySchemes`` and per-operation ``security`` fields.\n

Guarantees:

- No route can exist without an OpenAPI declaration.\n- No OpenAPI operation can exist without a handler.\n- Swagger UI and ``/openapi.json`` always reflect the provided spec.\n- Handler functions remain framework-agnostic and testable.\n- Auth enforcement is driven entirely by the spec \u2014 no manual\n  middleware or decorators required.\n
Example
from openapi_first.app import OpenAPIFirstApp\nimport app.routes as routes\n\napp = OpenAPIFirstApp(\n    openapi_path=\"app/openapi.json\",\n    routes_module=routes,\n    title=\"Example Service\",\n)\n
"},{"location":"openapi_first/app/#openapi_first.app-functions","title":"Functions","text":""},{"location":"openapi_first/binder/","title":"Binder","text":""},{"location":"openapi_first/binder/#openapi_first.binder","title":"openapi_first.binder","text":""},{"location":"openapi_first/binder/#openapi_first.binder--summary","title":"Summary","text":"

OpenAPI-driven route binding for FastAPI.

This module is responsible for translating an OpenAPI 3.x specification into concrete FastAPI routes. It enforces a strict one-to-one mapping between OpenAPI operations and Python handler functions using operationId.

Notes

Core Responsibility:

- Read path + method definitions from an OpenAPI specification.\n- Resolve each `operationId` to a Python callable.\n- Register routes with FastAPI using `APIRoute`.\n- Fail fast when contract violations are detected.\n

Design Constraints:

- All routes MUST be declared in the OpenAPI specification.\n- All OpenAPI operations MUST define an `operationId`.\n- Every `operationId` MUST resolve to a handler function.\n- Handlers are plain Python callables (no decorators required).\n- No implicit route creation or inference is allowed.\n

Constraints:

- This module intentionally does NOT:\n    - Perform request or response validation.\n    - Generate Pydantic models.\n    - Modify FastAPI dependency injection.\n    - Interpret OpenAPI semantics beyond routing metadata.\n
"},{"location":"openapi_first/binder/#openapi_first.binder-classes","title":"Classes","text":""},{"location":"openapi_first/binder/#openapi_first.binder-functions","title":"Functions","text":""},{"location":"openapi_first/binder/#openapi_first.binder.bind_routes","title":"bind_routes","text":"
bind_routes(\n    app: FastAPI,\n    spec: dict[str, Any],\n    routes_module: ModuleType,\n    security_deps: dict[str, list[Any]] | None = None,\n) -> None\n

Bind OpenAPI operations to FastAPI routes.

Parameters:

Name Type Description Default app FastAPI

The FastAPI application instance to which routes will be added.

required spec dict[str, Any]

Parsed OpenAPI 3.x specification dictionary.

required routes_module ModuleType

Python module containing handler functions. Each handler's name MUST exactly match an OpenAPI operationId.

required security_deps dict[str, list[Any]] | None

Optional mapping of METHOD:/path \u2192 list[Depends(...)] generated from the spec's securitySchemes and per-operation security fields.

None

Raises:

Type Description MissingOperationHandler

If an operationId is missing from the spec or if no corresponding handler function exists in the routes module.

Notes

Responsibilities:

- Iterates through the OpenAPI specification paths and methods.\n- Resolves each ``operationId`` to a handler function, and registers\n  a corresponding ``APIRoute`` on the FastAPI application.\n- Injects FastAPI ``Depends()`` for each security requirement found\n  on the operation or inherited from the top-level ``security`` field.\n

Guarantees:

- Route registration is deterministic and spec-driven. No route\n  decorators are required or supported. Handler resolution errors\n  surface at application startup.\n
"},{"location":"openapi_first/cli/","title":"Cli","text":""},{"location":"openapi_first/cli/#openapi_first.cli","title":"openapi_first.cli","text":""},{"location":"openapi_first/cli/#openapi_first.cli--summary","title":"Summary","text":"

Command-line interface for FastAPI OpenAPI-first scaffolding utilities.

This CLI bootstraps OpenAPI-first FastAPI applications from versioned, bundled templates packaged with the library.

"},{"location":"openapi_first/cli/#openapi_first.cli-functions","title":"Functions","text":""},{"location":"openapi_first/cli/#openapi_first.cli.available_templates","title":"available_templates","text":"
available_templates() -> list[str]\n

Return a list of available application templates.

Returns:

Type Description list[str]

list[str]: Sorted list of template names found in the internal templates directory.

"},{"location":"openapi_first/cli/#openapi_first.cli.copy_template","title":"copy_template","text":"
copy_template(template: str, target_dir: Path) -> None\n

Copy a bundled OpenAPI-first application template into a directory.

Parameters:

Name Type Description Default template str

Name of the template to copy.

required target_dir Path

Filesystem path where the template should be copied.

required

Raises:

Type Description FileNotFoundError

If the requested template does not exist.

"},{"location":"openapi_first/client/","title":"Client","text":""},{"location":"openapi_first/client/#openapi_first.client","title":"openapi_first.client","text":""},{"location":"openapi_first/client/#openapi_first.client--summary","title":"Summary","text":"

OpenAPI-first HTTP client for contract-driven services.

This module provides OpenAPIClient, a thin, strict HTTP client that derives all callable operations directly from an OpenAPI 3.x specification.

It is the client counterpart to OpenAPIFirstApp.

Notes

Core Principles:

- The OpenAPI specification is the single source of truth\n- Each operationId becomes a callable Python method\n- No implicit schema mutation or inference\n- No code generation step\n- Minimal abstraction over httpx\n

Responsibilities:

- Parses an OpenAPI 3.x specification\n- Dynamically creates one callable per operationId\n- Enforces presence of servers, paths, and operationId\n- Formats path parameters safely\n- Handles JSON request bodies explicitly\n- Returns raw `httpx.Response` objects\n

Constraints:

- This module intentionally does NOT: Generate client code, validate request/response schemas, deserialize responses, retry requests, implement authentication helpers, or assume non-2xx responses are failures.\n
"},{"location":"openapi_first/client/#openapi_first.client-classes","title":"Classes","text":""},{"location":"openapi_first/client/#openapi_first.client.OpenAPIClient","title":"OpenAPIClient","text":"
OpenAPIClient(\n    spec: dict[str, Any],\n    base_url: str | None = None,\n    client: httpx.Client | None = None,\n)\n

OpenAPI-first HTTP client (httpx-based).

Parameters:

Name Type Description Default spec dict[str, Any]

Parsed OpenAPI 3.x specification.

required base_url str | None

Base URL of the target service. If omitted, the first entry in the OpenAPI servers list is used.

None client Client | None

Optional preconfigured httpx client instance.

None

Raises:

Type Description OpenAPIClientError

If no servers are defined, spec has no paths, operationIds are missing/duplicate, or required parameters are missing.

Notes

Responsibilities:

- This client derives all callable methods directly from an\n  OpenAPI 3.x specification. Each `operationId` becomes a method\n  on the client instance.\n

Guarantees:

- One callable per `operationId`.\n- Explicit parameters (path, query, headers, body).\n- No implicit schema inference or mutation.\n- Returns raw `httpx.Response` objects.\n- No response validation or deserialization.\n
Example
from openapi_first import loader, client\n\nspec = loader.load_openapi(\"openapi.yaml\")\n\napi = client.OpenAPIClient(\n    spec=spec,\n    base_url=\"http://localhost:8000\",\n)\n\n# Call operationId: getUser\nresponse = api.getUser(\n    path_params={\"user_id\": 123}\n)\n\nprint(response.status_code)\nprint(response.json())\n
"},{"location":"openapi_first/client/#openapi_first.client.OpenAPIClientError","title":"OpenAPIClientError","text":"

Bases: OpenAPIFirstError

Raised when an OpenAPI client operation fails.

"},{"location":"openapi_first/codegen/","title":"Codegen","text":""},{"location":"openapi_first/codegen/#openapi_first.codegen","title":"openapi_first.codegen","text":""},{"location":"openapi_first/codegen/#openapi_first.codegen--summary","title":"Summary","text":"

Core logic for generating Python source code from OpenAPI specifications.

This module provides reusable utilities for code generation, specifically generating Pydantic models and route handler stubs from OpenAPI 3.x schema definitions.

"},{"location":"openapi_first/codegen/#openapi_first.codegen-functions","title":"Functions","text":""},{"location":"openapi_first/codegen/#openapi_first.codegen.generate_models","title":"generate_models","text":"
generate_models(\n    spec_path: Path,\n    output_path: Path,\n    pydantic_version: int = 2,\n) -> None\n

Generate Pydantic models from an OpenAPI specification.

Parameters:

Name Type Description Default spec_path Path

Path to the OpenAPI specification file (YAML or JSON).

required output_path Path

Path where the generated Python code should be written.

required pydantic_version int

The Pydantic version to target (1 or 2). Defaults to 2.

2 Notes

Reusability: This function is designed to be used by the CLI and can be exposed as an MCP tool without modification.

"},{"location":"openapi_first/codegen/#openapi_first.codegen.generate_routes","title":"generate_routes","text":"
generate_routes(\n    spec_path: Path,\n    output_dir: Path,\n    *,\n    use_models: bool = False,\n    models_module: str = \"models\",\n    inject_request: bool = False,\n    package: bool = False\n) -> list[Path]\n

Generate route handler stubs from an OpenAPI specification.

Creates one route module per resource in output_dir. Resources are derived from the first path segment (e.g. /pets and /pets/{id} both group under pets) and sanitized to valid module identifiers.

Parameters:

Name Type Description Default spec_path Path

Path to the OpenAPI specification file (YAML or JSON).

required output_dir Path

Directory where the generated route files are written. Created automatically if it does not exist.

required use_models bool

If True, import Pydantic models from models_module for request-body schemas referenced via $ref.

False models_module str

Dotted Python module path from which to import models (e.g. \"models\", \"app.models\").

'models' inject_request bool

If True, prepend request: Request to every handler so the stub can later resolve services from request.app.state.

False package bool

If True, emit <resource>/routes.py packages (each with an __init__.py) instead of flat <resource>.py modules.

False

Returns:

Type Description list[Path]

list[Path]: Absolute paths of every generated route file.

Raises:

Type Description OpenAPISpecLoadError

If the spec cannot be loaded or validated.

ValueError

If any operation is missing operationId.

"},{"location":"openapi_first/codegen_routes/","title":"Codegen Routes","text":""},{"location":"openapi_first/codegen_routes/#openapi_first.codegen_routes","title":"openapi_first.codegen_routes","text":""},{"location":"openapi_first/codegen_routes/#openapi_first.codegen_routes--summary","title":"Summary","text":"

Route handler code generation from OpenAPI specifications.

This module generates Python route handler stubs from an OpenAPI 3.x specification. Each resource (derived from the first path segment) gets its own file under the output directory. Every OpenAPI operation must define an operationId, which becomes the handler function name.

Notes

Design constraints:

- ``operationId`` is required on every operation (matching\n  ``binder.bind_routes``).\n- Handlers are stubs raising ``NotImplementedError``.\n- Sub-resources (e.g. ``/pets/{id}/photo``) are grouped with their\n  parent resource (``pets``).\n- Parameter types and defaults are inferred from the spec.\n- ``response: Response`` is injected for non-200 success codes.\n- Resource names are sanitized to valid Python module identifiers,\n  so a path such as ``/fetch-requests`` yields ``fetch_requests.py``.\n- Passing ``inject_request=True`` prepends ``request: Request`` to\n  every handler (the composition-root / DI convention).\n- ``multipart/form-data`` and\n  ``application/x-www-form-urlencoded`` request bodies expand to one\n  parameter per schema property (``UploadFile`` / ``File`` / ``Form``)\n  instead of an opaque ``payload: dict``.\n- Passing ``package=True`` writes ``<resource>/routes.py`` (with\n  ``__init__.py`` sidecars) instead of a flat ``<resource>.py``.\n
"},{"location":"openapi_first/codegen_routes/#openapi_first.codegen_routes-functions","title":"Functions","text":""},{"location":"openapi_first/codegen_routes/#openapi_first.codegen_routes.generate_routes","title":"generate_routes","text":"
generate_routes(\n    spec_path: Path,\n    output_dir: Path,\n    *,\n    use_models: bool = False,\n    models_module: str = \"models\",\n    inject_request: bool = False,\n    package: bool = False\n) -> list[Path]\n

Generate route handler stubs from an OpenAPI specification.

Creates one route module per resource in output_dir. Resources are derived from the first path segment (e.g. /pets and /pets/{id} both group under pets) and sanitized to valid module identifiers.

Parameters:

Name Type Description Default spec_path Path

Path to the OpenAPI specification file (YAML or JSON).

required output_dir Path

Directory where the generated route files are written. Created automatically if it does not exist.

required use_models bool

If True, import Pydantic models from models_module for request-body schemas referenced via $ref.

False models_module str

Dotted Python module path from which to import models (e.g. \"models\", \"app.models\").

'models' inject_request bool

If True, prepend request: Request to every handler so the stub can later resolve services from request.app.state.

False package bool

If True, emit <resource>/routes.py packages (each with an __init__.py) instead of flat <resource>.py modules.

False

Returns:

Type Description list[Path]

list[Path]: Absolute paths of every generated route file.

Raises:

Type Description OpenAPISpecLoadError

If the spec cannot be loaded or validated.

ValueError

If any operation is missing operationId.

"},{"location":"openapi_first/errors/","title":"Errors","text":""},{"location":"openapi_first/errors/#openapi_first.errors","title":"openapi_first.errors","text":""},{"location":"openapi_first/errors/#openapi_first.errors--summary","title":"Summary","text":"

Exceptions for OpenAPI-first FastAPI applications.

This module defines a small hierarchy of explicit, intention-revealing exceptions used to signal contract violations between an OpenAPI specification and its Python implementation.

Notes

Design Principles:

- Errors represent programmer mistakes, not runtime conditions.\n- All errors are raised during application startup.\n- Messages are actionable and suitable for CI/CD output.\n- Exceptions are explicit rather than reused from generic built-ins.\n

These errors should normally cause immediate application failure.

"},{"location":"openapi_first/errors/#openapi_first.errors-classes","title":"Classes","text":""},{"location":"openapi_first/errors/#openapi_first.errors.MissingOperationHandler","title":"MissingOperationHandler","text":"
MissingOperationHandler(\n    *,\n    path: str,\n    method: str,\n    operation_id: str | None = None\n)\n

Bases: OpenAPIFirstError

Raised when an OpenAPI operation cannot be resolved to a handler.

Parameters:

Name Type Description Default path str

The HTTP path declared in the OpenAPI specification.

required method str

The HTTP method (as declared in the OpenAPI spec).

required operation_id str | None

The operationId declared in the OpenAPI spec, if present.

None Notes

Scenarios:

- An OpenAPI operation does not define an `operationId`.\n- An `operationId` is defined but no matching function exists in\n  the provided routes module.\n

Guarantees:

- This represents a violation of the OpenAPI-first contract and\n  indicates that the specification and implementation are out of\n  sync.\n
"},{"location":"openapi_first/errors/#openapi_first.errors.OpenAPIFirstError","title":"OpenAPIFirstError","text":"

Bases: Exception

Base exception for all OpenAPI-first enforcement errors.

Notes

Responsibilities:

- This exception exists to allow callers, test suites, and CI\n  pipelines to catch and distinguish OpenAPI contract violations\n  from unrelated runtime errors.\n- All exceptions raised by the OpenAPI-first core should inherit\n  from this type.\n
"},{"location":"openapi_first/loader/","title":"Loader","text":""},{"location":"openapi_first/loader/#openapi_first.loader","title":"openapi_first.loader","text":""},{"location":"openapi_first/loader/#openapi_first.loader--summary","title":"Summary","text":"

OpenAPI specification loading and validation utilities.

This module is responsible for loading an OpenAPI 3.x specification from disk and validating it before it is used by the application.

It enforces the principle that an invalid or malformed OpenAPI document must never reach the routing or runtime layers.

Notes

Design Principles:

- OpenAPI is treated as an authoritative contract.\n- Invalid specifications fail fast at application startup.\n- Supported formats are JSON and YAML.\n- Validation errors are surfaced clearly and early.\n

Constraints:

- This module intentionally does NOT:\n    - Modify the OpenAPI document.\n    - Infer missing fields.\n    - Generate models or code.\n    - Perform request/response validation at runtime.\n
"},{"location":"openapi_first/loader/#openapi_first.loader-classes","title":"Classes","text":""},{"location":"openapi_first/loader/#openapi_first.loader.OpenAPISpecLoadError","title":"OpenAPISpecLoadError","text":"

Bases: OpenAPIFirstError

Raised when an OpenAPI specification cannot be loaded or validated.

Notes

Guarantees:

- This error indicates that the OpenAPI document is unreadable,\n  malformed, or violates the OpenAPI 3.x specification.\n
"},{"location":"openapi_first/loader/#openapi_first.loader-functions","title":"Functions","text":""},{"location":"openapi_first/loader/#openapi_first.loader.load_openapi","title":"load_openapi","text":"
load_openapi(path: str | Path) -> dict[str, Any]\n

Load and validate an OpenAPI 3.x specification from disk.

Parameters:

Name Type Description Default path str | Path

Filesystem path to an OpenAPI specification file. Supported extensions: .json, .yaml, .yml.

required

Returns:

Type Description dict[str, Any]

dict[str, Any]: Parsed and validated OpenAPI specification.

Raises:

Type Description OpenAPISpecLoadError

If the file does not exist, cannot be parsed, or fails OpenAPI schema validation.

Notes

Guarantees:

- The specification is parsed based on file extension and validated\n  using a strict OpenAPI schema validator.\n- Any error results in an immediate exception, preventing\n  application startup.\n
"},{"location":"openapi_first/security/","title":"Security","text":""},{"location":"openapi_first/security/#openapi_first.security","title":"openapi_first.security","text":""},{"location":"openapi_first/security/#openapi_first.security--summary","title":"Summary","text":"

OpenAPI security scheme parsing and auto-generated auth dependencies.

This module reads securitySchemes and per-operation security from an OpenAPI spec, resolves {ENV_VAR} placeholders in x- extension fields, and generates FastAPI dependencies for token validation (e.g., Bearer JWT introspection).

"},{"location":"openapi_first/security/#openapi_first.security-functions","title":"Functions","text":""},{"location":"openapi_first/security/#openapi_first.security.make_security_dependencies","title":"make_security_dependencies","text":"
make_security_dependencies(\n    spec: dict[str, Any],\n    security_schemes: dict[str, dict[str, Any]],\n) -> dict[str, list[Any]]\n

Build a mapping of METHOD:/path \u2192 list of Depends(...).

The effective security for each operation is resolved by:

  1. Using the operation-level security field if present.
  2. Falling back to the top-level security field.
  3. An empty list means no auth required for that operation.
"},{"location":"openapi_first/security/#openapi_first.security.parse_security_schemes","title":"parse_security_schemes","text":"
parse_security_schemes(\n    spec: dict[str, Any],\n) -> dict[str, dict[str, Any]]\n

Extract and resolve environment variables in security schemes.

"},{"location":"openapi_first/templates/","title":"Templates","text":"
  • Crud App
  • Health App
  • Model App
  • Vet App
"},{"location":"openapi_first/templates/#openapi_first.templates","title":"openapi_first.templates","text":"

Application templates for FastAPI OpenAPI First.

This package contains example and scaffolding templates intended to be copied into user projects via the openapi-first CLI.

Templates in this package are: - Reference implementations of OpenAPI-first services - Not part of the openapi_first public or internal API - Not intended to be imported as runtime dependencies

The presence of this file exists solely to: - Mark the directory as an explicit Python package - Enable deterministic tooling behavior (documentation, packaging) - Avoid accidental traversal of non-package directories

No code in this package should be imported by library consumers.

"},{"location":"openapi_first/templates/crud_app/","title":"Crud App","text":"
  • Data
  • Main
  • Routes
  • Test Crud App
"},{"location":"openapi_first/templates/crud_app/#openapi_first.templates.crud_app","title":"openapi_first.templates.crud_app","text":"

OpenAPI-first CRUD application template.

This package contains a complete, minimal example of an OpenAPI-first CRUD service built using the openapi_first library.

The application is assembled exclusively from: - an OpenAPI specification (openapi.yaml) - a handler namespace implementing CRUD operations (routes) - an in-memory mock data store (data)

All HTTP routes, methods, schemas, and operation bindings are defined in the OpenAPI specification and enforced at application startup. No decorator-driven routing or implicit framework behavior is used.

This template demonstrates: - operationId-driven server-side route binding - explicit HTTP status code control in handlers - operationId-driven client usage against the same OpenAPI contract - end-to-end validation using in-memory data and tests

"},{"location":"openapi_first/templates/crud_app/#openapi_first.templates.crud_app--scaffolding-via-cli","title":"Scaffolding via CLI","text":"

Create a new CRUD example service using the bundled template:

openapi-first scaffold crud_app\n

Create the service in a custom directory:

openapi-first scaffold crud_app my-crud-service\n

List all available application templates:

openapi-first scaffold --list\n

The CLI copies template files verbatim into the target directory. No code is generated or modified beyond the copied scaffold.

"},{"location":"openapi_first/templates/crud_app/#openapi_first.templates.crud_app--client-usage-example","title":"Client Usage Example","text":"

The same OpenAPI specification used by the server can be used to construct a strict, operationId-driven HTTP client.

Example client calls for CRUD operations:

from openapi_first.loader import load_openapi\nfrom openapi_first.client import OpenAPIClient\n\nspec = load_openapi(\"openapi.yaml\")\nclient = OpenAPIClient(spec)\n\n# List items\nresponse = client.list_items()\n\n# Get item by ID\nresponse = client.get_item(\n    path_params={\"item_id\": 1}\n)\n\n# Create item\nresponse = client.create_item(\n    body={\"name\": \"Orange\", \"price\": 0.8}\n)\n\n# Update item\nresponse = client.update_item(\n    path_params={\"item_id\": 1},\n    body={\"name\": \"Green Apple\", \"price\": 0.6},\n)\n\n# Delete item\nresponse = client.delete_item(\n    path_params={\"item_id\": 1}\n)\n

Client guarantees: - One callable per OpenAPI operationId - No hardcoded URLs or HTTP methods in user code - Path and request parameters must match the OpenAPI specification - Invalid or incomplete OpenAPI specs fail at client construction time

"},{"location":"openapi_first/templates/crud_app/#openapi_first.templates.crud_app--non-goals","title":"Non-Goals","text":"

This template is intentionally minimal and is NOT: - production-ready - persistent or concurrency-safe - a reference architecture for data storage

It exists solely as a copyable example for learning, testing, and bootstrapping OpenAPI-first services.

This package is not part of the openapi_first library API surface.

"},{"location":"openapi_first/templates/crud_app/data/","title":"Data","text":""},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data","title":"openapi_first.templates.crud_app.data","text":"

In-memory mock data store for CRUD example.

This module intentionally avoids persistence and concurrency guarantees. It is suitable for demos, tests, and scaffolding only.

It intentionally avoids - persistence - concurrency guarantees - validation - error handling

The implementation is suitable for: - demonstrations - tests - scaffolding and example services

It is explicitly NOT suitable for production use.

This module is not part of the openapi_first library API surface.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data-functions","title":"Functions","text":""},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.create_item","title":"create_item","text":"
create_item(payload: dict) -> dict\n

Create a new item in the data store.

A new integer ID is assigned automatically. No validation is performed on the provided payload.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.create_item--parameters","title":"Parameters","text":"

payload : dict Item attributes excluding the id field.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.create_item--returns","title":"Returns","text":"

dict The newly created item, including its assigned ID.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.delete_item","title":"delete_item","text":"
delete_item(item_id: int)\n

Remove an item from the data store.

This function assumes the item exists and will raise KeyError if the ID is not present.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.delete_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to delete.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.get_item","title":"get_item","text":"
get_item(item_id: int) -> dict\n

Retrieve a single item by ID.

This function assumes the item exists and will raise KeyError if the ID is not present in the store.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.get_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to retrieve.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.get_item--returns","title":"Returns","text":"

dict The stored item representation.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.list_items","title":"list_items","text":"
list_items() -> list[dict]\n

Return all items in the data store.

This function performs no filtering, pagination, or sorting. The returned collection reflects the current in-memory state.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.list_items--returns","title":"Returns","text":"

list[dict] A list of item representations.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.update_item","title":"update_item","text":"
update_item(item_id: int, payload: dict) -> dict\n

Replace an existing item in the data store.

This function overwrites the existing item entirely and does not perform partial updates or validation. If the item does not exist, it will be created implicitly.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.update_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to update. payload : dict Item attributes excluding the id field.

"},{"location":"openapi_first/templates/crud_app/data/#openapi_first.templates.crud_app.data.update_item--returns","title":"Returns","text":"

dict The updated item representation.

"},{"location":"openapi_first/templates/crud_app/main/","title":"Main","text":""},{"location":"openapi_first/templates/crud_app/main/#openapi_first.templates.crud_app.main","title":"openapi_first.templates.crud_app.main","text":"

Application entry point for an OpenAPI-first CRUD example service.

This module constructs a FastAPI application exclusively from an OpenAPI specification and a handler namespace, without using decorator-driven routing.

All HTTP routes, methods, request/response schemas, and operation bindings are defined in the OpenAPI document referenced by openapi_path. Python callables defined in the routes module are bound to OpenAPI operations strictly via operationId.

This module contains no routing logic, persistence concerns, or framework configuration beyond application assembly.

Design guarantees: - OpenAPI is the single source of truth - No undocumented routes can exist - Every OpenAPI operationId must resolve to exactly one handler - All contract violations fail at application startup

This file is intended to be used as the ASGI entry point.

Example

uvicorn main:app

"},{"location":"openapi_first/templates/crud_app/main/#openapi_first.templates.crud_app.main-classes","title":"Classes","text":""},{"location":"openapi_first/templates/crud_app/routes/","title":"Routes","text":""},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes","title":"openapi_first.templates.crud_app.routes","text":"

CRUD route handlers bound via OpenAPI operationId.

These handlers explicitly control HTTP status codes to ensure runtime behavior matches the OpenAPI contract.

This module defines OpenAPI-bound operation handlers for a simple CRUD service. Functions in this module are bound to HTTP routes exclusively via OpenAPI operationId values.

Handlers explicitly control HTTP response status codes to ensure runtime behavior matches the OpenAPI contract. Error conditions are translated into explicit HTTP responses rather than relying on implicit framework behavior.

No routing decorators or path definitions appear in this module. All routing, HTTP methods, and schemas are defined in the OpenAPI specification.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes-functions","title":"Functions","text":""},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.create_item","title":"create_item","text":"
create_item(payload: dict, response: Response) -> dict\n

Create a new item.

Implements the OpenAPI operation identified by operationId: create_item.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.create_item--parameters","title":"Parameters","text":"

payload : dict Item attributes excluding the id field. response : Response Response object used to set the HTTP status code.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.create_item--returns","title":"Returns","text":"

dict The newly created item.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.delete_item","title":"delete_item","text":"
delete_item(item_id: int, response: Response) -> None\n

Delete an existing item.

Implements the OpenAPI operation identified by operationId: delete_item.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.delete_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to delete. response : Response Response object used to set the HTTP status code.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.delete_item--returns","title":"Returns","text":"

None No content.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.delete_item--raises","title":"Raises","text":"

HTTPException 404 if the item does not exist.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.get_item","title":"get_item","text":"
get_item(item_id: int) -> dict\n

Retrieve a single item by ID.

Implements the OpenAPI operation identified by operationId: get_item.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.get_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to retrieve.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.get_item--returns","title":"Returns","text":"

dict The requested item.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.get_item--raises","title":"Raises","text":"

HTTPException 404 if the item does not exist.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.list_items","title":"list_items","text":"
list_items() -> list[dict]\n

List all items.

Implements the OpenAPI operation identified by operationId: list_items.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.list_items--returns","title":"Returns","text":"

list[dict] A list of item representations.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.update_item","title":"update_item","text":"
update_item(item_id: int, payload: dict) -> dict\n

Update an existing item.

Implements the OpenAPI operation identified by operationId: update_item.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.update_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to update. payload : dict Item attributes excluding the id field.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.update_item--returns","title":"Returns","text":"

dict The updated item.

"},{"location":"openapi_first/templates/crud_app/routes/#openapi_first.templates.crud_app.routes.update_item--raises","title":"Raises","text":"

HTTPException 404 if the item does not exist.

"},{"location":"openapi_first/templates/crud_app/test_crud_app/","title":"Test Crud App","text":""},{"location":"openapi_first/templates/crud_app/test_crud_app/#openapi_first.templates.crud_app.test_crud_app","title":"openapi_first.templates.crud_app.test_crud_app","text":"

End-to-end tests for the OpenAPI-first CRUD example app.

These tests validate that all CRUD operations behave correctly against the in-memory mock data store. - OpenAPI specification loading - OperationId-driven route binding on the server - OperationId-driven client invocation - Correct HTTP status codes and response payloads

The tests exercise all CRUD operations against an in-memory mock data store and assume deterministic behavior within a single process.

The tests assume: - OpenAPI-first route binding - In-memory storage (no persistence guarantees) - Deterministic behavior in a single process - One-to-one correspondence between OpenAPI operationId values and server/client callables

"},{"location":"openapi_first/templates/crud_app/test_crud_app/#openapi_first.templates.crud_app.test_crud_app-classes","title":"Classes","text":""},{"location":"openapi_first/templates/crud_app/test_crud_app/#openapi_first.templates.crud_app.test_crud_app-functions","title":"Functions","text":""},{"location":"openapi_first/templates/crud_app/test_crud_app/#openapi_first.templates.crud_app.test_crud_app.test_create_item","title":"test_create_item","text":"
test_create_item()\n

Creating a new item should return the created entity.

"},{"location":"openapi_first/templates/crud_app/test_crud_app/#openapi_first.templates.crud_app.test_crud_app.test_delete_item","title":"test_delete_item","text":"
test_delete_item()\n

Deleting an item should remove it from the store.

"},{"location":"openapi_first/templates/crud_app/test_crud_app/#openapi_first.templates.crud_app.test_crud_app.test_get_item","title":"test_get_item","text":"
test_get_item()\n

Existing item should be retrievable by ID.

"},{"location":"openapi_first/templates/crud_app/test_crud_app/#openapi_first.templates.crud_app.test_crud_app.test_list_items_initial","title":"test_list_items_initial","text":"
test_list_items_initial()\n

Initial items should be present.

"},{"location":"openapi_first/templates/crud_app/test_crud_app/#openapi_first.templates.crud_app.test_crud_app.test_update_item","title":"test_update_item","text":"
test_update_item()\n

Updating an item should replace its values.

"},{"location":"openapi_first/templates/health_app/","title":"Health App","text":"
  • Main
  • Routes
"},{"location":"openapi_first/templates/health_app/#openapi_first.templates.health_app","title":"openapi_first.templates.health_app","text":"

OpenAPI-first FastAPI application template.

This package contains a minimal, fully working example of an OpenAPI-first FastAPI service built using the openapi_first library.

The application is assembled exclusively from: - an OpenAPI specification (openapi.yaml) - a handler namespace (routes)

No routing decorators, implicit behavior, or framework-specific convenience abstractions are used. All HTTP routes, methods, and operation bindings are defined in OpenAPI and enforced at application startup.

This package is intended to be copied as a starting point for new services via the openapi-first CLI. It is not part of the openapi_first library API surface.

"},{"location":"openapi_first/templates/health_app/#openapi_first.templates.health_app--scaffolding-via-cli","title":"Scaffolding via CLI","text":"

Create a new OpenAPI-first health check service using the bundled template:

openapi-first scaffold health_app\n

Create the service in a custom directory:

openapi-first scaffold health_app my-health-service\n

List all available application templates:

openapi-first scaffold --list\n

The CLI copies template files verbatim into the target directory. No code is generated or modified beyond the copied scaffold.

"},{"location":"openapi_first/templates/health_app/#openapi_first.templates.health_app--client-usage-example","title":"Client Usage Example","text":"

The same OpenAPI specification used by the server can be used to construct a strict, operationId-driven HTTP client.

Example client call for the get_health operation:

from openapi_first.loader import load_openapi\nfrom openapi_first.client import OpenAPIClient\n\nspec = load_openapi(\"openapi.yaml\")\nclient = OpenAPIClient(spec)\n\nresponse = client.get_health()\n\nassert response.status_code == 200\nassert response.json() == {\"status\": \"ok\"}\n

Client guarantees: - One callable per OpenAPI operationId - No hardcoded URLs or HTTP methods in user code - Path and request parameters must match the OpenAPI specification - Invalid or incomplete OpenAPI specs fail at client construction time

"},{"location":"openapi_first/templates/health_app/main/","title":"Main","text":""},{"location":"openapi_first/templates/health_app/main/#openapi_first.templates.health_app.main","title":"openapi_first.templates.health_app.main","text":"

Application entry point for an OpenAPI-first FastAPI service.

This module constructs a FastAPI application exclusively from an OpenAPI specification and a handler namespace, without using decorator-driven routing.

All HTTP routes, methods, and operation bindings are defined in the OpenAPI document referenced by openapi_path. Python callables defined in the routes module are bound to OpenAPI operations strictly via operationId.

This module contains no routing logic, request handling, or framework configuration beyond application assembly.

Design guarantees: - OpenAPI is the single source of truth - No undocumented routes can exist - Every OpenAPI operationId must resolve to exactly one handler - All contract violations fail at application startup

This file is intended to be used as the ASGI entry point.

Example

uvicorn main:app

"},{"location":"openapi_first/templates/health_app/main/#openapi_first.templates.health_app.main-classes","title":"Classes","text":""},{"location":"openapi_first/templates/health_app/routes/","title":"Routes","text":""},{"location":"openapi_first/templates/health_app/routes/#openapi_first.templates.health_app.routes","title":"openapi_first.templates.health_app.routes","text":"

OpenAPI operation handlers.

This module defines pure Python callables that implement OpenAPI operations for this service. Functions in this module are bound to HTTP routes exclusively via OpenAPI operationId values.

No routing decorators, HTTP metadata, or framework-specific logic should appear here. All request/response semantics are defined in the OpenAPI specification.

This module serves solely as an operationId namespace.

"},{"location":"openapi_first/templates/health_app/routes/#openapi_first.templates.health_app.routes-functions","title":"Functions","text":""},{"location":"openapi_first/templates/health_app/routes/#openapi_first.templates.health_app.routes.get_health","title":"get_health","text":"
get_health() -> dict\n

Health check operation handler.

This function implements the OpenAPI operation identified by operationId: get_health.

It contains no routing metadata or framework-specific logic. Request binding, HTTP method, and response semantics are defined exclusively by the OpenAPI specification.

"},{"location":"openapi_first/templates/health_app/routes/#openapi_first.templates.health_app.routes.get_health--returns","title":"Returns","text":"

dict A minimal liveness payload indicating service health.

"},{"location":"openapi_first/templates/model_app/","title":"Model App","text":"
  • Data
  • Main
  • Models
  • Routes
  • Test Model App
"},{"location":"openapi_first/templates/model_app/#openapi_first.templates.model_app","title":"openapi_first.templates.model_app","text":"

OpenAPI-first model-based CRUD application template.

This package contains a complete, minimal example of an OpenAPI-first CRUD service that uses explicit Pydantic domain models for request and response schemas.

The application is assembled exclusively from: - an OpenAPI specification (openapi.yaml) - a handler namespace implementing CRUD operations (routes) - Pydantic domain models (models) - an in-memory mock data store (data)

All HTTP routes, methods, schemas, and operation bindings are defined in the OpenAPI specification and enforced at application startup. No decorator-driven routing or implicit framework behavior is used.

This template demonstrates: - operationId-driven server-side route binding - explicit request and response modeling with Pydantic - explicit HTTP status code control in handlers - operationId-driven client usage against the same OpenAPI contract - end-to-end validation using in-memory data and tests

"},{"location":"openapi_first/templates/model_app/#openapi_first.templates.model_app--scaffolding-via-cli","title":"Scaffolding via CLI","text":"

Create a new model-based CRUD example service using the bundled template:

openapi-first scaffold model_app\n

Create the service in a custom directory:

openapi-first scaffold model_app my-model-service\n

List all available application templates:

openapi-first scaffold --list\n

The CLI copies template files verbatim into the target directory. No code is generated or modified beyond the copied scaffold.

"},{"location":"openapi_first/templates/model_app/#openapi_first.templates.model_app--client-usage-example","title":"Client Usage Example","text":"

The same OpenAPI specification used by the server can be used to construct a strict, operationId-driven HTTP client.

Example client calls for model-based CRUD operations:

from openapi_first.loader import load_openapi\nfrom openapi_first.client import OpenAPIClient\n\nspec = load_openapi(\"openapi.yaml\")\nclient = OpenAPIClient(spec)\n\n# List items\nresponse = client.list_items()\n\n# Get item by ID\nresponse = client.get_item(\n    path_params={\"item_id\": 1}\n)\n\n# Create item\nresponse = client.create_item(\n    body={\"name\": \"Orange\", \"price\": 0.8}\n)\n\n# Update item\nresponse = client.update_item(\n    path_params={\"item_id\": 1},\n    body={\"name\": \"Green Apple\", \"price\": 0.6},\n)\n\n# Delete item\nresponse = client.delete_item(\n    path_params={\"item_id\": 1}\n)\n

Client guarantees: - One callable per OpenAPI operationId - No hardcoded URLs or HTTP methods in user code - Request and response payloads conform to Pydantic models - Invalid or incomplete OpenAPI specs fail at client construction time

"},{"location":"openapi_first/templates/model_app/#openapi_first.templates.model_app--non-goals","title":"Non-Goals","text":"

This template is intentionally minimal and is NOT: - production-ready - persistent or concurrency-safe - a reference architecture for data storage

It exists solely as a copyable example for learning, testing, and bootstrapping OpenAPI-first services.

This package is not part of the openapi_first library API surface.

"},{"location":"openapi_first/templates/model_app/data/","title":"Data","text":""},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data","title":"openapi_first.templates.model_app.data","text":"

In-memory data store using Pydantic models.

This module is NOT thread-safe and is intended for demos and scaffolds only. This module provides a minimal, process-local data store for the model-based CRUD example application. It stores and returns domain objects defined using Pydantic models and is intended solely for demonstration and scaffolding purposes.

The implementation intentionally avoids: - persistence - concurrency guarantees - transactional semantics - validation beyond what Pydantic provides

It is not part of the openapi_first library API surface.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data-functions","title":"Functions","text":""},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.create_item","title":"create_item","text":"
create_item(payload: ItemCreate) -> Item\n

Create a new item in the data store.

A new identifier is assigned automatically. No additional validation is performed beyond Pydantic model validation.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.create_item--parameters","title":"Parameters","text":"

payload : ItemCreate Data required to create a new item.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.create_item--returns","title":"Returns","text":"

Item The newly created item.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.delete_item","title":"delete_item","text":"
delete_item(item_id: int) -> None\n

Remove an item from the data store.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.delete_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to delete.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.delete_item--raises","title":"Raises","text":"

KeyError If the item does not exist.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.get_item","title":"get_item","text":"
get_item(item_id: int) -> Item\n

Retrieve a single item by ID.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.get_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to retrieve.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.get_item--returns","title":"Returns","text":"

Item The requested item.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.get_item--raises","title":"Raises","text":"

KeyError If the item does not exist.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.list_items","title":"list_items","text":"
list_items() -> list[Item]\n

Return all items in the data store.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.list_items--returns","title":"Returns","text":"

list[Item] A list of item domain objects.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.update_item","title":"update_item","text":"
update_item(item_id: int, payload: ItemCreate) -> Item\n

Replace an existing item in the data store.

This function performs a full replacement of the stored item. Partial updates are not supported.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.update_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to update. payload : ItemCreate New item data.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.update_item--returns","title":"Returns","text":"

Item The updated item.

"},{"location":"openapi_first/templates/model_app/data/#openapi_first.templates.model_app.data.update_item--raises","title":"Raises","text":"

KeyError If the item does not exist.

"},{"location":"openapi_first/templates/model_app/main/","title":"Main","text":""},{"location":"openapi_first/templates/model_app/main/#openapi_first.templates.model_app.main","title":"openapi_first.templates.model_app.main","text":"

Application entry point for an OpenAPI-first model-based CRUD example service.

This module constructs a FastAPI application exclusively from an OpenAPI specification and a handler namespace, without using decorator-driven routing.

All HTTP routes, methods, request/response schemas, and operation bindings are defined in the OpenAPI document referenced by openapi_path. Python callables defined in the routes module are bound to OpenAPI operations strictly via operationId.

This module contains no routing logic, persistence concerns, or framework configuration beyond application assembly.

Design guarantees: - OpenAPI is the single source of truth - No undocumented routes can exist - Every OpenAPI operationId must resolve to exactly one handler - All contract violations fail at application startup

This file is intended to be used as the ASGI entry point.

Example

uvicorn main:app

"},{"location":"openapi_first/templates/model_app/main/#openapi_first.templates.model_app.main-classes","title":"Classes","text":""},{"location":"openapi_first/templates/model_app/models/","title":"Models","text":""},{"location":"openapi_first/templates/model_app/models/#openapi_first.templates.model_app.models","title":"openapi_first.templates.model_app.models","text":"

Pydantic domain models for the CRUD example.

This module defines Pydantic models that represent the domain entities used by the service. These models are referenced by the OpenAPI specification for request and response schemas.

The models are declarative and framework-agnostic. They contain no persistence logic, validation beyond type constraints, or business behavior.

This module is not part of the openapi_first library API surface. It exists solely to support the example application template.

"},{"location":"openapi_first/templates/model_app/models/#openapi_first.templates.model_app.models-classes","title":"Classes","text":""},{"location":"openapi_first/templates/model_app/models/#openapi_first.templates.model_app.models.Item","title":"Item","text":"

Bases: ItemBase

Domain model for a persisted item.

This model represents the full item state returned in responses, including the server-assigned identifier.

"},{"location":"openapi_first/templates/model_app/models/#openapi_first.templates.model_app.models.ItemBase","title":"ItemBase","text":"

Bases: BaseModel

Base domain model for an item.

Defines fields common to all item representations.

"},{"location":"openapi_first/templates/model_app/models/#openapi_first.templates.model_app.models.ItemCreate","title":"ItemCreate","text":"

Bases: ItemBase

Domain model for item creation requests.

This model is used for request bodies when creating new items. It intentionally excludes the id field, which is assigned by the service.

"},{"location":"openapi_first/templates/model_app/routes/","title":"Routes","text":""},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes","title":"openapi_first.templates.model_app.routes","text":"

CRUD route handlers bound via OpenAPI operationId.

This module defines OpenAPI-bound operation handlers for a model-based CRUD service. Functions in this module are bound to HTTP routes exclusively via OpenAPI operationId values.

Handlers explicitly control HTTP response status codes to ensure runtime behavior matches the OpenAPI contract. Domain models defined using Pydantic are used for request and response payloads.

No routing decorators, path definitions, or implicit framework behavior appear in this module. All routing, HTTP methods, and schemas are defined in the OpenAPI specification.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes-functions","title":"Functions","text":""},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.create_item","title":"create_item","text":"
create_item(\n    payload: ItemCreate, response: Response\n) -> Item\n

Create a new item.

Implements the OpenAPI operation identified by operationId: create_item.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.create_item--parameters","title":"Parameters","text":"

payload : ItemCreate Request body describing the item to create. response : Response Response object used to set the HTTP status code.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.create_item--returns","title":"Returns","text":"

Item The newly created item.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.delete_item","title":"delete_item","text":"
delete_item(item_id: int, response: Response) -> None\n

Delete an existing item.

Implements the OpenAPI operation identified by operationId: delete_item.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.delete_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to delete. response : Response Response object used to set the HTTP status code.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.delete_item--returns","title":"Returns","text":"

None No content.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.delete_item--raises","title":"Raises","text":"

HTTPException 404 if the item does not exist.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.get_item","title":"get_item","text":"
get_item(item_id: int) -> Item\n

Retrieve a single item by ID.

Implements the OpenAPI operation identified by operationId: get_item.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.get_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to retrieve.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.get_item--returns","title":"Returns","text":"

Item The requested item.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.get_item--raises","title":"Raises","text":"

HTTPException 404 if the item does not exist.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.list_items","title":"list_items","text":"
list_items() -> list[Item]\n

List all items.

Implements the OpenAPI operation identified by operationId: list_items.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.list_items--returns","title":"Returns","text":"

list[Item] A list of item domain objects.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.update_item","title":"update_item","text":"
update_item(item_id: int, payload: ItemCreate) -> Item\n

Update an existing item.

Implements the OpenAPI operation identified by operationId: update_item.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.update_item--parameters","title":"Parameters","text":"

item_id : int Identifier of the item to update. payload : ItemCreate New item data.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.update_item--returns","title":"Returns","text":"

Item The updated item.

"},{"location":"openapi_first/templates/model_app/routes/#openapi_first.templates.model_app.routes.update_item--raises","title":"Raises","text":"

HTTPException 404 if the item does not exist.

"},{"location":"openapi_first/templates/model_app/test_model_app/","title":"Test Model App","text":""},{"location":"openapi_first/templates/model_app/test_model_app/#openapi_first.templates.model_app.test_model_app","title":"openapi_first.templates.model_app.test_model_app","text":"

End-to-end tests for the OpenAPI-first model CRUD example app.

These tests validate that all CRUD operations behave correctly against the in-memory mock data store using Pydantic models. - OpenAPI specification loading - OperationId-driven route binding on the server - OperationId-driven client invocation - Pydantic model-based request and response handling

All CRUD operations are exercised against an in-memory mock data store backed by Pydantic domain models.

The tests assume: - OpenAPI-first route binding - Pydantic model validation - In-memory storage (no persistence guarantees) - Deterministic behavior in a single process

"},{"location":"openapi_first/templates/model_app/test_model_app/#openapi_first.templates.model_app.test_model_app-classes","title":"Classes","text":""},{"location":"openapi_first/templates/model_app/test_model_app/#openapi_first.templates.model_app.test_model_app-functions","title":"Functions","text":""},{"location":"openapi_first/templates/model_app/test_model_app/#openapi_first.templates.model_app.test_model_app.test_create_item","title":"test_create_item","text":"
test_create_item()\n

Creating a new item should return the created entity.

"},{"location":"openapi_first/templates/model_app/test_model_app/#openapi_first.templates.model_app.test_model_app.test_delete_item","title":"test_delete_item","text":"
test_delete_item()\n

Deleting an item should remove it from the store.

"},{"location":"openapi_first/templates/model_app/test_model_app/#openapi_first.templates.model_app.test_model_app.test_get_item","title":"test_get_item","text":"
test_get_item()\n

Existing item should be retrievable by ID.

"},{"location":"openapi_first/templates/model_app/test_model_app/#openapi_first.templates.model_app.test_model_app.test_list_items_initial","title":"test_list_items_initial","text":"
test_list_items_initial()\n

Initial items should be present.

"},{"location":"openapi_first/templates/model_app/test_model_app/#openapi_first.templates.model_app.test_model_app.test_update_item","title":"test_update_item","text":"
test_update_item()\n

Updating an item should replace its values.

"},{"location":"openapi_first/templates/vet_app/","title":"Vet App","text":"
  • Data
  • Main
  • Models
  • Routes
  • Sse
  • Test Vet App
"},{"location":"openapi_first/templates/vet_app/#openapi_first.templates.vet_app","title":"openapi_first.templates.vet_app","text":"

OpenAPI-first Veterinary Clinic application template.

This package contains a complete, runnable example of an OpenAPI-first veterinary clinic management service. It demonstrates all x- extension fields consumed by the react-openapi admin panel renderer.

The application manages five resources:

  • Parents \u2014 pet owners with contact details
  • Vets \u2014 veterinarians with specializations
  • Treatments \u2014 medical procedure catalog
  • Pets \u2014 animals with species, age, weight, and photos
  • Appointments \u2014 scheduled visits linking pets, vets, and treatments

All HTTP routes, methods, schemas, and operation bindings are defined in the OpenAPI specification (openapi.yaml). Every operation has an explicit operationId that maps to a Python handler in routes.py.

This file is a copyable template. It is not part of the openapi_first library API surface.

"},{"location":"openapi_first/templates/vet_app/#openapi_first.templates.vet_app--openapi-x-extension-fields-demonstrated","title":"OpenAPI x- extension fields demonstrated","text":"

Schema-level extensions (display metadata for resource endpoints):

x-primary-key (REQUIRED) Primary key property name x-display-format (REQUIRED) Human-readable label template x-list-columns (REQUIRED) Columns for the datatable

Property-level extensions (control UI rendering):

x-label (REQUIRED) Human-readable field label x-order (REQUIRED) Field ordering in forms/detail x-description (optional) Helper text below form fields x-hidden (optional) Visibility in form / list / detail x-filterable (optional) Allows column filtering x-sortable (optional) Allows column sorting x-fk (optional) Foreign key \u2014 renders as dropdown x-fk.resource (REQUIRED for FK) Target resource name x-fk.prefetch (optional) Preload all FK options on mount x-ui-type (optional) Custom UI type (e.g. image upload) x-upload-url (optional) Upload endpoint for binary fields

"},{"location":"openapi_first/templates/vet_app/#openapi_first.templates.vet_app--scaffolding-via-cli","title":"Scaffolding via CLI","text":"

Create a new vet clinic service using the bundled template:

openapi-first scaffold vet_app\n

Create the service in a custom directory:

openapi-first scaffold vet_app my-vet-clinic\n
"},{"location":"openapi_first/templates/vet_app/#openapi_first.templates.vet_app--client-usage-example","title":"Client Usage Example","text":"
from openapi_first.loader import load_openapi\nfrom openapi_first.client import OpenAPIClient\n\nspec = load_openapi(\"openapi.yaml\")\nclient = OpenAPIClient(spec)\n\n# List pets with pagination\nresponse = client.list_pets(query_params={\"limit\": 10, \"offset\": 0})\n\n# Create a pet with FK references\nresponse = client.create_pet(\n    body={\"name\": \"Fido\", \"species\": \"dog\", \"parents\": [1, 2]}\n)\n\n# Upload a pet photo\nresponse = client.upload_pet_photo(\n    path_params={\"id\": 1},\n    body={\"file\": open(\"photo.jpg\", \"rb\")},\n)\n
"},{"location":"openapi_first/templates/vet_app/#openapi_first.templates.vet_app--non-goals","title":"Non-Goals","text":"

This template is intentionally minimal and is NOT: - production-ready - persistent or concurrency-safe - a reference architecture for data storage

It exists solely as a copyable example for learning, testing, and bootstrapping OpenAPI-first services.

This package is not part of the openapi_first library API surface.

"},{"location":"openapi_first/templates/vet_app/data/","title":"Data","text":""},{"location":"openapi_first/templates/vet_app/data/#openapi_first.templates.vet_app.data","title":"openapi_first.templates.vet_app.data","text":"

In-memory data store for the Veterinary Clinic example.

This module is NOT thread-safe and is intended for demos and scaffolds only.

It provides minimal, process-local data stores for the five veterinary clinic entities. Each store exposes standard CRUD operations backed by a simple dictionary.

This module intentionally avoids: - persistence - concurrency guarantees - transactional semantics - validation beyond what Pydantic provides

This module is not part of the openapi_first library API surface.

"},{"location":"openapi_first/templates/vet_app/main/","title":"Main","text":""},{"location":"openapi_first/templates/vet_app/main/#openapi_first.templates.vet_app.main","title":"openapi_first.templates.vet_app.main","text":"

Application entry point for an OpenAPI-first Veterinary Clinic service.

This module constructs a FastAPI application exclusively from an OpenAPI specification and a handler namespace, without using decorator-driven routing.

All HTTP routes, methods, request/response schemas, and operation bindings are defined in the OpenAPI document referenced by openapi_path. Python callables defined in the routes module are bound to OpenAPI operations strictly via operationId.

This module contains no routing logic, persistence concerns, or framework configuration beyond application assembly.

Design guarantees: - OpenAPI is the single source of truth - No undocumented routes can exist - Every OpenAPI operationId must resolve to exactly one handler - All contract violations fail at application startup

This file is intended to be used as the ASGI entry point.

Example

uvicorn main:app

"},{"location":"openapi_first/templates/vet_app/main/#openapi_first.templates.vet_app.main-classes","title":"Classes","text":""},{"location":"openapi_first/templates/vet_app/models/","title":"Models","text":""},{"location":"openapi_first/templates/vet_app/models/#openapi_first.templates.vet_app.models","title":"openapi_first.templates.vet_app.models","text":""},{"location":"openapi_first/templates/vet_app/routes/","title":"Routes","text":""},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes","title":"openapi_first.templates.vet_app.routes","text":"

Veterinary Clinic route handlers bound via OpenAPI operationId.

Handlers explicitly control HTTP response status codes to ensure runtime behavior matches the OpenAPI contract. Domain models defined using Pydantic are used for request and response payloads.

No routing decorators, path definitions, or implicit framework behavior appear in this module. All routing, HTTP methods, and schemas are defined in the OpenAPI specification.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes-functions","title":"Functions","text":""},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.create_appointment","title":"create_appointment","text":"
create_appointment(\n    payload: AppointmentCreate, response: Response\n)\n

Create an appointment.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.create_parent","title":"create_parent","text":"
create_parent(\n    payload: ParentCreate, response: Response\n) -> Parent\n

Create a parent.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.create_parent--parameters","title":"Parameters","text":"

payload : ParentCreate Parent data excluding the id field. response : Response Response object used to set the HTTP status code.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.create_parent--returns","title":"Returns","text":"

Parent The newly created parent.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.create_pet","title":"create_pet","text":"
create_pet(payload: PetCreate, response: Response)\n

Create a pet.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.create_treatment","title":"create_treatment","text":"
create_treatment(\n    payload: TreatmentCreate, response: Response\n)\n

Add a treatment (admin only).

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.create_vet","title":"create_vet","text":"
create_vet(payload: VetCreate, response: Response)\n

Create a vet.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.delete_appointment","title":"delete_appointment","text":"
delete_appointment(id: int, response: Response)\n

Delete an existing appointment.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.delete_parent","title":"delete_parent","text":"
delete_parent(id: int, response: Response) -> None\n

Delete an existing parent.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.delete_parent--parameters","title":"Parameters","text":"

id : int Identifier of the parent. response : Response Response object used to set the HTTP status code.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.delete_parent--raises","title":"Raises","text":"

HTTPException 404 if the parent does not exist.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.delete_pet","title":"delete_pet","text":"
delete_pet(id: int, response: Response)\n

Delete an existing pet.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.delete_treatment","title":"delete_treatment","text":"
delete_treatment(id: int, response: Response)\n

Delete an existing treatment.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.delete_vet","title":"delete_vet","text":"
delete_vet(id: int, response: Response)\n

Delete an existing vet.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.get_appointment","title":"get_appointment","text":"
get_appointment(id: int)\n

Retrieve a single appointment by ID.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.get_parent","title":"get_parent","text":"
get_parent(id: int) -> Parent\n

Retrieve a single parent by ID.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.get_parent--parameters","title":"Parameters","text":"

id : int Identifier of the parent.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.get_parent--returns","title":"Returns","text":"

Parent The requested parent.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.get_parent--raises","title":"Raises","text":"

HTTPException 404 if the parent does not exist.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.get_pet","title":"get_pet","text":"
get_pet(id: int)\n

Retrieve a single pet by ID.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.get_treatment","title":"get_treatment","text":"
get_treatment(id: int)\n

Retrieve a single treatment by ID.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.get_vet","title":"get_vet","text":"
get_vet(id: int)\n

Retrieve a single vet by ID.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.list_appointments","title":"list_appointments","text":"
list_appointments(\n    limit: int = 20,\n    offset: int = 0,\n    date: str = None,\n    vet: int = None,\n    pet: int = None,\n)\n

List appointments (paginated, filterable).

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.list_parents","title":"list_parents","text":"
list_parents(limit: int = 20, offset: int = 0) -> dict\n

List parents (paginated).

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.list_parents--parameters","title":"Parameters","text":"

limit : int Maximum number of records to return. offset : int Number of records to skip.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.list_parents--returns","title":"Returns","text":"

dict Paginated response with total and items.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.list_pets","title":"list_pets","text":"
list_pets(limit: int = 20, offset: int = 0)\n

List pets (paginated).

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.list_treatments","title":"list_treatments","text":"
list_treatments() -> list[Treatment]\n

List treatments (catalogue).

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.list_treatments--returns","title":"Returns","text":"

list[Treatment] A list of treatment domain objects.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.list_vets","title":"list_vets","text":"
list_vets(limit: int = 20, offset: int = 0)\n

List vets (paginated).

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.stream_actions","title":"stream_actions async","text":"
stream_actions(id: int)\n

Stream animal actions via SSE, scoped to a pet's species.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.update_appointment","title":"update_appointment","text":"
update_appointment(id: int, payload: AppointmentCreate)\n

Update an existing appointment.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.update_parent","title":"update_parent","text":"
update_parent(id: int, payload: ParentCreate) -> Parent\n

Update an existing parent.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.update_parent--parameters","title":"Parameters","text":"

id : int Identifier of the parent. payload : ParentCreate Updated parent data.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.update_parent--returns","title":"Returns","text":"

Parent The updated parent.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.update_parent--raises","title":"Raises","text":"

HTTPException 404 if the parent does not exist.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.update_pet","title":"update_pet","text":"
update_pet(id: int, payload: PetCreate)\n

Update an existing pet.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.update_treatment","title":"update_treatment","text":"
update_treatment(id: int, payload: TreatmentCreate)\n

Update an existing treatment.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.update_vet","title":"update_vet","text":"
update_vet(id: int, payload: VetCreate)\n

Update an existing vet.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.upload_pet_photo","title":"upload_pet_photo","text":"
upload_pet_photo(id: int, file: UploadFile) -> dict\n

Upload a pet photo.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.upload_pet_photo--parameters","title":"Parameters","text":"

id : int Identifier of the pet. file : UploadFile Image file to upload.

"},{"location":"openapi_first/templates/vet_app/routes/#openapi_first.templates.vet_app.routes.upload_pet_photo--returns","title":"Returns","text":"

dict A confirmation with the pet ID.

"},{"location":"openapi_first/templates/vet_app/sse/","title":"Sse","text":""},{"location":"openapi_first/templates/vet_app/sse/#openapi_first.templates.vet_app.sse","title":"openapi_first.templates.vet_app.sse","text":""},{"location":"openapi_first/templates/vet_app/test_vet_app/","title":"Test Vet App","text":""},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app","title":"openapi_first.templates.vet_app.test_vet_app","text":"

End-to-end tests for the OpenAPI-first Veterinary Clinic example app.

These tests validate that all CRUD operations behave correctly against the in-memory mock data store using Pydantic models.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app-classes","title":"Classes","text":""},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app-functions","title":"Functions","text":""},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_create_parent","title":"test_create_parent","text":"
test_create_parent()\n

Creating a parent returns 201 with the created entity.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_create_pet","title":"test_create_pet","text":"
test_create_pet()\n

Creating a pet links FK references.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_create_treatment","title":"test_create_treatment","text":"
test_create_treatment()\n

Creating a treatment returns 201.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_create_vet","title":"test_create_vet","text":"
test_create_vet()\n

Creating a vet returns 201.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_delete_parent","title":"test_delete_parent","text":"
test_delete_parent()\n

Delete parent returns 204 and removes the entity.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_full_appointment_lifecycle","title":"test_full_appointment_lifecycle","text":"
test_full_appointment_lifecycle()\n

Create a parent, vet, treatment, pet, then an appointment.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_get_parent","title":"test_get_parent","text":"
test_get_parent()\n

Get parent by ID returns the entity.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_list_appointments","title":"test_list_appointments","text":"
test_list_appointments()\n

List appointments returns paginated response with filter params.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_list_parents","title":"test_list_parents","text":"
test_list_parents()\n

List parents returns paginated response.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_list_treatments","title":"test_list_treatments","text":"
test_list_treatments()\n

List treatments returns an array.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_list_vets","title":"test_list_vets","text":"
test_list_vets()\n

List vets returns paginated response.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_update_parent","title":"test_update_parent","text":"
test_update_parent()\n

Update parent replaces its values.

"},{"location":"openapi_first/templates/vet_app/test_vet_app/#openapi_first.templates.vet_app.test_vet_app.test_upload_pet_photo","title":"test_upload_pet_photo","text":"
test_upload_pet_photo()\n

Upload pet photo returns 200.

"}]} \ No newline at end of file diff --git a/openapi-first/lib/sitemap.xml.gz b/openapi-first/lib/sitemap.xml.gz index 01465253604ef76b3b2b517f1166919160e58317..36fff951d658b8fa1c2624e6fcea07a1ebb3772b 100644 GIT binary patch delta 13 Ucmb=gXP58h;9yWbGm*Ul02yinzW@LL delta 13 Ucmb=gXP58h;ArStF_FCj03F=~f&c&j diff --git a/openapi-first/mcp/modules/openapi_first.app.json b/openapi-first/mcp/modules/openapi_first.app.json index fdb22ca..8c79d2b 100644 --- a/openapi-first/mcp/modules/openapi_first.app.json +++ b/openapi-first/mcp/modules/openapi_first.app.json @@ -4,75 +4,40 @@ "path": "openapi_first.app", "docstring": "# Summary\n\nOpenAPI-first application bootstrap for FastAPI.\n\nThis module provides `OpenAPIFirstApp`, a thin but strict abstraction\nthat enforces OpenAPI as the single source of truth for a FastAPI service.\n\nNotes:\n **Core Principles:**\n\n - The OpenAPI specification (JSON or YAML) defines the entire API surface.\n - Every `operationId` in the OpenAPI spec must have a corresponding\n Python handler function.\n - Handlers are plain Python callables (no FastAPI decorators).\n - FastAPI route registration is derived exclusively from the spec.\n - FastAPI's autogenerated OpenAPI schema is fully overridden.\n\n **Responsibilities:**\n\n - Loads and validates an OpenAPI 3.x specification.\n - Dynamically binds HTTP routes to handler functions using `operationId`.\n - Registers routes with FastAPI at application startup.\n - Ensures runtime behavior matches the OpenAPI contract exactly.\n\n **Constraints:**\n\n - This module intentionally does NOT:\n - Generate OpenAPI specs.\n - Generate client code.\n - Introduce a new framework or lifecycle.\n - Alter FastAPI dependency injection semantics.", "objects": { - "os": { - "name": "os", - "kind": "alias", - "path": "openapi_first.app.os", - "signature": "", - "docstring": null - }, - "re": { - "name": "re", - "kind": "alias", - "path": "openapi_first.app.re", - "signature": "", - "docstring": null - }, - "ModuleType": { - "name": "ModuleType", - "kind": "alias", - "path": "openapi_first.app.ModuleType", - "signature": "", - "docstring": null - }, - "Any": { - "name": "Any", - "kind": "alias", - "path": "openapi_first.app.Any", - "signature": "", - "docstring": null - }, - "FastAPI": { - "name": "FastAPI", - "kind": "alias", - "path": "openapi_first.app.FastAPI", - "signature": "", - "docstring": null - }, "bind_routes": { "name": "bind_routes", "kind": "function", "path": "openapi_first.app.bind_routes", - "signature": "", + "signature": "bind_routes(app: FastAPI, spec: dict[str, Any], routes_module: ModuleType, security_deps: dict[str, list[Any]] | None = None)", "docstring": "Bind OpenAPI operations to FastAPI routes.\n\nArgs:\n app (FastAPI):\n The FastAPI application instance to which routes will be added.\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification dictionary.\n routes_module (ModuleType):\n Python module containing handler functions. Each handler's name MUST\n exactly match an OpenAPI `operationId`.\n security_deps (dict[str, list[Any]] | None):\n Optional mapping of ``METHOD:/path`` → ``list[Depends(...)]``\n generated from the spec's ``securitySchemes`` and per-operation\n ``security`` fields.\n\nRaises:\n MissingOperationHandler:\n If an ``operationId`` is missing from the spec or if no corresponding\n handler function exists in the routes module.\n\nNotes:\n **Responsibilities:**\n\n - Iterates through the OpenAPI specification paths and methods.\n - Resolves each ``operationId`` to a handler function, and registers\n a corresponding ``APIRoute`` on the FastAPI application.\n - Injects FastAPI ``Depends()`` for each security requirement found\n on the operation or inherited from the top-level ``security`` field.\n\n **Guarantees:**\n\n - Route registration is deterministic and spec-driven. No route\n decorators are required or supported. Handler resolution errors\n surface at application startup." }, "load_openapi": { "name": "load_openapi", "kind": "function", "path": "openapi_first.app.load_openapi", - "signature": "", + "signature": "load_openapi(path: str | Path)", "docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup." }, "make_security_dependencies": { "name": "make_security_dependencies", "kind": "function", "path": "openapi_first.app.make_security_dependencies", - "signature": "", + "signature": "make_security_dependencies(spec: dict[str, Any], security_schemes: dict[str, dict[str, Any]])", "docstring": "Build a mapping of ``METHOD:/path`` → list of ``Depends(...)``.\n\nThe effective security for each operation is resolved by:\n\n1. Using the operation-level ``security`` field if present.\n2. Falling back to the top-level ``security`` field.\n3. An empty list means *no auth required* for that operation." }, "parse_security_schemes": { "name": "parse_security_schemes", "kind": "function", "path": "openapi_first.app.parse_security_schemes", - "signature": "", + "signature": "parse_security_schemes(spec: dict[str, Any])", "docstring": "Extract and resolve environment variables in security schemes." }, "OpenAPIFirstApp": { "name": "OpenAPIFirstApp", "kind": "class", "path": "openapi_first.app.OpenAPIFirstApp", - "signature": "", - "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", + "signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)", + "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", "members": { "openapi": { "name": "openapi", diff --git a/openapi-first/mcp/modules/openapi_first.binder.json b/openapi-first/mcp/modules/openapi_first.binder.json index 605a031..99e8618 100644 --- a/openapi-first/mcp/modules/openapi_first.binder.json +++ b/openapi-first/mcp/modules/openapi_first.binder.json @@ -4,46 +4,18 @@ "path": "openapi_first.binder", "docstring": "# Summary\n\nOpenAPI-driven route binding for FastAPI.\n\nThis module is responsible for translating an OpenAPI 3.x specification\ninto concrete FastAPI routes. It enforces a strict one-to-one mapping\nbetween OpenAPI operations and Python handler functions using `operationId`.\n\nNotes:\n **Core Responsibility:**\n\n - Read path + method definitions from an OpenAPI specification.\n - Resolve each `operationId` to a Python callable.\n - Register routes with FastAPI using `APIRoute`.\n - Fail fast when contract violations are detected.\n\n **Design Constraints:**\n\n - All routes MUST be declared in the OpenAPI specification.\n - All OpenAPI operations MUST define an `operationId`.\n - Every `operationId` MUST resolve to a handler function.\n - Handlers are plain Python callables (no decorators required).\n - No implicit route creation or inference is allowed.\n\n **Constraints:**\n\n - This module intentionally does NOT:\n - Perform request or response validation.\n - Generate Pydantic models.\n - Modify FastAPI dependency injection.\n - Interpret OpenAPI semantics beyond routing metadata.", "objects": { - "ModuleType": { - "name": "ModuleType", - "kind": "alias", - "path": "openapi_first.binder.ModuleType", - "signature": "", - "docstring": null - }, - "Any": { - "name": "Any", - "kind": "alias", - "path": "openapi_first.binder.Any", - "signature": "", - "docstring": null - }, - "FastAPI": { - "name": "FastAPI", - "kind": "alias", - "path": "openapi_first.binder.FastAPI", - "signature": "", - "docstring": null - }, - "APIRoute": { - "name": "APIRoute", - "kind": "alias", - "path": "openapi_first.binder.APIRoute", - "signature": "", - "docstring": null - }, "MissingOperationHandler": { "name": "MissingOperationHandler", "kind": "class", "path": "openapi_first.binder.MissingOperationHandler", - "signature": "", - "docstring": "Raised when an OpenAPI operation cannot be resolved to a handler.\n\nNotes:\n **Scenarios:**\n\n - An OpenAPI operation does not define an `operationId`.\n - An `operationId` is defined but no matching function exists in\n the provided routes module.\n\n **Guarantees:**\n\n - This represents a violation of the OpenAPI-first contract and\n indicates that the specification and implementation are out of\n sync." + "signature": "MissingOperationHandler(*, path: str, method: str, operation_id: str | None = None)", + "docstring": "Raised when an OpenAPI operation cannot be resolved to a handler.\n\nArgs:\n path (str):\n The HTTP path declared in the OpenAPI specification.\n method (str):\n The HTTP method (as declared in the OpenAPI spec).\n operation_id (str | None):\n The operationId declared in the OpenAPI spec, if present.\n\nNotes:\n **Scenarios:**\n\n - An OpenAPI operation does not define an `operationId`.\n - An `operationId` is defined but no matching function exists in\n the provided routes module.\n\n **Guarantees:**\n\n - This represents a violation of the OpenAPI-first contract and\n indicates that the specification and implementation are out of\n sync." }, "bind_routes": { "name": "bind_routes", "kind": "function", "path": "openapi_first.binder.bind_routes", - "signature": "", + "signature": "bind_routes(app: FastAPI, spec: dict[str, Any], routes_module: ModuleType, security_deps: dict[str, list[Any]] | None = None) -> None", "docstring": "Bind OpenAPI operations to FastAPI routes.\n\nArgs:\n app (FastAPI):\n The FastAPI application instance to which routes will be added.\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification dictionary.\n routes_module (ModuleType):\n Python module containing handler functions. Each handler's name MUST\n exactly match an OpenAPI `operationId`.\n security_deps (dict[str, list[Any]] | None):\n Optional mapping of ``METHOD:/path`` → ``list[Depends(...)]``\n generated from the spec's ``securitySchemes`` and per-operation\n ``security`` fields.\n\nRaises:\n MissingOperationHandler:\n If an ``operationId`` is missing from the spec or if no corresponding\n handler function exists in the routes module.\n\nNotes:\n **Responsibilities:**\n\n - Iterates through the OpenAPI specification paths and methods.\n - Resolves each ``operationId`` to a handler function, and registers\n a corresponding ``APIRoute`` on the FastAPI application.\n - Injects FastAPI ``Depends()`` for each security requirement found\n on the operation or inherited from the top-level ``security`` field.\n\n **Guarantees:**\n\n - Route registration is deterministic and spec-driven. No route\n decorators are required or supported. Handler resolution errors\n surface at application startup." } } diff --git a/openapi-first/mcp/modules/openapi_first.cli.json b/openapi-first/mcp/modules/openapi_first.cli.json index f77f555..96fb2d9 100644 --- a/openapi-first/mcp/modules/openapi_first.cli.json +++ b/openapi-first/mcp/modules/openapi_first.cli.json @@ -4,34 +4,6 @@ "path": "openapi_first.cli", "docstring": "# Summary\n\nCommand-line interface for FastAPI OpenAPI-first scaffolding utilities.\n\nThis CLI bootstraps OpenAPI-first FastAPI applications from versioned,\nbundled templates packaged with the library.", "objects": { - "argparse": { - "name": "argparse", - "kind": "alias", - "path": "openapi_first.cli.argparse", - "signature": "", - "docstring": null - }, - "shutil": { - "name": "shutil", - "kind": "alias", - "path": "openapi_first.cli.shutil", - "signature": "", - "docstring": null - }, - "resources": { - "name": "resources", - "kind": "alias", - "path": "openapi_first.cli.resources", - "signature": "", - "docstring": null - }, - "Path": { - "name": "Path", - "kind": "alias", - "path": "openapi_first.cli.Path", - "signature": "", - "docstring": null - }, "DEFAULT_TEMPLATE": { "name": "DEFAULT_TEMPLATE", "kind": "attribute", @@ -43,21 +15,21 @@ "name": "available_templates", "kind": "function", "path": "openapi_first.cli.available_templates", - "signature": "", + "signature": "available_templates() -> list[str]", "docstring": "Return a list of available application templates.\n\nReturns:\n list[str]:\n Sorted list of template names found in the internal templates directory." }, "copy_template": { "name": "copy_template", "kind": "function", "path": "openapi_first.cli.copy_template", - "signature": "", + "signature": "copy_template(template: str, target_dir: Path) -> None", "docstring": "Copy a bundled OpenAPI-first application template into a directory.\n\nArgs:\n template (str):\n Name of the template to copy.\n target_dir (Path):\n Filesystem path where the template should be copied.\n\nRaises:\n FileNotFoundError:\n If the requested template does not exist." }, "main": { "name": "main", "kind": "function", "path": "openapi_first.cli.main", - "signature": "", + "signature": "main() -> None", "docstring": null } } diff --git a/openapi-first/mcp/modules/openapi_first.client.json b/openapi-first/mcp/modules/openapi_first.client.json index 91eb734..6582b93 100644 --- a/openapi-first/mcp/modules/openapi_first.client.json +++ b/openapi-first/mcp/modules/openapi_first.client.json @@ -4,54 +4,26 @@ "path": "openapi_first.client", "docstring": "# Summary\n\nOpenAPI-first HTTP client for contract-driven services.\n\nThis module provides `OpenAPIClient`, a thin, strict HTTP client that\nderives all callable operations directly from an OpenAPI 3.x specification.\n\nIt is the client counterpart to `OpenAPIFirstApp`.\n\nNotes:\n **Core Principles:**\n\n - The OpenAPI specification is the single source of truth\n - Each operationId becomes a callable Python method\n - No implicit schema mutation or inference\n - No code generation step\n - Minimal abstraction over httpx\n\n **Responsibilities:**\n\n - Parses an OpenAPI 3.x specification\n - Dynamically creates one callable per operationId\n - Enforces presence of servers, paths, and operationId\n - Formats path parameters safely\n - Handles JSON request bodies explicitly\n - Returns raw `httpx.Response` objects\n\n **Constraints:**\n\n - This module intentionally does NOT: Generate client code, validate request/response schemas, deserialize responses, retry requests, implement authentication helpers, or assume non-2xx responses are failures.", "objects": { - "Callable": { - "name": "Callable", - "kind": "alias", - "path": "openapi_first.client.Callable", - "signature": "", - "docstring": null - }, - "Any": { - "name": "Any", - "kind": "alias", - "path": "openapi_first.client.Any", - "signature": "", - "docstring": null - }, - "urljoin": { - "name": "urljoin", - "kind": "alias", - "path": "openapi_first.client.urljoin", - "signature": "", - "docstring": null - }, - "httpx": { - "name": "httpx", - "kind": "alias", - "path": "openapi_first.client.httpx", - "signature": "", - "docstring": null - }, "OpenAPIFirstError": { "name": "OpenAPIFirstError", "kind": "class", "path": "openapi_first.client.OpenAPIFirstError", - "signature": "", + "signature": null, "docstring": "Base exception for all OpenAPI-first enforcement errors.\n\nNotes:\n **Responsibilities:**\n\n - This exception exists to allow callers, test suites, and CI\n pipelines to catch and distinguish OpenAPI contract violations\n from unrelated runtime errors.\n - All exceptions raised by the OpenAPI-first core should inherit\n from this type." }, "OpenAPIClientError": { "name": "OpenAPIClientError", "kind": "class", "path": "openapi_first.client.OpenAPIClientError", - "signature": "", + "signature": null, "docstring": "Raised when an OpenAPI client operation fails." }, "OpenAPIClient": { "name": "OpenAPIClient", "kind": "class", "path": "openapi_first.client.OpenAPIClient", - "signature": "", - "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", + "signature": "OpenAPIClient(spec: dict[str, Any], base_url: str | None = None, client: httpx.Client | None = None)", + "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nArgs:\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification.\n base_url (str | None):\n Base URL of the target service. If omitted, the first entry in the\n OpenAPI `servers` list is used.\n client (httpx.Client | None):\n Optional preconfigured httpx client instance.\n\nRaises:\n OpenAPIClientError:\n If no servers are defined, spec has no paths, operationIds are\n missing/duplicate, or required parameters are missing.\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", "members": { "spec": { "name": "spec", @@ -78,7 +50,7 @@ "name": "operations", "kind": "function", "path": "openapi_first.client.OpenAPIClient.operations", - "signature": "", + "signature": "operations() -> dict[str, Callable[..., httpx.Response]]", "docstring": null } } diff --git a/openapi-first/mcp/modules/openapi_first.codegen.json b/openapi-first/mcp/modules/openapi_first.codegen.json index bc8ba9d..9e239df 100644 --- a/openapi-first/mcp/modules/openapi_first.codegen.json +++ b/openapi-first/mcp/modules/openapi_first.codegen.json @@ -4,46 +4,18 @@ "path": "openapi_first.codegen", "docstring": "# Summary\n\nCore logic for generating Python source code from OpenAPI specifications.\n\nThis module provides reusable utilities for code generation, specifically\ngenerating Pydantic models and route handler stubs from OpenAPI 3.x schema\ndefinitions.", "objects": { - "Path": { - "name": "Path", - "kind": "alias", - "path": "openapi_first.codegen.Path", - "signature": "", - "docstring": null - }, - "InputFileType": { - "name": "InputFileType", - "kind": "alias", - "path": "openapi_first.codegen.InputFileType", - "signature": "", - "docstring": null - }, - "PythonVersion": { - "name": "PythonVersion", - "kind": "alias", - "path": "openapi_first.codegen.PythonVersion", - "signature": "", - "docstring": null - }, - "generate": { - "name": "generate", - "kind": "alias", - "path": "openapi_first.codegen.generate", - "signature": "", - "docstring": null - }, "generate_routes": { "name": "generate_routes", "kind": "function", "path": "openapi_first.codegen.generate_routes", - "signature": "", - "docstring": "Generate route handler stubs from an OpenAPI specification.\n\nCreates one ``.py`` file per resource in *output_dir*.\nResources are derived from the first path segment (e.g. ``/pets``\nand ``/pets/{id}`` both group under ``pets``).\n\nArgs:\n spec_path (Path):\n Path to the OpenAPI specification file (YAML or JSON).\n output_dir (Path):\n Directory where the generated route files are written.\n Created automatically if it does not exist.\n use_models (bool, optional):\n If ``True``, import Pydantic models from *models_module*\n for request-body schemas referenced via ``$ref``.\n models_module (str, optional):\n Dotted Python module path from which to import models\n (e.g. ``\"models\"``, ``\"app.models\"``).\n\nReturns:\n list[Path]:\n Absolute paths of every generated route file.\n\nRaises:\n OpenAPISpecLoadError:\n If the spec cannot be loaded or validated.\n ValueError:\n If any operation is missing ``operationId``." + "signature": "generate_routes(spec_path: Path, output_dir: Path, *, use_models: bool = False, models_module: str = 'models', inject_request: bool = False, package: bool = False)", + "docstring": "Generate route handler stubs from an OpenAPI specification.\n\nCreates one route module per resource in *output_dir*. Resources are\nderived from the first path segment (e.g. ``/pets`` and ``/pets/{id}``\nboth group under ``pets``) and sanitized to valid module identifiers.\n\nArgs:\n spec_path (Path):\n Path to the OpenAPI specification file (YAML or JSON).\n output_dir (Path):\n Directory where the generated route files are written.\n Created automatically if it does not exist.\n use_models (bool, optional):\n If ``True``, import Pydantic models from *models_module*\n for request-body schemas referenced via ``$ref``.\n models_module (str, optional):\n Dotted Python module path from which to import models\n (e.g. ``\"models\"``, ``\"app.models\"``).\n inject_request (bool, optional):\n If ``True``, prepend ``request: Request`` to every handler so the\n stub can later resolve services from ``request.app.state``.\n package (bool, optional):\n If ``True``, emit ``/routes.py`` packages (each with an\n ``__init__.py``) instead of flat ``.py`` modules.\n\nReturns:\n list[Path]:\n Absolute paths of every generated route file.\n\nRaises:\n OpenAPISpecLoadError:\n If the spec cannot be loaded or validated.\n ValueError:\n If any operation is missing ``operationId``." }, "generate_models": { "name": "generate_models", "kind": "function", "path": "openapi_first.codegen.generate_models", - "signature": "", + "signature": "generate_models(spec_path: Path, output_path: Path, pydantic_version: int = 2) -> None", "docstring": "Generate Pydantic models from an OpenAPI specification.\n\nArgs:\n spec_path (Path):\n Path to the OpenAPI specification file (YAML or JSON).\n output_path (Path):\n Path where the generated Python code should be written.\n pydantic_version (int, optional):\n The Pydantic version to target (1 or 2). Defaults to 2.\n\nNotes:\n **Reusability:**\n This function is designed to be used by the CLI and can be\n exposed as an MCP tool without modification." } } diff --git a/openapi-first/mcp/modules/openapi_first.codegen_routes.json b/openapi-first/mcp/modules/openapi_first.codegen_routes.json index 77db335..6cd5010 100644 --- a/openapi-first/mcp/modules/openapi_first.codegen_routes.json +++ b/openapi-first/mcp/modules/openapi_first.codegen_routes.json @@ -2,35 +2,21 @@ "module": "openapi_first.codegen_routes", "content": { "path": "openapi_first.codegen_routes", - "docstring": "# Summary\n\nRoute handler code generation from OpenAPI specifications.\n\nThis module generates Python route handler stubs from an OpenAPI 3.x\nspecification. Each resource (derived from the first path segment)\ngets its own file under the output directory. Every OpenAPI operation\nmust define an ``operationId``, which becomes the handler function name.\n\nNotes:\n **Design constraints:**\n\n - ``operationId`` is required on every operation (matching\n ``binder.bind_routes``).\n - Handlers are stubs raising ``NotImplementedError``.\n - Sub-resources (e.g. ``/pets/{id}/photo``) are grouped with their\n parent resource (``pets``).\n - Parameter types and defaults are inferred from the spec.\n - ``response: Response`` is injected for non-200 success codes.", + "docstring": "# Summary\n\nRoute handler code generation from OpenAPI specifications.\n\nThis module generates Python route handler stubs from an OpenAPI 3.x\nspecification. Each resource (derived from the first path segment)\ngets its own file under the output directory. Every OpenAPI operation\nmust define an ``operationId``, which becomes the handler function name.\n\nNotes:\n **Design constraints:**\n\n - ``operationId`` is required on every operation (matching\n ``binder.bind_routes``).\n - Handlers are stubs raising ``NotImplementedError``.\n - Sub-resources (e.g. ``/pets/{id}/photo``) are grouped with their\n parent resource (``pets``).\n - Parameter types and defaults are inferred from the spec.\n - ``response: Response`` is injected for non-200 success codes.\n - Resource names are sanitized to valid Python module identifiers,\n so a path such as ``/fetch-requests`` yields ``fetch_requests.py``.\n - Passing ``inject_request=True`` prepends ``request: Request`` to\n every handler (the composition-root / DI convention).\n - ``multipart/form-data`` and\n ``application/x-www-form-urlencoded`` request bodies expand to one\n parameter per schema property (``UploadFile`` / ``File`` / ``Form``)\n instead of an opaque ``payload: dict``.\n - Passing ``package=True`` writes ``/routes.py`` (with\n ``__init__.py`` sidecars) instead of a flat ``.py``.", "objects": { - "Path": { - "name": "Path", - "kind": "alias", - "path": "openapi_first.codegen_routes.Path", - "signature": "", - "docstring": null - }, - "Any": { - "name": "Any", - "kind": "alias", - "path": "openapi_first.codegen_routes.Any", - "signature": "", - "docstring": null - }, "load_openapi": { "name": "load_openapi", "kind": "function", "path": "openapi_first.codegen_routes.load_openapi", - "signature": "", + "signature": "load_openapi(path: str | Path)", "docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup." }, "generate_routes": { "name": "generate_routes", "kind": "function", "path": "openapi_first.codegen_routes.generate_routes", - "signature": "", - "docstring": "Generate route handler stubs from an OpenAPI specification.\n\nCreates one ``.py`` file per resource in *output_dir*.\nResources are derived from the first path segment (e.g. ``/pets``\nand ``/pets/{id}`` both group under ``pets``).\n\nArgs:\n spec_path (Path):\n Path to the OpenAPI specification file (YAML or JSON).\n output_dir (Path):\n Directory where the generated route files are written.\n Created automatically if it does not exist.\n use_models (bool, optional):\n If ``True``, import Pydantic models from *models_module*\n for request-body schemas referenced via ``$ref``.\n models_module (str, optional):\n Dotted Python module path from which to import models\n (e.g. ``\"models\"``, ``\"app.models\"``).\n\nReturns:\n list[Path]:\n Absolute paths of every generated route file.\n\nRaises:\n OpenAPISpecLoadError:\n If the spec cannot be loaded or validated.\n ValueError:\n If any operation is missing ``operationId``." + "signature": "generate_routes(spec_path: Path, output_dir: Path, *, use_models: bool = False, models_module: str = 'models', inject_request: bool = False, package: bool = False) -> list[Path]", + "docstring": "Generate route handler stubs from an OpenAPI specification.\n\nCreates one route module per resource in *output_dir*. Resources are\nderived from the first path segment (e.g. ``/pets`` and ``/pets/{id}``\nboth group under ``pets``) and sanitized to valid module identifiers.\n\nArgs:\n spec_path (Path):\n Path to the OpenAPI specification file (YAML or JSON).\n output_dir (Path):\n Directory where the generated route files are written.\n Created automatically if it does not exist.\n use_models (bool, optional):\n If ``True``, import Pydantic models from *models_module*\n for request-body schemas referenced via ``$ref``.\n models_module (str, optional):\n Dotted Python module path from which to import models\n (e.g. ``\"models\"``, ``\"app.models\"``).\n inject_request (bool, optional):\n If ``True``, prepend ``request: Request`` to every handler so the\n stub can later resolve services from ``request.app.state``.\n package (bool, optional):\n If ``True``, emit ``/routes.py`` packages (each with an\n ``__init__.py``) instead of flat ``.py`` modules.\n\nReturns:\n list[Path]:\n Absolute paths of every generated route file.\n\nRaises:\n OpenAPISpecLoadError:\n If the spec cannot be loaded or validated.\n ValueError:\n If any operation is missing ``operationId``." } } } diff --git a/openapi-first/mcp/modules/openapi_first.errors.json b/openapi-first/mcp/modules/openapi_first.errors.json index 61b7eca..0d7d14b 100644 --- a/openapi-first/mcp/modules/openapi_first.errors.json +++ b/openapi-first/mcp/modules/openapi_first.errors.json @@ -8,15 +8,15 @@ "name": "OpenAPIFirstError", "kind": "class", "path": "openapi_first.errors.OpenAPIFirstError", - "signature": "", + "signature": null, "docstring": "Base exception for all OpenAPI-first enforcement errors.\n\nNotes:\n **Responsibilities:**\n\n - This exception exists to allow callers, test suites, and CI\n pipelines to catch and distinguish OpenAPI contract violations\n from unrelated runtime errors.\n - All exceptions raised by the OpenAPI-first core should inherit\n from this type." }, "MissingOperationHandler": { "name": "MissingOperationHandler", "kind": "class", "path": "openapi_first.errors.MissingOperationHandler", - "signature": "", - "docstring": "Raised when an OpenAPI operation cannot be resolved to a handler.\n\nNotes:\n **Scenarios:**\n\n - An OpenAPI operation does not define an `operationId`.\n - An `operationId` is defined but no matching function exists in\n the provided routes module.\n\n **Guarantees:**\n\n - This represents a violation of the OpenAPI-first contract and\n indicates that the specification and implementation are out of\n sync." + "signature": "MissingOperationHandler(*, path: str, method: str, operation_id: str | None = None)", + "docstring": "Raised when an OpenAPI operation cannot be resolved to a handler.\n\nArgs:\n path (str):\n The HTTP path declared in the OpenAPI specification.\n method (str):\n The HTTP method (as declared in the OpenAPI spec).\n operation_id (str | None):\n The operationId declared in the OpenAPI spec, if present.\n\nNotes:\n **Scenarios:**\n\n - An OpenAPI operation does not define an `operationId`.\n - An `operationId` is defined but no matching function exists in\n the provided routes module.\n\n **Guarantees:**\n\n - This represents a violation of the OpenAPI-first contract and\n indicates that the specification and implementation are out of\n sync." } } } diff --git a/openapi-first/mcp/modules/openapi_first.json b/openapi-first/mcp/modules/openapi_first.json index 0ebae41..8d7eed1 100644 --- a/openapi-first/mcp/modules/openapi_first.json +++ b/openapi-first/mcp/modules/openapi_first.json @@ -11,75 +11,40 @@ "signature": null, "docstring": "# Summary\n\nOpenAPI-first application bootstrap for FastAPI.\n\nThis module provides `OpenAPIFirstApp`, a thin but strict abstraction\nthat enforces OpenAPI as the single source of truth for a FastAPI service.\n\nNotes:\n **Core Principles:**\n\n - The OpenAPI specification (JSON or YAML) defines the entire API surface.\n - Every `operationId` in the OpenAPI spec must have a corresponding\n Python handler function.\n - Handlers are plain Python callables (no FastAPI decorators).\n - FastAPI route registration is derived exclusively from the spec.\n - FastAPI's autogenerated OpenAPI schema is fully overridden.\n\n **Responsibilities:**\n\n - Loads and validates an OpenAPI 3.x specification.\n - Dynamically binds HTTP routes to handler functions using `operationId`.\n - Registers routes with FastAPI at application startup.\n - Ensures runtime behavior matches the OpenAPI contract exactly.\n\n **Constraints:**\n\n - This module intentionally does NOT:\n - Generate OpenAPI specs.\n - Generate client code.\n - Introduce a new framework or lifecycle.\n - Alter FastAPI dependency injection semantics.", "members": { - "os": { - "name": "os", - "kind": "alias", - "path": "openapi_first.app.os", - "signature": "", - "docstring": null - }, - "re": { - "name": "re", - "kind": "alias", - "path": "openapi_first.app.re", - "signature": "", - "docstring": null - }, - "ModuleType": { - "name": "ModuleType", - "kind": "alias", - "path": "openapi_first.app.ModuleType", - "signature": "", - "docstring": null - }, - "Any": { - "name": "Any", - "kind": "alias", - "path": "openapi_first.app.Any", - "signature": "", - "docstring": null - }, - "FastAPI": { - "name": "FastAPI", - "kind": "alias", - "path": "openapi_first.app.FastAPI", - "signature": "", - "docstring": null - }, "bind_routes": { "name": "bind_routes", "kind": "function", "path": "openapi_first.app.bind_routes", - "signature": "", + "signature": "bind_routes(app: FastAPI, spec: dict[str, Any], routes_module: ModuleType, security_deps: dict[str, list[Any]] | None = None)", "docstring": "Bind OpenAPI operations to FastAPI routes.\n\nArgs:\n app (FastAPI):\n The FastAPI application instance to which routes will be added.\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification dictionary.\n routes_module (ModuleType):\n Python module containing handler functions. Each handler's name MUST\n exactly match an OpenAPI `operationId`.\n security_deps (dict[str, list[Any]] | None):\n Optional mapping of ``METHOD:/path`` → ``list[Depends(...)]``\n generated from the spec's ``securitySchemes`` and per-operation\n ``security`` fields.\n\nRaises:\n MissingOperationHandler:\n If an ``operationId`` is missing from the spec or if no corresponding\n handler function exists in the routes module.\n\nNotes:\n **Responsibilities:**\n\n - Iterates through the OpenAPI specification paths and methods.\n - Resolves each ``operationId`` to a handler function, and registers\n a corresponding ``APIRoute`` on the FastAPI application.\n - Injects FastAPI ``Depends()`` for each security requirement found\n on the operation or inherited from the top-level ``security`` field.\n\n **Guarantees:**\n\n - Route registration is deterministic and spec-driven. No route\n decorators are required or supported. Handler resolution errors\n surface at application startup." }, "load_openapi": { "name": "load_openapi", "kind": "function", "path": "openapi_first.app.load_openapi", - "signature": "", + "signature": "load_openapi(path: str | Path)", "docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup." }, "make_security_dependencies": { "name": "make_security_dependencies", "kind": "function", "path": "openapi_first.app.make_security_dependencies", - "signature": "", + "signature": "make_security_dependencies(spec: dict[str, Any], security_schemes: dict[str, dict[str, Any]])", "docstring": "Build a mapping of ``METHOD:/path`` → list of ``Depends(...)``.\n\nThe effective security for each operation is resolved by:\n\n1. Using the operation-level ``security`` field if present.\n2. Falling back to the top-level ``security`` field.\n3. An empty list means *no auth required* for that operation." }, "parse_security_schemes": { "name": "parse_security_schemes", "kind": "function", "path": "openapi_first.app.parse_security_schemes", - "signature": "", + "signature": "parse_security_schemes(spec: dict[str, Any])", "docstring": "Extract and resolve environment variables in security schemes." }, "OpenAPIFirstApp": { "name": "OpenAPIFirstApp", "kind": "class", "path": "openapi_first.app.OpenAPIFirstApp", - "signature": "", - "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", + "signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)", + "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", "members": { "openapi": { "name": "openapi", @@ -99,46 +64,18 @@ "signature": null, "docstring": "# Summary\n\nOpenAPI-driven route binding for FastAPI.\n\nThis module is responsible for translating an OpenAPI 3.x specification\ninto concrete FastAPI routes. It enforces a strict one-to-one mapping\nbetween OpenAPI operations and Python handler functions using `operationId`.\n\nNotes:\n **Core Responsibility:**\n\n - Read path + method definitions from an OpenAPI specification.\n - Resolve each `operationId` to a Python callable.\n - Register routes with FastAPI using `APIRoute`.\n - Fail fast when contract violations are detected.\n\n **Design Constraints:**\n\n - All routes MUST be declared in the OpenAPI specification.\n - All OpenAPI operations MUST define an `operationId`.\n - Every `operationId` MUST resolve to a handler function.\n - Handlers are plain Python callables (no decorators required).\n - No implicit route creation or inference is allowed.\n\n **Constraints:**\n\n - This module intentionally does NOT:\n - Perform request or response validation.\n - Generate Pydantic models.\n - Modify FastAPI dependency injection.\n - Interpret OpenAPI semantics beyond routing metadata.", "members": { - "ModuleType": { - "name": "ModuleType", - "kind": "alias", - "path": "openapi_first.binder.ModuleType", - "signature": "", - "docstring": null - }, - "Any": { - "name": "Any", - "kind": "alias", - "path": "openapi_first.binder.Any", - "signature": "", - "docstring": null - }, - "FastAPI": { - "name": "FastAPI", - "kind": "alias", - "path": "openapi_first.binder.FastAPI", - "signature": "", - "docstring": null - }, - "APIRoute": { - "name": "APIRoute", - "kind": "alias", - "path": "openapi_first.binder.APIRoute", - "signature": "", - "docstring": null - }, "MissingOperationHandler": { "name": "MissingOperationHandler", "kind": "class", "path": "openapi_first.binder.MissingOperationHandler", - "signature": "", - "docstring": "Raised when an OpenAPI operation cannot be resolved to a handler.\n\nNotes:\n **Scenarios:**\n\n - An OpenAPI operation does not define an `operationId`.\n - An `operationId` is defined but no matching function exists in\n the provided routes module.\n\n **Guarantees:**\n\n - This represents a violation of the OpenAPI-first contract and\n indicates that the specification and implementation are out of\n sync." + "signature": "MissingOperationHandler(*, path: str, method: str, operation_id: str | None = None)", + "docstring": "Raised when an OpenAPI operation cannot be resolved to a handler.\n\nArgs:\n path (str):\n The HTTP path declared in the OpenAPI specification.\n method (str):\n The HTTP method (as declared in the OpenAPI spec).\n operation_id (str | None):\n The operationId declared in the OpenAPI spec, if present.\n\nNotes:\n **Scenarios:**\n\n - An OpenAPI operation does not define an `operationId`.\n - An `operationId` is defined but no matching function exists in\n the provided routes module.\n\n **Guarantees:**\n\n - This represents a violation of the OpenAPI-first contract and\n indicates that the specification and implementation are out of\n sync." }, "bind_routes": { "name": "bind_routes", "kind": "function", "path": "openapi_first.binder.bind_routes", - "signature": "", + "signature": "bind_routes(app: FastAPI, spec: dict[str, Any], routes_module: ModuleType, security_deps: dict[str, list[Any]] | None = None) -> None", "docstring": "Bind OpenAPI operations to FastAPI routes.\n\nArgs:\n app (FastAPI):\n The FastAPI application instance to which routes will be added.\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification dictionary.\n routes_module (ModuleType):\n Python module containing handler functions. Each handler's name MUST\n exactly match an OpenAPI `operationId`.\n security_deps (dict[str, list[Any]] | None):\n Optional mapping of ``METHOD:/path`` → ``list[Depends(...)]``\n generated from the spec's ``securitySchemes`` and per-operation\n ``security`` fields.\n\nRaises:\n MissingOperationHandler:\n If an ``operationId`` is missing from the spec or if no corresponding\n handler function exists in the routes module.\n\nNotes:\n **Responsibilities:**\n\n - Iterates through the OpenAPI specification paths and methods.\n - Resolves each ``operationId`` to a handler function, and registers\n a corresponding ``APIRoute`` on the FastAPI application.\n - Injects FastAPI ``Depends()`` for each security requirement found\n on the operation or inherited from the top-level ``security`` field.\n\n **Guarantees:**\n\n - Route registration is deterministic and spec-driven. No route\n decorators are required or supported. Handler resolution errors\n surface at application startup." } } @@ -150,34 +87,6 @@ "signature": null, "docstring": "# Summary\n\nCommand-line interface for FastAPI OpenAPI-first scaffolding utilities.\n\nThis CLI bootstraps OpenAPI-first FastAPI applications from versioned,\nbundled templates packaged with the library.", "members": { - "argparse": { - "name": "argparse", - "kind": "alias", - "path": "openapi_first.cli.argparse", - "signature": "", - "docstring": null - }, - "shutil": { - "name": "shutil", - "kind": "alias", - "path": "openapi_first.cli.shutil", - "signature": "", - "docstring": null - }, - "resources": { - "name": "resources", - "kind": "alias", - "path": "openapi_first.cli.resources", - "signature": "", - "docstring": null - }, - "Path": { - "name": "Path", - "kind": "alias", - "path": "openapi_first.cli.Path", - "signature": "", - "docstring": null - }, "DEFAULT_TEMPLATE": { "name": "DEFAULT_TEMPLATE", "kind": "attribute", @@ -189,21 +98,21 @@ "name": "available_templates", "kind": "function", "path": "openapi_first.cli.available_templates", - "signature": "", + "signature": "available_templates() -> list[str]", "docstring": "Return a list of available application templates.\n\nReturns:\n list[str]:\n Sorted list of template names found in the internal templates directory." }, "copy_template": { "name": "copy_template", "kind": "function", "path": "openapi_first.cli.copy_template", - "signature": "", + "signature": "copy_template(template: str, target_dir: Path) -> None", "docstring": "Copy a bundled OpenAPI-first application template into a directory.\n\nArgs:\n template (str):\n Name of the template to copy.\n target_dir (Path):\n Filesystem path where the template should be copied.\n\nRaises:\n FileNotFoundError:\n If the requested template does not exist." }, "main": { "name": "main", "kind": "function", "path": "openapi_first.cli.main", - "signature": "", + "signature": "main() -> None", "docstring": null } } @@ -215,54 +124,26 @@ "signature": null, "docstring": "# Summary\n\nOpenAPI-first HTTP client for contract-driven services.\n\nThis module provides `OpenAPIClient`, a thin, strict HTTP client that\nderives all callable operations directly from an OpenAPI 3.x specification.\n\nIt is the client counterpart to `OpenAPIFirstApp`.\n\nNotes:\n **Core Principles:**\n\n - The OpenAPI specification is the single source of truth\n - Each operationId becomes a callable Python method\n - No implicit schema mutation or inference\n - No code generation step\n - Minimal abstraction over httpx\n\n **Responsibilities:**\n\n - Parses an OpenAPI 3.x specification\n - Dynamically creates one callable per operationId\n - Enforces presence of servers, paths, and operationId\n - Formats path parameters safely\n - Handles JSON request bodies explicitly\n - Returns raw `httpx.Response` objects\n\n **Constraints:**\n\n - This module intentionally does NOT: Generate client code, validate request/response schemas, deserialize responses, retry requests, implement authentication helpers, or assume non-2xx responses are failures.", "members": { - "Callable": { - "name": "Callable", - "kind": "alias", - "path": "openapi_first.client.Callable", - "signature": "", - "docstring": null - }, - "Any": { - "name": "Any", - "kind": "alias", - "path": "openapi_first.client.Any", - "signature": "", - "docstring": null - }, - "urljoin": { - "name": "urljoin", - "kind": "alias", - "path": "openapi_first.client.urljoin", - "signature": "", - "docstring": null - }, - "httpx": { - "name": "httpx", - "kind": "alias", - "path": "openapi_first.client.httpx", - "signature": "", - "docstring": null - }, "OpenAPIFirstError": { "name": "OpenAPIFirstError", "kind": "class", "path": "openapi_first.client.OpenAPIFirstError", - "signature": "", + "signature": null, "docstring": "Base exception for all OpenAPI-first enforcement errors.\n\nNotes:\n **Responsibilities:**\n\n - This exception exists to allow callers, test suites, and CI\n pipelines to catch and distinguish OpenAPI contract violations\n from unrelated runtime errors.\n - All exceptions raised by the OpenAPI-first core should inherit\n from this type." }, "OpenAPIClientError": { "name": "OpenAPIClientError", "kind": "class", "path": "openapi_first.client.OpenAPIClientError", - "signature": "", + "signature": null, "docstring": "Raised when an OpenAPI client operation fails." }, "OpenAPIClient": { "name": "OpenAPIClient", "kind": "class", "path": "openapi_first.client.OpenAPIClient", - "signature": "", - "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", + "signature": "OpenAPIClient(spec: dict[str, Any], base_url: str | None = None, client: httpx.Client | None = None)", + "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nArgs:\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification.\n base_url (str | None):\n Base URL of the target service. If omitted, the first entry in the\n OpenAPI `servers` list is used.\n client (httpx.Client | None):\n Optional preconfigured httpx client instance.\n\nRaises:\n OpenAPIClientError:\n If no servers are defined, spec has no paths, operationIds are\n missing/duplicate, or required parameters are missing.\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", "members": { "spec": { "name": "spec", @@ -289,7 +170,7 @@ "name": "operations", "kind": "function", "path": "openapi_first.client.OpenAPIClient.operations", - "signature": "", + "signature": "operations() -> dict[str, Callable[..., httpx.Response]]", "docstring": null } } @@ -303,46 +184,18 @@ "signature": null, "docstring": "# Summary\n\nCore logic for generating Python source code from OpenAPI specifications.\n\nThis module provides reusable utilities for code generation, specifically\ngenerating Pydantic models and route handler stubs from OpenAPI 3.x schema\ndefinitions.", "members": { - "Path": { - "name": "Path", - "kind": "alias", - "path": "openapi_first.codegen.Path", - "signature": "", - "docstring": null - }, - "InputFileType": { - "name": "InputFileType", - "kind": "alias", - "path": "openapi_first.codegen.InputFileType", - "signature": "", - "docstring": null - }, - "PythonVersion": { - "name": "PythonVersion", - "kind": "alias", - "path": "openapi_first.codegen.PythonVersion", - "signature": "", - "docstring": null - }, - "generate": { - "name": "generate", - "kind": "alias", - "path": "openapi_first.codegen.generate", - "signature": "", - "docstring": null - }, "generate_routes": { "name": "generate_routes", "kind": "function", "path": "openapi_first.codegen.generate_routes", - "signature": "", - "docstring": "Generate route handler stubs from an OpenAPI specification.\n\nCreates one ``.py`` file per resource in *output_dir*.\nResources are derived from the first path segment (e.g. ``/pets``\nand ``/pets/{id}`` both group under ``pets``).\n\nArgs:\n spec_path (Path):\n Path to the OpenAPI specification file (YAML or JSON).\n output_dir (Path):\n Directory where the generated route files are written.\n Created automatically if it does not exist.\n use_models (bool, optional):\n If ``True``, import Pydantic models from *models_module*\n for request-body schemas referenced via ``$ref``.\n models_module (str, optional):\n Dotted Python module path from which to import models\n (e.g. ``\"models\"``, ``\"app.models\"``).\n\nReturns:\n list[Path]:\n Absolute paths of every generated route file.\n\nRaises:\n OpenAPISpecLoadError:\n If the spec cannot be loaded or validated.\n ValueError:\n If any operation is missing ``operationId``." + "signature": "generate_routes(spec_path: Path, output_dir: Path, *, use_models: bool = False, models_module: str = 'models', inject_request: bool = False, package: bool = False)", + "docstring": "Generate route handler stubs from an OpenAPI specification.\n\nCreates one route module per resource in *output_dir*. Resources are\nderived from the first path segment (e.g. ``/pets`` and ``/pets/{id}``\nboth group under ``pets``) and sanitized to valid module identifiers.\n\nArgs:\n spec_path (Path):\n Path to the OpenAPI specification file (YAML or JSON).\n output_dir (Path):\n Directory where the generated route files are written.\n Created automatically if it does not exist.\n use_models (bool, optional):\n If ``True``, import Pydantic models from *models_module*\n for request-body schemas referenced via ``$ref``.\n models_module (str, optional):\n Dotted Python module path from which to import models\n (e.g. ``\"models\"``, ``\"app.models\"``).\n inject_request (bool, optional):\n If ``True``, prepend ``request: Request`` to every handler so the\n stub can later resolve services from ``request.app.state``.\n package (bool, optional):\n If ``True``, emit ``/routes.py`` packages (each with an\n ``__init__.py``) instead of flat ``.py`` modules.\n\nReturns:\n list[Path]:\n Absolute paths of every generated route file.\n\nRaises:\n OpenAPISpecLoadError:\n If the spec cannot be loaded or validated.\n ValueError:\n If any operation is missing ``operationId``." }, "generate_models": { "name": "generate_models", "kind": "function", "path": "openapi_first.codegen.generate_models", - "signature": "", + "signature": "generate_models(spec_path: Path, output_path: Path, pydantic_version: int = 2) -> None", "docstring": "Generate Pydantic models from an OpenAPI specification.\n\nArgs:\n spec_path (Path):\n Path to the OpenAPI specification file (YAML or JSON).\n output_path (Path):\n Path where the generated Python code should be written.\n pydantic_version (int, optional):\n The Pydantic version to target (1 or 2). Defaults to 2.\n\nNotes:\n **Reusability:**\n This function is designed to be used by the CLI and can be\n exposed as an MCP tool without modification." } } @@ -352,35 +205,21 @@ "kind": "module", "path": "openapi_first.codegen_routes", "signature": null, - "docstring": "# Summary\n\nRoute handler code generation from OpenAPI specifications.\n\nThis module generates Python route handler stubs from an OpenAPI 3.x\nspecification. Each resource (derived from the first path segment)\ngets its own file under the output directory. Every OpenAPI operation\nmust define an ``operationId``, which becomes the handler function name.\n\nNotes:\n **Design constraints:**\n\n - ``operationId`` is required on every operation (matching\n ``binder.bind_routes``).\n - Handlers are stubs raising ``NotImplementedError``.\n - Sub-resources (e.g. ``/pets/{id}/photo``) are grouped with their\n parent resource (``pets``).\n - Parameter types and defaults are inferred from the spec.\n - ``response: Response`` is injected for non-200 success codes.", + "docstring": "# Summary\n\nRoute handler code generation from OpenAPI specifications.\n\nThis module generates Python route handler stubs from an OpenAPI 3.x\nspecification. Each resource (derived from the first path segment)\ngets its own file under the output directory. Every OpenAPI operation\nmust define an ``operationId``, which becomes the handler function name.\n\nNotes:\n **Design constraints:**\n\n - ``operationId`` is required on every operation (matching\n ``binder.bind_routes``).\n - Handlers are stubs raising ``NotImplementedError``.\n - Sub-resources (e.g. ``/pets/{id}/photo``) are grouped with their\n parent resource (``pets``).\n - Parameter types and defaults are inferred from the spec.\n - ``response: Response`` is injected for non-200 success codes.\n - Resource names are sanitized to valid Python module identifiers,\n so a path such as ``/fetch-requests`` yields ``fetch_requests.py``.\n - Passing ``inject_request=True`` prepends ``request: Request`` to\n every handler (the composition-root / DI convention).\n - ``multipart/form-data`` and\n ``application/x-www-form-urlencoded`` request bodies expand to one\n parameter per schema property (``UploadFile`` / ``File`` / ``Form``)\n instead of an opaque ``payload: dict``.\n - Passing ``package=True`` writes ``/routes.py`` (with\n ``__init__.py`` sidecars) instead of a flat ``.py``.", "members": { - "Path": { - "name": "Path", - "kind": "alias", - "path": "openapi_first.codegen_routes.Path", - "signature": "", - "docstring": null - }, - "Any": { - "name": "Any", - "kind": "alias", - "path": "openapi_first.codegen_routes.Any", - "signature": "", - "docstring": null - }, "load_openapi": { "name": "load_openapi", "kind": "function", "path": "openapi_first.codegen_routes.load_openapi", - "signature": "", + "signature": "load_openapi(path: str | Path)", "docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup." }, "generate_routes": { "name": "generate_routes", "kind": "function", "path": "openapi_first.codegen_routes.generate_routes", - "signature": "", - "docstring": "Generate route handler stubs from an OpenAPI specification.\n\nCreates one ``.py`` file per resource in *output_dir*.\nResources are derived from the first path segment (e.g. ``/pets``\nand ``/pets/{id}`` both group under ``pets``).\n\nArgs:\n spec_path (Path):\n Path to the OpenAPI specification file (YAML or JSON).\n output_dir (Path):\n Directory where the generated route files are written.\n Created automatically if it does not exist.\n use_models (bool, optional):\n If ``True``, import Pydantic models from *models_module*\n for request-body schemas referenced via ``$ref``.\n models_module (str, optional):\n Dotted Python module path from which to import models\n (e.g. ``\"models\"``, ``\"app.models\"``).\n\nReturns:\n list[Path]:\n Absolute paths of every generated route file.\n\nRaises:\n OpenAPISpecLoadError:\n If the spec cannot be loaded or validated.\n ValueError:\n If any operation is missing ``operationId``." + "signature": "generate_routes(spec_path: Path, output_dir: Path, *, use_models: bool = False, models_module: str = 'models', inject_request: bool = False, package: bool = False) -> list[Path]", + "docstring": "Generate route handler stubs from an OpenAPI specification.\n\nCreates one route module per resource in *output_dir*. Resources are\nderived from the first path segment (e.g. ``/pets`` and ``/pets/{id}``\nboth group under ``pets``) and sanitized to valid module identifiers.\n\nArgs:\n spec_path (Path):\n Path to the OpenAPI specification file (YAML or JSON).\n output_dir (Path):\n Directory where the generated route files are written.\n Created automatically if it does not exist.\n use_models (bool, optional):\n If ``True``, import Pydantic models from *models_module*\n for request-body schemas referenced via ``$ref``.\n models_module (str, optional):\n Dotted Python module path from which to import models\n (e.g. ``\"models\"``, ``\"app.models\"``).\n inject_request (bool, optional):\n If ``True``, prepend ``request: Request`` to every handler so the\n stub can later resolve services from ``request.app.state``.\n package (bool, optional):\n If ``True``, emit ``/routes.py`` packages (each with an\n ``__init__.py``) instead of flat ``.py`` modules.\n\nReturns:\n list[Path]:\n Absolute paths of every generated route file.\n\nRaises:\n OpenAPISpecLoadError:\n If the spec cannot be loaded or validated.\n ValueError:\n If any operation is missing ``operationId``." } } }, @@ -395,15 +234,15 @@ "name": "OpenAPIFirstError", "kind": "class", "path": "openapi_first.errors.OpenAPIFirstError", - "signature": "", + "signature": null, "docstring": "Base exception for all OpenAPI-first enforcement errors.\n\nNotes:\n **Responsibilities:**\n\n - This exception exists to allow callers, test suites, and CI\n pipelines to catch and distinguish OpenAPI contract violations\n from unrelated runtime errors.\n - All exceptions raised by the OpenAPI-first core should inherit\n from this type." }, "MissingOperationHandler": { "name": "MissingOperationHandler", "kind": "class", "path": "openapi_first.errors.MissingOperationHandler", - "signature": "", - "docstring": "Raised when an OpenAPI operation cannot be resolved to a handler.\n\nNotes:\n **Scenarios:**\n\n - An OpenAPI operation does not define an `operationId`.\n - An `operationId` is defined but no matching function exists in\n the provided routes module.\n\n **Guarantees:**\n\n - This represents a violation of the OpenAPI-first contract and\n indicates that the specification and implementation are out of\n sync." + "signature": "MissingOperationHandler(*, path: str, method: str, operation_id: str | None = None)", + "docstring": "Raised when an OpenAPI operation cannot be resolved to a handler.\n\nArgs:\n path (str):\n The HTTP path declared in the OpenAPI specification.\n method (str):\n The HTTP method (as declared in the OpenAPI spec).\n operation_id (str | None):\n The operationId declared in the OpenAPI spec, if present.\n\nNotes:\n **Scenarios:**\n\n - An OpenAPI operation does not define an `operationId`.\n - An `operationId` is defined but no matching function exists in\n the provided routes module.\n\n **Guarantees:**\n\n - This represents a violation of the OpenAPI-first contract and\n indicates that the specification and implementation are out of\n sync." } } }, @@ -414,60 +253,25 @@ "signature": null, "docstring": "# Summary\n\nOpenAPI specification loading and validation utilities.\n\nThis module is responsible for loading an OpenAPI 3.x specification\nfrom disk and validating it before it is used by the application.\n\nIt enforces the principle that an invalid or malformed OpenAPI document\nmust never reach the routing or runtime layers.\n\nNotes:\n **Design Principles:**\n\n - OpenAPI is treated as an authoritative contract.\n - Invalid specifications fail fast at application startup.\n - Supported formats are JSON and YAML.\n - Validation errors are surfaced clearly and early.\n\n **Constraints:**\n\n - This module intentionally does NOT:\n - Modify the OpenAPI document.\n - Infer missing fields.\n - Generate models or code.\n - Perform request/response validation at runtime.", "members": { - "json": { - "name": "json", - "kind": "alias", - "path": "openapi_first.loader.json", - "signature": "", - "docstring": null - }, - "Path": { - "name": "Path", - "kind": "alias", - "path": "openapi_first.loader.Path", - "signature": "", - "docstring": null - }, - "Any": { - "name": "Any", - "kind": "alias", - "path": "openapi_first.loader.Any", - "signature": "", - "docstring": null - }, - "yaml": { - "name": "yaml", - "kind": "alias", - "path": "openapi_first.loader.yaml", - "signature": "", - "docstring": null - }, - "validate_spec": { - "name": "validate_spec", - "kind": "alias", - "path": "openapi_first.loader.validate_spec", - "signature": "", - "docstring": null - }, "OpenAPIFirstError": { "name": "OpenAPIFirstError", "kind": "class", "path": "openapi_first.loader.OpenAPIFirstError", - "signature": "", + "signature": null, "docstring": "Base exception for all OpenAPI-first enforcement errors.\n\nNotes:\n **Responsibilities:**\n\n - This exception exists to allow callers, test suites, and CI\n pipelines to catch and distinguish OpenAPI contract violations\n from unrelated runtime errors.\n - All exceptions raised by the OpenAPI-first core should inherit\n from this type." }, "OpenAPISpecLoadError": { "name": "OpenAPISpecLoadError", "kind": "class", "path": "openapi_first.loader.OpenAPISpecLoadError", - "signature": "", + "signature": null, "docstring": "Raised when an OpenAPI specification cannot be loaded or validated.\n\nNotes:\n **Guarantees:**\n\n - This error indicates that the OpenAPI document is unreadable,\n malformed, or violates the OpenAPI 3.x specification." }, "load_openapi": { "name": "load_openapi", "kind": "function", "path": "openapi_first.loader.load_openapi", - "signature": "", + "signature": "load_openapi(path: str | Path) -> dict[str, Any]", "docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup." } } @@ -479,88 +283,18 @@ "signature": null, "docstring": "# Summary\n\nOpenAPI security scheme parsing and auto-generated auth dependencies.\n\nThis module reads `securitySchemes` and per-operation `security` from an\nOpenAPI spec, resolves `{ENV_VAR}` placeholders in `x-` extension fields,\nand generates FastAPI dependencies for token validation (e.g., Bearer JWT\nintrospection).", "members": { - "os": { - "name": "os", - "kind": "alias", - "path": "openapi_first.security.os", - "signature": "", - "docstring": null - }, - "re": { - "name": "re", - "kind": "alias", - "path": "openapi_first.security.re", - "signature": "", - "docstring": null - }, - "Callable": { - "name": "Callable", - "kind": "alias", - "path": "openapi_first.security.Callable", - "signature": "", - "docstring": null - }, - "Any": { - "name": "Any", - "kind": "alias", - "path": "openapi_first.security.Any", - "signature": "", - "docstring": null - }, - "httpx": { - "name": "httpx", - "kind": "alias", - "path": "openapi_first.security.httpx", - "signature": "", - "docstring": null - }, - "Depends": { - "name": "Depends", - "kind": "alias", - "path": "openapi_first.security.Depends", - "signature": "", - "docstring": null - }, - "HTTPException": { - "name": "HTTPException", - "kind": "alias", - "path": "openapi_first.security.HTTPException", - "signature": "", - "docstring": null - }, - "Request": { - "name": "Request", - "kind": "alias", - "path": "openapi_first.security.Request", - "signature": "", - "docstring": null - }, - "HTTPAuthorizationCredentials": { - "name": "HTTPAuthorizationCredentials", - "kind": "alias", - "path": "openapi_first.security.HTTPAuthorizationCredentials", - "signature": "", - "docstring": null - }, - "HTTPBearer": { - "name": "HTTPBearer", - "kind": "alias", - "path": "openapi_first.security.HTTPBearer", - "signature": "", - "docstring": null - }, "parse_security_schemes": { "name": "parse_security_schemes", "kind": "function", "path": "openapi_first.security.parse_security_schemes", - "signature": "", + "signature": "parse_security_schemes(spec: dict[str, Any]) -> dict[str, dict[str, Any]]", "docstring": "Extract and resolve environment variables in security schemes." }, "make_security_dependencies": { "name": "make_security_dependencies", "kind": "function", "path": "openapi_first.security.make_security_dependencies", - "signature": "", + "signature": "make_security_dependencies(spec: dict[str, Any], security_schemes: dict[str, dict[str, Any]]) -> dict[str, list[Any]]", "docstring": "Build a mapping of ``METHOD:/path`` → list of ``Depends(...)``.\n\nThe effective security for each operation is resolved by:\n\n1. Using the operation-level ``security`` field if present.\n2. Falling back to the top-level ``security`` field.\n3. An empty list means *no auth required* for that operation." } } @@ -577,7 +311,7 @@ "kind": "module", "path": "openapi_first.templates.crud_app", "signature": null, - "docstring": "OpenAPI-first CRUD application template.\n\nThis package contains a complete, minimal example of an OpenAPI-first\nCRUD service built using the ``openapi_first`` library.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace implementing CRUD operations (``routes``)\n- an in-memory mock data store (``data``)\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification and enforced at application startup.\nNo decorator-driven routing or implicit framework behavior is used.\n\nThis template demonstrates:\n- operationId-driven server-side route binding\n- explicit HTTP status code control in handlers\n- operationId-driven client usage against the same OpenAPI contract\n- end-to-end validation using in-memory data and tests\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new CRUD example service using the bundled template:\n\n openapi-first crud_app\n\nCreate the service in a custom directory:\n\n openapi-first crud_app my-crud-service\n\nList all available application templates:\n\n openapi-first --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client calls for CRUD operations:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List items\n response = client.list_items()\n\n # Get item by ID\n response = client.get_item(\n path_params={\"item_id\": 1}\n )\n\n # Create item\n response = client.create_item(\n body={\"name\": \"Orange\", \"price\": 0.8}\n )\n\n # Update item\n response = client.update_item(\n path_params={\"item_id\": 1},\n body={\"name\": \"Green Apple\", \"price\": 0.6},\n )\n\n # Delete item\n response = client.delete_item(\n path_params={\"item_id\": 1}\n )\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Path and request parameters must match the OpenAPI specification\n- Invalid or incomplete OpenAPI specs fail at client construction time\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.", + "docstring": "OpenAPI-first CRUD application template.\n\nThis package contains a complete, minimal example of an OpenAPI-first\nCRUD service built using the ``openapi_first`` library.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace implementing CRUD operations (``routes``)\n- an in-memory mock data store (``data``)\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification and enforced at application startup.\nNo decorator-driven routing or implicit framework behavior is used.\n\nThis template demonstrates:\n- operationId-driven server-side route binding\n- explicit HTTP status code control in handlers\n- operationId-driven client usage against the same OpenAPI contract\n- end-to-end validation using in-memory data and tests\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new CRUD example service using the bundled template:\n\n openapi-first scaffold crud_app\n\nCreate the service in a custom directory:\n\n openapi-first scaffold crud_app my-crud-service\n\nList all available application templates:\n\n openapi-first scaffold --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client calls for CRUD operations:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List items\n response = client.list_items()\n\n # Get item by ID\n response = client.get_item(\n path_params={\"item_id\": 1}\n )\n\n # Create item\n response = client.create_item(\n body={\"name\": \"Orange\", \"price\": 0.8}\n )\n\n # Update item\n response = client.update_item(\n path_params={\"item_id\": 1},\n body={\"name\": \"Green Apple\", \"price\": 0.6},\n )\n\n # Delete item\n response = client.delete_item(\n path_params={\"item_id\": 1}\n )\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Path and request parameters must match the OpenAPI specification\n- Invalid or incomplete OpenAPI specs fail at client construction time\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.", "members": { "data": { "name": "data", @@ -586,46 +320,39 @@ "signature": null, "docstring": "In-memory mock data store for CRUD example.\n\nThis module intentionally avoids persistence and concurrency guarantees.\nIt is suitable for demos, tests, and scaffolding only.\n\nIt intentionally avoids\n- persistence\n- concurrency guarantees\n- validation\n- error handling\n\nThe implementation is suitable for:\n- demonstrations\n- tests\n- scaffolding and example services\n\nIt is explicitly NOT suitable for production use.\n\nThis module is not part of the ``openapi_first`` library API surface.", "members": { - "Dict": { - "name": "Dict", - "kind": "alias", - "path": "openapi_first.templates.crud_app.data.Dict", - "signature": "", - "docstring": null - }, "list_items": { "name": "list_items", "kind": "function", "path": "openapi_first.templates.crud_app.data.list_items", - "signature": "", + "signature": "list_items() -> list[dict]", "docstring": "Return all items in the data store.\n\nThis function performs no filtering, pagination, or sorting.\nThe returned collection reflects the current in-memory state.\n\nReturns\n-------\nlist[dict]\n A list of item representations." }, "get_item": { "name": "get_item", "kind": "function", "path": "openapi_first.templates.crud_app.data.get_item", - "signature": "", + "signature": "get_item(item_id: int) -> dict", "docstring": "Retrieve a single item by ID.\n\nThis function assumes the item exists and will raise ``KeyError``\nif the ID is not present in the store.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\ndict\n The stored item representation." }, "create_item": { "name": "create_item", "kind": "function", "path": "openapi_first.templates.crud_app.data.create_item", - "signature": "", + "signature": "create_item(payload: dict) -> dict", "docstring": "Create a new item in the data store.\n\nA new integer ID is assigned automatically. No validation is\nperformed on the provided payload.\n\nParameters\n----------\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The newly created item, including its assigned ID." }, "update_item": { "name": "update_item", "kind": "function", "path": "openapi_first.templates.crud_app.data.update_item", - "signature": "", + "signature": "update_item(item_id: int, payload: dict) -> dict", "docstring": "Replace an existing item in the data store.\n\nThis function overwrites the existing item entirely and does not\nperform partial updates or validation. If the item does not exist,\nit will be created implicitly.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The updated item representation." }, "delete_item": { "name": "delete_item", "kind": "function", "path": "openapi_first.templates.crud_app.data.delete_item", - "signature": "", + "signature": "delete_item(item_id: int)", "docstring": "Remove an item from the data store.\n\nThis function assumes the item exists and will raise ``KeyError``\nif the ID is not present.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete." } } @@ -641,25 +368,18 @@ "name": "OpenAPIFirstApp", "kind": "class", "path": "openapi_first.templates.crud_app.main.OpenAPIFirstApp", - "signature": "", - "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", + "signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)", + "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", "members": { "openapi": { "name": "openapi", "kind": "attribute", "path": "openapi_first.templates.crud_app.main.OpenAPIFirstApp.openapi", - "signature": "", + "signature": null, "docstring": null } } }, - "routes": { - "name": "routes", - "kind": "alias", - "path": "openapi_first.templates.crud_app.main.routes", - "signature": "", - "docstring": null - }, "app": { "name": "app", "kind": "attribute", @@ -676,54 +396,40 @@ "signature": null, "docstring": "CRUD route handlers bound via OpenAPI operationId.\n\nThese handlers explicitly control HTTP status codes to ensure\nruntime behavior matches the OpenAPI contract.\n\nThis module defines OpenAPI-bound operation handlers for a simple CRUD\nservice. Functions in this module are bound to HTTP routes exclusively\nvia OpenAPI ``operationId`` values.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Error conditions are translated\ninto explicit HTTP responses rather than relying on implicit framework\nbehavior.\n\nNo routing decorators or path definitions appear in this module. All\nrouting, HTTP methods, and schemas are defined in the OpenAPI\nspecification.", "members": { - "Response": { - "name": "Response", - "kind": "alias", - "path": "openapi_first.templates.crud_app.routes.Response", - "signature": "", - "docstring": null - }, - "HTTPException": { - "name": "HTTPException", - "kind": "alias", - "path": "openapi_first.templates.crud_app.routes.HTTPException", - "signature": "", - "docstring": null - }, "list_items": { "name": "list_items", "kind": "function", "path": "openapi_first.templates.crud_app.routes.list_items", - "signature": "", + "signature": "list_items() -> list[dict]", "docstring": "List all items.\n\nImplements the OpenAPI operation identified by\n``operationId: list_items``.\n\nReturns\n-------\nlist[dict]\n A list of item representations." }, "get_item": { "name": "get_item", "kind": "function", "path": "openapi_first.templates.crud_app.routes.get_item", - "signature": "", + "signature": "get_item(item_id: int) -> dict", "docstring": "Retrieve a single item by ID.\n\nImplements the OpenAPI operation identified by\n``operationId: get_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\ndict\n The requested item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." }, "create_item": { "name": "create_item", "kind": "function", "path": "openapi_first.templates.crud_app.routes.create_item", - "signature": "", - "docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : dict\n Item attributes excluding the ``id`` field.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\ndict\n The newly created item." + "signature": "create_item(payload: dict, response: Response) -> dict", + "docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : dict\n Item attributes excluding the ``id`` field.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\ndict\n The newly created item." }, "update_item": { "name": "update_item", "kind": "function", "path": "openapi_first.templates.crud_app.routes.update_item", - "signature": "", + "signature": "update_item(item_id: int, payload: dict) -> dict", "docstring": "Update an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: update_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The updated item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." }, "delete_item": { "name": "delete_item", "kind": "function", "path": "openapi_first.templates.crud_app.routes.delete_item", - "signature": "", - "docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." + "signature": "delete_item(item_id: int, response: Response) -> None", + "docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." } } }, @@ -734,60 +440,46 @@ "signature": null, "docstring": "End-to-end tests for the OpenAPI-first CRUD example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store.\n- OpenAPI specification loading\n- OperationId-driven route binding on the server\n- OperationId-driven client invocation\n- Correct HTTP status codes and response payloads\n\nThe tests exercise all CRUD operations against an in-memory mock data\nstore and assume deterministic behavior within a single process.\n\nThe tests assume:\n- OpenAPI-first route binding\n- In-memory storage (no persistence guarantees)\n- Deterministic behavior in a single process\n- One-to-one correspondence between OpenAPI operationId values and\n server/client callables", "members": { - "TestClient": { - "name": "TestClient", - "kind": "alias", - "path": "openapi_first.templates.crud_app.test_crud_app.TestClient", - "signature": "", - "docstring": null - }, - "app": { - "name": "app", - "kind": "alias", - "path": "openapi_first.templates.crud_app.test_crud_app.app", - "signature": "", - "docstring": null - }, "load_openapi": { "name": "load_openapi", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.load_openapi", - "signature": "", + "signature": "load_openapi(path: str | Path)", "docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup." }, "OpenAPIClient": { "name": "OpenAPIClient", "kind": "class", "path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient", - "signature": "", - "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", + "signature": "OpenAPIClient(spec: dict[str, Any], base_url: str | None = None, client: httpx.Client | None = None)", + "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nArgs:\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification.\n base_url (str | None):\n Base URL of the target service. If omitted, the first entry in the\n OpenAPI `servers` list is used.\n client (httpx.Client | None):\n Optional preconfigured httpx client instance.\n\nRaises:\n OpenAPIClientError:\n If no servers are defined, spec has no paths, operationIds are\n missing/duplicate, or required parameters are missing.\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", "members": { "spec": { "name": "spec", "kind": "attribute", "path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.spec", - "signature": "", + "signature": null, "docstring": null }, "base_url": { "name": "base_url", "kind": "attribute", "path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.base_url", - "signature": "", + "signature": null, "docstring": null }, "client": { "name": "client", "kind": "attribute", "path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.client", - "signature": "", + "signature": null, "docstring": null }, "operations": { "name": "operations", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.operations", - "signature": "", + "signature": "operations()", "docstring": null } } @@ -810,35 +502,35 @@ "name": "test_list_items_initial", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.test_list_items_initial", - "signature": "", + "signature": "test_list_items_initial()", "docstring": "Initial items should be present." }, "test_get_item": { "name": "test_get_item", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.test_get_item", - "signature": "", + "signature": "test_get_item()", "docstring": "Existing item should be retrievable by ID." }, "test_create_item": { "name": "test_create_item", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.test_create_item", - "signature": "", + "signature": "test_create_item()", "docstring": "Creating a new item should return the created entity." }, "test_update_item": { "name": "test_update_item", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.test_update_item", - "signature": "", + "signature": "test_update_item()", "docstring": "Updating an item should replace its values." }, "test_delete_item": { "name": "test_delete_item", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.test_delete_item", - "signature": "", + "signature": "test_delete_item()", "docstring": "Deleting an item should remove it from the store." } } @@ -850,7 +542,7 @@ "kind": "module", "path": "openapi_first.templates.health_app", "signature": null, - "docstring": "OpenAPI-first FastAPI application template.\n\nThis package contains a minimal, fully working example of an\nOpenAPI-first FastAPI service built using the ``openapi_first`` library.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace (``routes``)\n\nNo routing decorators, implicit behavior, or framework-specific\nconvenience abstractions are used. All HTTP routes, methods, and\noperation bindings are defined in OpenAPI and enforced at application\nstartup.\n\nThis package is intended to be copied as a starting point for new\nservices via the ``openapi-first`` CLI. It is not part of the\n``openapi_first`` library API surface.\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new OpenAPI-first health check service using the bundled\ntemplate:\n\n openapi-first health_app\n\nCreate the service in a custom directory:\n\n openapi-first health_app my-health-service\n\nList all available application templates:\n\n openapi-first --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client call for the ``get_health`` operation:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n response = client.get_health()\n\n assert response.status_code == 200\n assert response.json() == {\"status\": \"ok\"}\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Path and request parameters must match the OpenAPI specification\n- Invalid or incomplete OpenAPI specs fail at client construction time", + "docstring": "OpenAPI-first FastAPI application template.\n\nThis package contains a minimal, fully working example of an\nOpenAPI-first FastAPI service built using the ``openapi_first`` library.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace (``routes``)\n\nNo routing decorators, implicit behavior, or framework-specific\nconvenience abstractions are used. All HTTP routes, methods, and\noperation bindings are defined in OpenAPI and enforced at application\nstartup.\n\nThis package is intended to be copied as a starting point for new\nservices via the ``openapi-first`` CLI. It is not part of the\n``openapi_first`` library API surface.\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new OpenAPI-first health check service using the bundled\ntemplate:\n\n openapi-first scaffold health_app\n\nCreate the service in a custom directory:\n\n openapi-first scaffold health_app my-health-service\n\nList all available application templates:\n\n openapi-first scaffold --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client call for the ``get_health`` operation:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n response = client.get_health()\n\n assert response.status_code == 200\n assert response.json() == {\"status\": \"ok\"}\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Path and request parameters must match the OpenAPI specification\n- Invalid or incomplete OpenAPI specs fail at client construction time", "members": { "main": { "name": "main", @@ -863,25 +555,18 @@ "name": "OpenAPIFirstApp", "kind": "class", "path": "openapi_first.templates.health_app.main.OpenAPIFirstApp", - "signature": "", - "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", + "signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)", + "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", "members": { "openapi": { "name": "openapi", "kind": "attribute", "path": "openapi_first.templates.health_app.main.OpenAPIFirstApp.openapi", - "signature": "", + "signature": null, "docstring": null } } }, - "routes": { - "name": "routes", - "kind": "alias", - "path": "openapi_first.templates.health_app.main.routes", - "signature": "", - "docstring": null - }, "app": { "name": "app", "kind": "attribute", @@ -902,7 +587,7 @@ "name": "get_health", "kind": "function", "path": "openapi_first.templates.health_app.routes.get_health", - "signature": "", + "signature": "get_health() -> dict", "docstring": "Health check operation handler.\n\nThis function implements the OpenAPI operation identified by\n``operationId: get_health``.\n\nIt contains no routing metadata or framework-specific logic.\nRequest binding, HTTP method, and response semantics are defined\nexclusively by the OpenAPI specification.\n\nReturns\n-------\ndict\n A minimal liveness payload indicating service health." } } @@ -914,7 +599,7 @@ "kind": "module", "path": "openapi_first.templates.model_app", "signature": null, - "docstring": "OpenAPI-first model-based CRUD application template.\n\nThis package contains a complete, minimal example of an OpenAPI-first\nCRUD service that uses explicit Pydantic domain models for request and\nresponse schemas.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace implementing CRUD operations (``routes``)\n- Pydantic domain models (``models``)\n- an in-memory mock data store (``data``)\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification and enforced at application startup.\nNo decorator-driven routing or implicit framework behavior is used.\n\nThis template demonstrates:\n- operationId-driven server-side route binding\n- explicit request and response modeling with Pydantic\n- explicit HTTP status code control in handlers\n- operationId-driven client usage against the same OpenAPI contract\n- end-to-end validation using in-memory data and tests\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new model-based CRUD example service using the bundled template:\n\n openapi-first model_app\n\nCreate the service in a custom directory:\n\n openapi-first model_app my-model-service\n\nList all available application templates:\n\n openapi-first --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client calls for model-based CRUD operations:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List items\n response = client.list_items()\n\n # Get item by ID\n response = client.get_item(\n path_params={\"item_id\": 1}\n )\n\n # Create item\n response = client.create_item(\n body={\"name\": \"Orange\", \"price\": 0.8}\n )\n\n # Update item\n response = client.update_item(\n path_params={\"item_id\": 1},\n body={\"name\": \"Green Apple\", \"price\": 0.6},\n )\n\n # Delete item\n response = client.delete_item(\n path_params={\"item_id\": 1}\n )\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Request and response payloads conform to Pydantic models\n- Invalid or incomplete OpenAPI specs fail at client construction time\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.", + "docstring": "OpenAPI-first model-based CRUD application template.\n\nThis package contains a complete, minimal example of an OpenAPI-first\nCRUD service that uses explicit Pydantic domain models for request and\nresponse schemas.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace implementing CRUD operations (``routes``)\n- Pydantic domain models (``models``)\n- an in-memory mock data store (``data``)\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification and enforced at application startup.\nNo decorator-driven routing or implicit framework behavior is used.\n\nThis template demonstrates:\n- operationId-driven server-side route binding\n- explicit request and response modeling with Pydantic\n- explicit HTTP status code control in handlers\n- operationId-driven client usage against the same OpenAPI contract\n- end-to-end validation using in-memory data and tests\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new model-based CRUD example service using the bundled template:\n\n openapi-first scaffold model_app\n\nCreate the service in a custom directory:\n\n openapi-first scaffold model_app my-model-service\n\nList all available application templates:\n\n openapi-first scaffold --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client calls for model-based CRUD operations:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List items\n response = client.list_items()\n\n # Get item by ID\n response = client.get_item(\n path_params={\"item_id\": 1}\n )\n\n # Create item\n response = client.create_item(\n body={\"name\": \"Orange\", \"price\": 0.8}\n )\n\n # Update item\n response = client.update_item(\n path_params={\"item_id\": 1},\n body={\"name\": \"Green Apple\", \"price\": 0.6},\n )\n\n # Delete item\n response = client.delete_item(\n path_params={\"item_id\": 1}\n )\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Request and response payloads conform to Pydantic models\n- Invalid or incomplete OpenAPI specs fail at client construction time\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.", "members": { "data": { "name": "data", @@ -923,60 +608,39 @@ "signature": null, "docstring": "In-memory data store using Pydantic models.\n\nThis module is NOT thread-safe and is intended for demos and scaffolds only.\nThis module provides a minimal, process-local data store for the\nmodel-based CRUD example application. It stores and returns domain\nobjects defined using Pydantic models and is intended solely for\ndemonstration and scaffolding purposes.\n\nThe implementation intentionally avoids:\n- persistence\n- concurrency guarantees\n- transactional semantics\n- validation beyond what Pydantic provides\n\nIt is not part of the ``openapi_first`` library API surface.", "members": { - "Dict": { - "name": "Dict", - "kind": "alias", - "path": "openapi_first.templates.model_app.data.Dict", - "signature": "", - "docstring": null - }, - "Item": { - "name": "Item", - "kind": "alias", - "path": "openapi_first.templates.model_app.data.Item", - "signature": "", - "docstring": null - }, - "ItemCreate": { - "name": "ItemCreate", - "kind": "alias", - "path": "openapi_first.templates.model_app.data.ItemCreate", - "signature": "", - "docstring": null - }, "list_items": { "name": "list_items", "kind": "function", "path": "openapi_first.templates.model_app.data.list_items", - "signature": "", + "signature": "list_items() -> list[Item]", "docstring": "Return all items in the data store.\n\nReturns\n-------\nlist[Item]\n A list of item domain objects." }, "get_item": { "name": "get_item", "kind": "function", "path": "openapi_first.templates.model_app.data.get_item", - "signature": "", + "signature": "get_item(item_id: int) -> Item", "docstring": "Retrieve a single item by ID.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\nItem\n The requested item.\n\nRaises\n------\nKeyError\n If the item does not exist." }, "create_item": { "name": "create_item", "kind": "function", "path": "openapi_first.templates.model_app.data.create_item", - "signature": "", + "signature": "create_item(payload: ItemCreate) -> Item", "docstring": "Create a new item in the data store.\n\nA new identifier is assigned automatically. No additional validation\nis performed beyond Pydantic model validation.\n\nParameters\n----------\npayload : ItemCreate\n Data required to create a new item.\n\nReturns\n-------\nItem\n The newly created item." }, "update_item": { "name": "update_item", "kind": "function", "path": "openapi_first.templates.model_app.data.update_item", - "signature": "", + "signature": "update_item(item_id: int, payload: ItemCreate) -> Item", "docstring": "Replace an existing item in the data store.\n\nThis function performs a full replacement of the stored item.\nPartial updates are not supported.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : ItemCreate\n New item data.\n\nReturns\n-------\nItem\n The updated item.\n\nRaises\n------\nKeyError\n If the item does not exist." }, "delete_item": { "name": "delete_item", "kind": "function", "path": "openapi_first.templates.model_app.data.delete_item", - "signature": "", + "signature": "delete_item(item_id: int) -> None", "docstring": "Remove an item from the data store.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\n\nRaises\n------\nKeyError\n If the item does not exist." } } @@ -992,25 +656,18 @@ "name": "OpenAPIFirstApp", "kind": "class", "path": "openapi_first.templates.model_app.main.OpenAPIFirstApp", - "signature": "", - "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", + "signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)", + "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", "members": { "openapi": { "name": "openapi", "kind": "attribute", "path": "openapi_first.templates.model_app.main.OpenAPIFirstApp.openapi", - "signature": "", + "signature": null, "docstring": null } } }, - "routes": { - "name": "routes", - "kind": "alias", - "path": "openapi_first.templates.model_app.main.routes", - "signature": "", - "docstring": null - }, "app": { "name": "app", "kind": "attribute", @@ -1027,18 +684,11 @@ "signature": null, "docstring": "Pydantic domain models for the CRUD example.\n\nThis module defines Pydantic models that represent the domain entities\nused by the service. These models are referenced by the OpenAPI\nspecification for request and response schemas.\n\nThe models are declarative and framework-agnostic. They contain no\npersistence logic, validation beyond type constraints, or business\nbehavior.\n\nThis module is not part of the ``openapi_first`` library API surface.\nIt exists solely to support the example application template.", "members": { - "BaseModel": { - "name": "BaseModel", - "kind": "alias", - "path": "openapi_first.templates.model_app.models.BaseModel", - "signature": "", - "docstring": null - }, "ItemBase": { "name": "ItemBase", "kind": "class", "path": "openapi_first.templates.model_app.models.ItemBase", - "signature": "", + "signature": null, "docstring": "Base domain model for an item.\n\nDefines fields common to all item representations.", "members": { "name": { @@ -1061,14 +711,14 @@ "name": "ItemCreate", "kind": "class", "path": "openapi_first.templates.model_app.models.ItemCreate", - "signature": "", + "signature": null, "docstring": "Domain model for item creation requests.\n\nThis model is used for request bodies when creating new items.\nIt intentionally excludes the ``id`` field, which is assigned\nby the service." }, "Item": { "name": "Item", "kind": "class", "path": "openapi_first.templates.model_app.models.Item", - "signature": "", + "signature": null, "docstring": "Domain model for a persisted item.\n\nThis model represents the full item state returned in responses,\nincluding the server-assigned identifier.", "members": { "id": { @@ -1089,61 +739,40 @@ "signature": null, "docstring": "CRUD route handlers bound via OpenAPI operationId.\n\nThis module defines OpenAPI-bound operation handlers for a model-based\nCRUD service. Functions in this module are bound to HTTP routes\nexclusively via OpenAPI ``operationId`` values.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Domain models defined using\nPydantic are used for request and response payloads.\n\nNo routing decorators, path definitions, or implicit framework behavior\nappear in this module. All routing, HTTP methods, and schemas are defined\nin the OpenAPI specification.", "members": { - "Response": { - "name": "Response", - "kind": "alias", - "path": "openapi_first.templates.model_app.routes.Response", - "signature": "", - "docstring": null - }, - "HTTPException": { - "name": "HTTPException", - "kind": "alias", - "path": "openapi_first.templates.model_app.routes.HTTPException", - "signature": "", - "docstring": null - }, - "ItemCreate": { - "name": "ItemCreate", - "kind": "alias", - "path": "openapi_first.templates.model_app.routes.ItemCreate", - "signature": "", - "docstring": null - }, "list_items": { "name": "list_items", "kind": "function", "path": "openapi_first.templates.model_app.routes.list_items", - "signature": "", + "signature": "list_items() -> list[Item]", "docstring": "List all items.\n\nImplements the OpenAPI operation identified by\n``operationId: list_items``.\n\nReturns\n-------\nlist[Item]\n A list of item domain objects." }, "get_item": { "name": "get_item", "kind": "function", "path": "openapi_first.templates.model_app.routes.get_item", - "signature": "", + "signature": "get_item(item_id: int) -> Item", "docstring": "Retrieve a single item by ID.\n\nImplements the OpenAPI operation identified by\n``operationId: get_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\nItem\n The requested item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." }, "create_item": { "name": "create_item", "kind": "function", "path": "openapi_first.templates.model_app.routes.create_item", - "signature": "", - "docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : ItemCreate\n Request body describing the item to create.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nItem\n The newly created item." + "signature": "create_item(payload: ItemCreate, response: Response) -> Item", + "docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : ItemCreate\n Request body describing the item to create.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nItem\n The newly created item." }, "update_item": { "name": "update_item", "kind": "function", "path": "openapi_first.templates.model_app.routes.update_item", - "signature": "", + "signature": "update_item(item_id: int, payload: ItemCreate) -> Item", "docstring": "Update an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: update_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : ItemCreate\n New item data.\n\nReturns\n-------\nItem\n The updated item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." }, "delete_item": { "name": "delete_item", "kind": "function", "path": "openapi_first.templates.model_app.routes.delete_item", - "signature": "", - "docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." + "signature": "delete_item(item_id: int, response: Response) -> None", + "docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." } } }, @@ -1154,60 +783,46 @@ "signature": null, "docstring": "End-to-end tests for the OpenAPI-first model CRUD example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store using Pydantic models.\n- OpenAPI specification loading\n- OperationId-driven route binding on the server\n- OperationId-driven client invocation\n- Pydantic model-based request and response handling\n\nAll CRUD operations are exercised against an in-memory mock data store\nbacked by Pydantic domain models.\n\nThe tests assume:\n- OpenAPI-first route binding\n- Pydantic model validation\n- In-memory storage (no persistence guarantees)\n- Deterministic behavior in a single process", "members": { - "TestClient": { - "name": "TestClient", - "kind": "alias", - "path": "openapi_first.templates.model_app.test_model_app.TestClient", - "signature": "", - "docstring": null - }, - "app": { - "name": "app", - "kind": "alias", - "path": "openapi_first.templates.model_app.test_model_app.app", - "signature": "", - "docstring": null - }, "load_openapi": { "name": "load_openapi", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.load_openapi", - "signature": "", + "signature": "load_openapi(path: str | Path)", "docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup." }, "OpenAPIClient": { "name": "OpenAPIClient", "kind": "class", "path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient", - "signature": "", - "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", + "signature": "OpenAPIClient(spec: dict[str, Any], base_url: str | None = None, client: httpx.Client | None = None)", + "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nArgs:\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification.\n base_url (str | None):\n Base URL of the target service. If omitted, the first entry in the\n OpenAPI `servers` list is used.\n client (httpx.Client | None):\n Optional preconfigured httpx client instance.\n\nRaises:\n OpenAPIClientError:\n If no servers are defined, spec has no paths, operationIds are\n missing/duplicate, or required parameters are missing.\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", "members": { "spec": { "name": "spec", "kind": "attribute", "path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.spec", - "signature": "", + "signature": null, "docstring": null }, "base_url": { "name": "base_url", "kind": "attribute", "path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.base_url", - "signature": "", + "signature": null, "docstring": null }, "client": { "name": "client", "kind": "attribute", "path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.client", - "signature": "", + "signature": null, "docstring": null }, "operations": { "name": "operations", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.operations", - "signature": "", + "signature": "operations()", "docstring": null } } @@ -1230,35 +845,35 @@ "name": "test_list_items_initial", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.test_list_items_initial", - "signature": "", + "signature": "test_list_items_initial()", "docstring": "Initial items should be present." }, "test_get_item": { "name": "test_get_item", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.test_get_item", - "signature": "", + "signature": "test_get_item()", "docstring": "Existing item should be retrievable by ID." }, "test_create_item": { "name": "test_create_item", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.test_create_item", - "signature": "", + "signature": "test_create_item()", "docstring": "Creating a new item should return the created entity." }, "test_update_item": { "name": "test_update_item", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.test_update_item", - "signature": "", + "signature": "test_update_item()", "docstring": "Updating an item should replace its values." }, "test_delete_item": { "name": "test_delete_item", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.test_delete_item", - "signature": "", + "signature": "test_delete_item()", "docstring": "Deleting an item should remove it from the store." } } @@ -1270,7 +885,7 @@ "kind": "module", "path": "openapi_first.templates.vet_app", "signature": null, - "docstring": "OpenAPI-first Veterinary Clinic application template.\n\nThis package contains a complete, runnable example of an OpenAPI-first\nveterinary clinic management service. It demonstrates all ``x-`` extension\nfields consumed by the ``react-openapi`` admin panel renderer.\n\nThe application manages five resources:\n\n- **Parents** — pet owners with contact details\n- **Vets** — veterinarians with specializations\n- **Treatments** — medical procedure catalog\n- **Pets** — animals with species, age, weight, and photos\n- **Appointments** — scheduled visits linking pets, vets, and treatments\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification (``openapi.yaml``). Every operation has an\nexplicit ``operationId`` that maps to a Python handler in ``routes.py``.\n\nThis file is a copyable template. It is not part of the ``openapi_first``\nlibrary API surface.\n\n----------------------------------------------------------------------\nOpenAPI x- extension fields demonstrated\n----------------------------------------------------------------------\n\nSchema-level extensions (display metadata for resource endpoints):\n\n ``x-primary-key`` (REQUIRED) Primary key property name\n ``x-display-format`` (REQUIRED) Human-readable label template\n ``x-list-columns`` (REQUIRED) Columns for the datatable\n\nProperty-level extensions (control UI rendering):\n\n ``x-label`` (REQUIRED) Human-readable field label\n ``x-order`` (REQUIRED) Field ordering in forms/detail\n ``x-description`` (optional) Helper text below form fields\n ``x-hidden`` (optional) Visibility in form / list / detail\n ``x-filterable`` (optional) Allows column filtering\n ``x-sortable`` (optional) Allows column sorting\n ``x-fk`` (optional) Foreign key — renders as dropdown\n ``x-fk.resource`` (REQUIRED for FK) Target resource name\n ``x-fk.prefetch`` (optional) Preload all FK options on mount\n ``x-ui-type`` (optional) Custom UI type (e.g. image upload)\n ``x-upload-url`` (optional) Upload endpoint for binary fields\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new vet clinic service using the bundled template:\n\n openapi-first vet_app\n\nCreate the service in a custom directory:\n\n openapi-first vet_app my-vet-clinic\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List pets with pagination\n response = client.list_pets(query_params={\"limit\": 10, \"offset\": 0})\n\n # Create a pet with FK references\n response = client.create_pet(\n body={\"name\": \"Fido\", \"species\": \"dog\", \"parents\": [1, 2]}\n )\n\n # Upload a pet photo\n response = client.upload_pet_photo(\n path_params={\"id\": 1},\n body={\"file\": open(\"photo.jpg\", \"rb\")},\n )\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.", + "docstring": "OpenAPI-first Veterinary Clinic application template.\n\nThis package contains a complete, runnable example of an OpenAPI-first\nveterinary clinic management service. It demonstrates all ``x-`` extension\nfields consumed by the ``react-openapi`` admin panel renderer.\n\nThe application manages five resources:\n\n- **Parents** — pet owners with contact details\n- **Vets** — veterinarians with specializations\n- **Treatments** — medical procedure catalog\n- **Pets** — animals with species, age, weight, and photos\n- **Appointments** — scheduled visits linking pets, vets, and treatments\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification (``openapi.yaml``). Every operation has an\nexplicit ``operationId`` that maps to a Python handler in ``routes.py``.\n\nThis file is a copyable template. It is not part of the ``openapi_first``\nlibrary API surface.\n\n----------------------------------------------------------------------\nOpenAPI x- extension fields demonstrated\n----------------------------------------------------------------------\n\nSchema-level extensions (display metadata for resource endpoints):\n\n ``x-primary-key`` (REQUIRED) Primary key property name\n ``x-display-format`` (REQUIRED) Human-readable label template\n ``x-list-columns`` (REQUIRED) Columns for the datatable\n\nProperty-level extensions (control UI rendering):\n\n ``x-label`` (REQUIRED) Human-readable field label\n ``x-order`` (REQUIRED) Field ordering in forms/detail\n ``x-description`` (optional) Helper text below form fields\n ``x-hidden`` (optional) Visibility in form / list / detail\n ``x-filterable`` (optional) Allows column filtering\n ``x-sortable`` (optional) Allows column sorting\n ``x-fk`` (optional) Foreign key — renders as dropdown\n ``x-fk.resource`` (REQUIRED for FK) Target resource name\n ``x-fk.prefetch`` (optional) Preload all FK options on mount\n ``x-ui-type`` (optional) Custom UI type (e.g. image upload)\n ``x-upload-url`` (optional) Upload endpoint for binary fields\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new vet clinic service using the bundled template:\n\n openapi-first scaffold vet_app\n\nCreate the service in a custom directory:\n\n openapi-first scaffold vet_app my-vet-clinic\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List pets with pagination\n response = client.list_pets(query_params={\"limit\": 10, \"offset\": 0})\n\n # Create a pet with FK references\n response = client.create_pet(\n body={\"name\": \"Fido\", \"species\": \"dog\", \"parents\": [1, 2]}\n )\n\n # Upload a pet photo\n response = client.upload_pet_photo(\n path_params={\"id\": 1},\n body={\"file\": open(\"photo.jpg\", \"rb\")},\n )\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.", "members": { "data": { "name": "data", @@ -1279,319 +894,179 @@ "signature": null, "docstring": "In-memory data store for the Veterinary Clinic example.\n\nThis module is NOT thread-safe and is intended for demos and scaffolds only.\n\nIt provides minimal, process-local data stores for the five veterinary\nclinic entities. Each store exposes standard CRUD operations backed by\na simple dictionary.\n\nThis module intentionally avoids:\n- persistence\n- concurrency guarantees\n- transactional semantics\n- validation beyond what Pydantic provides\n\nThis module is not part of the ``openapi_first`` library API surface.", "members": { - "date": { - "name": "date", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.date", - "signature": "", - "docstring": null - }, - "datetime": { - "name": "datetime", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.datetime", - "signature": "", - "docstring": null - }, - "timezone": { - "name": "timezone", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.timezone", - "signature": "", - "docstring": null - }, - "Parent": { - "name": "Parent", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Parent", - "signature": "", - "docstring": null - }, - "ParentCreate": { - "name": "ParentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.ParentCreate", - "signature": "", - "docstring": null - }, - "Vet": { - "name": "Vet", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Vet", - "signature": "", - "docstring": null - }, - "VetCreate": { - "name": "VetCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.VetCreate", - "signature": "", - "docstring": null - }, - "Treatment": { - "name": "Treatment", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Treatment", - "signature": "", - "docstring": null - }, - "TreatmentCreate": { - "name": "TreatmentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.TreatmentCreate", - "signature": "", - "docstring": null - }, - "Procedure": { - "name": "Procedure", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Procedure", - "signature": "", - "docstring": null - }, - "BasicNote": { - "name": "BasicNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.BasicNote", - "signature": "", - "docstring": null - }, - "HeartRateNote": { - "name": "HeartRateNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.HeartRateNote", - "signature": "", - "docstring": null - }, - "DentalNote": { - "name": "DentalNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.DentalNote", - "signature": "", - "docstring": null - }, - "VaccineNote": { - "name": "VaccineNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.VaccineNote", - "signature": "", - "docstring": null - }, - "PreOpNote": { - "name": "PreOpNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.PreOpNote", - "signature": "", - "docstring": null - }, - "SurgeryNote": { - "name": "SurgeryNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.SurgeryNote", - "signature": "", - "docstring": null - }, - "Pet": { - "name": "Pet", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Pet", - "signature": "", - "docstring": null - }, - "PetCreate": { - "name": "PetCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.PetCreate", - "signature": "", - "docstring": null - }, - "Appointment": { - "name": "Appointment", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Appointment", - "signature": "", - "docstring": null - }, - "AppointmentCreate": { - "name": "AppointmentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.AppointmentCreate", - "signature": "", - "docstring": null - }, "list_parents": { "name": "list_parents", "kind": "function", "path": "openapi_first.templates.vet_app.data.list_parents", - "signature": "", + "signature": "list_parents() -> list[Parent]", "docstring": null }, "get_parent": { "name": "get_parent", "kind": "function", "path": "openapi_first.templates.vet_app.data.get_parent", - "signature": "", + "signature": "get_parent(parent_id: int) -> Parent", "docstring": null }, "create_parent": { "name": "create_parent", "kind": "function", "path": "openapi_first.templates.vet_app.data.create_parent", - "signature": "", + "signature": "create_parent(payload: ParentCreate) -> Parent", "docstring": null }, "update_parent": { "name": "update_parent", "kind": "function", "path": "openapi_first.templates.vet_app.data.update_parent", - "signature": "", + "signature": "update_parent(parent_id: int, payload: ParentCreate) -> Parent", "docstring": null }, "delete_parent": { "name": "delete_parent", "kind": "function", "path": "openapi_first.templates.vet_app.data.delete_parent", - "signature": "", + "signature": "delete_parent(parent_id: int) -> None", "docstring": null }, "list_vets": { "name": "list_vets", "kind": "function", "path": "openapi_first.templates.vet_app.data.list_vets", - "signature": "", + "signature": "list_vets() -> list[Vet]", "docstring": null }, "get_vet": { "name": "get_vet", "kind": "function", "path": "openapi_first.templates.vet_app.data.get_vet", - "signature": "", + "signature": "get_vet(vet_id: int) -> Vet", "docstring": null }, "create_vet": { "name": "create_vet", "kind": "function", "path": "openapi_first.templates.vet_app.data.create_vet", - "signature": "", + "signature": "create_vet(payload: VetCreate) -> Vet", "docstring": null }, "update_vet": { "name": "update_vet", "kind": "function", "path": "openapi_first.templates.vet_app.data.update_vet", - "signature": "", + "signature": "update_vet(vet_id: int, payload: VetCreate) -> Vet", "docstring": null }, "delete_vet": { "name": "delete_vet", "kind": "function", "path": "openapi_first.templates.vet_app.data.delete_vet", - "signature": "", + "signature": "delete_vet(vet_id: int) -> None", "docstring": null }, "list_treatments": { "name": "list_treatments", "kind": "function", "path": "openapi_first.templates.vet_app.data.list_treatments", - "signature": "", + "signature": "list_treatments() -> list[Treatment]", "docstring": null }, "get_treatment": { "name": "get_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.data.get_treatment", - "signature": "", + "signature": "get_treatment(treatment_id: int) -> Treatment", "docstring": null }, "create_treatment": { "name": "create_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.data.create_treatment", - "signature": "", + "signature": "create_treatment(payload: TreatmentCreate) -> Treatment", "docstring": null }, "update_treatment": { "name": "update_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.data.update_treatment", - "signature": "", + "signature": "update_treatment(treatment_id: int, payload: TreatmentCreate) -> Treatment", "docstring": null }, "delete_treatment": { "name": "delete_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.data.delete_treatment", - "signature": "", + "signature": "delete_treatment(treatment_id: int) -> None", "docstring": null }, "list_pets": { "name": "list_pets", "kind": "function", "path": "openapi_first.templates.vet_app.data.list_pets", - "signature": "", + "signature": "list_pets() -> list[Pet]", "docstring": null }, "get_pet": { "name": "get_pet", "kind": "function", "path": "openapi_first.templates.vet_app.data.get_pet", - "signature": "", + "signature": "get_pet(pet_id: int) -> Pet", "docstring": null }, "create_pet": { "name": "create_pet", "kind": "function", "path": "openapi_first.templates.vet_app.data.create_pet", - "signature": "", + "signature": "create_pet(payload: PetCreate) -> Pet", "docstring": null }, "update_pet": { "name": "update_pet", "kind": "function", "path": "openapi_first.templates.vet_app.data.update_pet", - "signature": "", + "signature": "update_pet(pet_id: int, payload: PetCreate) -> Pet", "docstring": null }, "delete_pet": { "name": "delete_pet", "kind": "function", "path": "openapi_first.templates.vet_app.data.delete_pet", - "signature": "", + "signature": "delete_pet(pet_id: int) -> None", "docstring": null }, "list_appointments": { "name": "list_appointments", "kind": "function", "path": "openapi_first.templates.vet_app.data.list_appointments", - "signature": "", + "signature": "list_appointments() -> list[Appointment]", "docstring": null }, "get_appointment": { "name": "get_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.data.get_appointment", - "signature": "", + "signature": "get_appointment(appointment_id: int) -> Appointment", "docstring": null }, "create_appointment": { "name": "create_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.data.create_appointment", - "signature": "", + "signature": "create_appointment(payload: AppointmentCreate) -> Appointment", "docstring": null }, "update_appointment": { "name": "update_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.data.update_appointment", - "signature": "", + "signature": "update_appointment(appointment_id: int, payload: AppointmentCreate) -> Appointment", "docstring": null }, "delete_appointment": { "name": "delete_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.data.delete_appointment", - "signature": "", + "signature": "delete_appointment(appointment_id: int) -> None", "docstring": null } } @@ -1603,48 +1078,27 @@ "signature": null, "docstring": "Application entry point for an OpenAPI-first Veterinary Clinic service.\n\nThis module constructs a FastAPI application exclusively from an\nOpenAPI specification and a handler namespace, without using\ndecorator-driven routing.\n\nAll HTTP routes, methods, request/response schemas, and operation\nbindings are defined in the OpenAPI document referenced by\n``openapi_path``. Python callables defined in the ``routes`` module are\nbound to OpenAPI operations strictly via ``operationId``.\n\nThis module contains no routing logic, persistence concerns, or\nframework configuration beyond application assembly.\n\nDesign guarantees:\n- OpenAPI is the single source of truth\n- No undocumented routes can exist\n- Every OpenAPI operationId must resolve to exactly one handler\n- All contract violations fail at application startup\n\nThis file is intended to be used as the ASGI entry point.\n\nExample:\n uvicorn main:app", "members": { - "asynccontextmanager": { - "name": "asynccontextmanager", - "kind": "alias", - "path": "openapi_first.templates.vet_app.main.asynccontextmanager", - "signature": "", - "docstring": null - }, - "CORSMiddleware": { - "name": "CORSMiddleware", - "kind": "alias", - "path": "openapi_first.templates.vet_app.main.CORSMiddleware", - "signature": "", - "docstring": null - }, "OpenAPIFirstApp": { "name": "OpenAPIFirstApp", "kind": "class", "path": "openapi_first.templates.vet_app.main.OpenAPIFirstApp", - "signature": "", - "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", + "signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)", + "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", "members": { "openapi": { "name": "openapi", "kind": "attribute", "path": "openapi_first.templates.vet_app.main.OpenAPIFirstApp.openapi", - "signature": "", + "signature": null, "docstring": null } } }, - "routes": { - "name": "routes", - "kind": "alias", - "path": "openapi_first.templates.vet_app.main.routes", - "signature": "", - "docstring": null - }, "lifespan": { "name": "lifespan", "kind": "function", "path": "openapi_first.templates.vet_app.main.lifespan", - "signature": "", + "signature": "lifespan(app)", "docstring": null }, "app": { @@ -1663,53 +1117,11 @@ "signature": null, "docstring": null, "members": { - "date": { - "name": "date", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.date", - "signature": "", - "docstring": null - }, - "datetime": { - "name": "datetime", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.datetime", - "signature": "", - "docstring": null - }, - "Annotated": { - "name": "Annotated", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.Annotated", - "signature": "", - "docstring": null - }, - "Literal": { - "name": "Literal", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.Literal", - "signature": "", - "docstring": null - }, - "BaseModel": { - "name": "BaseModel", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.BaseModel", - "signature": "", - "docstring": null - }, - "Field": { - "name": "Field", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.Field", - "signature": "", - "docstring": null - }, "Metadata": { "name": "Metadata", "kind": "class", "path": "openapi_first.templates.vet_app.models.Metadata", - "signature": "", + "signature": null, "docstring": null, "members": { "createdOn": { @@ -1732,7 +1144,7 @@ "name": "ParentBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.ParentBase", - "signature": "", + "signature": null, "docstring": null, "members": { "name": { @@ -1769,14 +1181,14 @@ "name": "ParentCreate", "kind": "class", "path": "openapi_first.templates.vet_app.models.ParentCreate", - "signature": "", + "signature": null, "docstring": null }, "Parent": { "name": "Parent", "kind": "class", "path": "openapi_first.templates.vet_app.models.Parent", - "signature": "", + "signature": null, "docstring": null, "members": { "id": { @@ -1792,7 +1204,7 @@ "name": "VetBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.VetBase", - "signature": "", + "signature": null, "docstring": null, "members": { "name": { @@ -1836,14 +1248,14 @@ "name": "VetCreate", "kind": "class", "path": "openapi_first.templates.vet_app.models.VetCreate", - "signature": "", + "signature": null, "docstring": null }, "Vet": { "name": "Vet", "kind": "class", "path": "openapi_first.templates.vet_app.models.Vet", - "signature": "", + "signature": null, "docstring": null, "members": { "id": { @@ -1859,7 +1271,7 @@ "name": "ProcedureNoteBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.ProcedureNoteBase", - "signature": "", + "signature": null, "docstring": null, "members": { "summary": { @@ -1882,7 +1294,7 @@ "name": "BasicNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.BasicNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -1898,7 +1310,7 @@ "name": "HeartRateNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.HeartRateNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -1921,7 +1333,7 @@ "name": "DentalNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.DentalNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -1951,7 +1363,7 @@ "name": "VaccineNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.VaccineNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -1981,7 +1393,7 @@ "name": "PreOpNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.PreOpNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -2011,7 +1423,7 @@ "name": "SurgeryNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.SurgeryNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -2048,7 +1460,7 @@ "name": "Procedure", "kind": "class", "path": "openapi_first.templates.vet_app.models.Procedure", - "signature": "", + "signature": null, "docstring": null, "members": { "name": { @@ -2085,7 +1497,7 @@ "name": "TreatmentBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.TreatmentBase", - "signature": "", + "signature": null, "docstring": null, "members": { "label": { @@ -2115,14 +1527,14 @@ "name": "TreatmentCreate", "kind": "class", "path": "openapi_first.templates.vet_app.models.TreatmentCreate", - "signature": "", + "signature": null, "docstring": null }, "Treatment": { "name": "Treatment", "kind": "class", "path": "openapi_first.templates.vet_app.models.Treatment", - "signature": "", + "signature": null, "docstring": null, "members": { "id": { @@ -2138,7 +1550,7 @@ "name": "PetBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.PetBase", - "signature": "", + "signature": null, "docstring": null, "members": { "name": { @@ -2196,7 +1608,7 @@ "name": "PetCreate", "kind": "class", "path": "openapi_first.templates.vet_app.models.PetCreate", - "signature": "", + "signature": null, "docstring": null, "members": { "parent_ids": { @@ -2212,7 +1624,7 @@ "name": "Pet", "kind": "class", "path": "openapi_first.templates.vet_app.models.Pet", - "signature": "", + "signature": null, "docstring": null, "members": { "id": { @@ -2235,7 +1647,7 @@ "name": "AppointmentBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.AppointmentBase", - "signature": "", + "signature": null, "docstring": null, "members": { "date": { @@ -2272,7 +1684,7 @@ "name": "AppointmentCreate", "kind": "class", "path": "openapi_first.templates.vet_app.models.AppointmentCreate", - "signature": "", + "signature": null, "docstring": null, "members": { "pet_id": { @@ -2302,7 +1714,7 @@ "name": "Appointment", "kind": "class", "path": "openapi_first.templates.vet_app.models.Appointment", - "signature": "", + "signature": null, "docstring": null, "members": { "id": { @@ -2344,270 +1756,193 @@ "signature": null, "docstring": "Veterinary Clinic route handlers bound via OpenAPI operationId.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Domain models defined using\nPydantic are used for request and response payloads.\n\nNo routing decorators, path definitions, or implicit framework behavior\nappear in this module. All routing, HTTP methods, and schemas are defined\nin the OpenAPI specification.", "members": { - "Response": { - "name": "Response", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.Response", - "signature": "", - "docstring": null - }, - "HTTPException": { - "name": "HTTPException", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.HTTPException", - "signature": "", - "docstring": null - }, - "UploadFile": { - "name": "UploadFile", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.UploadFile", - "signature": "", - "docstring": null - }, - "StreamingResponse": { - "name": "StreamingResponse", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.StreamingResponse", - "signature": "", - "docstring": null - }, - "subscribe": { - "name": "subscribe", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.subscribe", - "signature": "", - "docstring": null - }, - "unsubscribe": { - "name": "unsubscribe", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.unsubscribe", - "signature": "", - "docstring": null - }, - "ParentCreate": { - "name": "ParentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.ParentCreate", - "signature": "", - "docstring": null - }, - "VetCreate": { - "name": "VetCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.VetCreate", - "signature": "", - "docstring": null - }, - "TreatmentCreate": { - "name": "TreatmentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.TreatmentCreate", - "signature": "", - "docstring": null - }, - "PetCreate": { - "name": "PetCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.PetCreate", - "signature": "", - "docstring": null - }, - "AppointmentCreate": { - "name": "AppointmentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.AppointmentCreate", - "signature": "", - "docstring": null - }, "list_parents": { "name": "list_parents", "kind": "function", "path": "openapi_first.templates.vet_app.routes.list_parents", - "signature": "", + "signature": "list_parents(limit: int = 20, offset: int = 0) -> dict", "docstring": "List parents (paginated).\n\nParameters\n----------\nlimit : int\n Maximum number of records to return.\noffset : int\n Number of records to skip.\n\nReturns\n-------\ndict\n Paginated response with ``total`` and ``items``." }, "create_parent": { "name": "create_parent", "kind": "function", "path": "openapi_first.templates.vet_app.routes.create_parent", - "signature": "", - "docstring": "Create a parent.\n\nParameters\n----------\npayload : ParentCreate\n Parent data excluding the ``id`` field.\n\nReturns\n-------\nParent\n The newly created parent." + "signature": "create_parent(payload: ParentCreate, response: Response) -> Parent", + "docstring": "Create a parent.\n\nParameters\n----------\npayload : ParentCreate\n Parent data excluding the ``id`` field.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nParent\n The newly created parent." }, "get_parent": { "name": "get_parent", "kind": "function", "path": "openapi_first.templates.vet_app.routes.get_parent", - "signature": "", + "signature": "get_parent(id: int) -> Parent", "docstring": "Retrieve a single parent by ID.\n\nParameters\n----------\nid : int\n Identifier of the parent.\n\nReturns\n-------\nParent\n The requested parent.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist." }, "update_parent": { "name": "update_parent", "kind": "function", "path": "openapi_first.templates.vet_app.routes.update_parent", - "signature": "", + "signature": "update_parent(id: int, payload: ParentCreate) -> Parent", "docstring": "Update an existing parent.\n\nParameters\n----------\nid : int\n Identifier of the parent.\npayload : ParentCreate\n Updated parent data.\n\nReturns\n-------\nParent\n The updated parent.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist." }, "delete_parent": { "name": "delete_parent", "kind": "function", "path": "openapi_first.templates.vet_app.routes.delete_parent", - "signature": "", - "docstring": "Delete an existing parent.\n\nParameters\n----------\nid : int\n Identifier of the parent.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist." + "signature": "delete_parent(id: int, response: Response) -> None", + "docstring": "Delete an existing parent.\n\nParameters\n----------\nid : int\n Identifier of the parent.\nresponse : Response\n Response object used to set the HTTP status code.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist." }, "list_vets": { "name": "list_vets", "kind": "function", "path": "openapi_first.templates.vet_app.routes.list_vets", - "signature": "", + "signature": "list_vets(limit: int = 20, offset: int = 0)", "docstring": "List vets (paginated)." }, "create_vet": { "name": "create_vet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.create_vet", - "signature": "", + "signature": "create_vet(payload: VetCreate, response: Response)", "docstring": "Create a vet." }, "get_vet": { "name": "get_vet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.get_vet", - "signature": "", + "signature": "get_vet(id: int)", "docstring": "Retrieve a single vet by ID." }, "update_vet": { "name": "update_vet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.update_vet", - "signature": "", + "signature": "update_vet(id: int, payload: VetCreate)", "docstring": "Update an existing vet." }, "delete_vet": { "name": "delete_vet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.delete_vet", - "signature": "", + "signature": "delete_vet(id: int, response: Response)", "docstring": "Delete an existing vet." }, "list_treatments": { "name": "list_treatments", "kind": "function", "path": "openapi_first.templates.vet_app.routes.list_treatments", - "signature": "", + "signature": "list_treatments() -> list[Treatment]", "docstring": "List treatments (catalogue).\n\nReturns\n-------\nlist[Treatment]\n A list of treatment domain objects." }, "create_treatment": { "name": "create_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.create_treatment", - "signature": "", + "signature": "create_treatment(payload: TreatmentCreate, response: Response)", "docstring": "Add a treatment (admin only)." }, "get_treatment": { "name": "get_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.get_treatment", - "signature": "", + "signature": "get_treatment(id: int)", "docstring": "Retrieve a single treatment by ID." }, "update_treatment": { "name": "update_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.update_treatment", - "signature": "", + "signature": "update_treatment(id: int, payload: TreatmentCreate)", "docstring": "Update an existing treatment." }, "delete_treatment": { "name": "delete_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.delete_treatment", - "signature": "", + "signature": "delete_treatment(id: int, response: Response)", "docstring": "Delete an existing treatment." }, "list_pets": { "name": "list_pets", "kind": "function", "path": "openapi_first.templates.vet_app.routes.list_pets", - "signature": "", + "signature": "list_pets(limit: int = 20, offset: int = 0)", "docstring": "List pets (paginated)." }, "create_pet": { "name": "create_pet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.create_pet", - "signature": "", + "signature": "create_pet(payload: PetCreate, response: Response)", "docstring": "Create a pet." }, "get_pet": { "name": "get_pet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.get_pet", - "signature": "", + "signature": "get_pet(id: int)", "docstring": "Retrieve a single pet by ID." }, "update_pet": { "name": "update_pet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.update_pet", - "signature": "", + "signature": "update_pet(id: int, payload: PetCreate)", "docstring": "Update an existing pet." }, "delete_pet": { "name": "delete_pet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.delete_pet", - "signature": "", + "signature": "delete_pet(id: int, response: Response)", "docstring": "Delete an existing pet." }, "upload_pet_photo": { "name": "upload_pet_photo", "kind": "function", "path": "openapi_first.templates.vet_app.routes.upload_pet_photo", - "signature": "", + "signature": "upload_pet_photo(id: int, file: UploadFile) -> dict", "docstring": "Upload a pet photo.\n\nParameters\n----------\nid : int\n Identifier of the pet.\nfile : UploadFile\n Image file to upload.\n\nReturns\n-------\ndict\n A confirmation with the pet ID." }, "list_appointments": { "name": "list_appointments", "kind": "function", "path": "openapi_first.templates.vet_app.routes.list_appointments", - "signature": "", + "signature": "list_appointments(limit: int = 20, offset: int = 0, date: str = None, vet: int = None, pet: int = None)", "docstring": "List appointments (paginated, filterable)." }, "create_appointment": { "name": "create_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.create_appointment", - "signature": "", + "signature": "create_appointment(payload: AppointmentCreate, response: Response)", "docstring": "Create an appointment." }, "get_appointment": { "name": "get_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.get_appointment", - "signature": "", + "signature": "get_appointment(id: int)", "docstring": "Retrieve a single appointment by ID." }, "update_appointment": { "name": "update_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.update_appointment", - "signature": "", + "signature": "update_appointment(id: int, payload: AppointmentCreate)", "docstring": "Update an existing appointment." }, "delete_appointment": { "name": "delete_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.delete_appointment", - "signature": "", + "signature": "delete_appointment(id: int, response: Response)", "docstring": "Delete an existing appointment." }, "stream_actions": { "name": "stream_actions", "kind": "function", "path": "openapi_first.templates.vet_app.routes.stream_actions", - "signature": "", + "signature": "stream_actions(id: int)", "docstring": "Stream animal actions via SSE, scoped to a pet's species." } } @@ -2619,39 +1954,18 @@ "signature": null, "docstring": null, "members": { - "asyncio": { - "name": "asyncio", - "kind": "alias", - "path": "openapi_first.templates.vet_app.sse.asyncio", - "signature": "", - "docstring": null - }, - "random": { - "name": "random", - "kind": "alias", - "path": "openapi_first.templates.vet_app.sse.random", - "signature": "", - "docstring": null - }, - "json": { - "name": "json", - "kind": "alias", - "path": "openapi_first.templates.vet_app.sse.json", - "signature": "", - "docstring": null - }, "subscribe": { "name": "subscribe", "kind": "function", "path": "openapi_first.templates.vet_app.sse.subscribe", - "signature": "", + "signature": "subscribe(pet_id: int, species: str) -> asyncio.Queue", "docstring": null }, "unsubscribe": { "name": "unsubscribe", "kind": "function", "path": "openapi_first.templates.vet_app.sse.unsubscribe", - "signature": "", + "signature": "unsubscribe(pet_id: int, q: asyncio.Queue)", "docstring": null } } @@ -2663,60 +1977,46 @@ "signature": null, "docstring": "End-to-end tests for the OpenAPI-first Veterinary Clinic example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store using Pydantic models.", "members": { - "TestClient": { - "name": "TestClient", - "kind": "alias", - "path": "openapi_first.templates.vet_app.test_vet_app.TestClient", - "signature": "", - "docstring": null - }, - "app": { - "name": "app", - "kind": "alias", - "path": "openapi_first.templates.vet_app.test_vet_app.app", - "signature": "", - "docstring": null - }, "load_openapi": { "name": "load_openapi", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.load_openapi", - "signature": "", + "signature": "load_openapi(path: str | Path)", "docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup." }, "OpenAPIClient": { "name": "OpenAPIClient", "kind": "class", "path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient", - "signature": "", - "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", + "signature": "OpenAPIClient(spec: dict[str, Any], base_url: str | None = None, client: httpx.Client | None = None)", + "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nArgs:\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification.\n base_url (str | None):\n Base URL of the target service. If omitted, the first entry in the\n OpenAPI `servers` list is used.\n client (httpx.Client | None):\n Optional preconfigured httpx client instance.\n\nRaises:\n OpenAPIClientError:\n If no servers are defined, spec has no paths, operationIds are\n missing/duplicate, or required parameters are missing.\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", "members": { "spec": { "name": "spec", "kind": "attribute", "path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.spec", - "signature": "", + "signature": null, "docstring": null }, "base_url": { "name": "base_url", "kind": "attribute", "path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.base_url", - "signature": "", + "signature": null, "docstring": null }, "client": { "name": "client", "kind": "attribute", "path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.client", - "signature": "", + "signature": null, "docstring": null }, "operations": { "name": "operations", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.operations", - "signature": "", + "signature": "operations()", "docstring": null } } @@ -2746,91 +2046,91 @@ "name": "test_list_parents", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_list_parents", - "signature": "", + "signature": "test_list_parents()", "docstring": "List parents returns paginated response." }, "test_create_parent": { "name": "test_create_parent", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_create_parent", - "signature": "", + "signature": "test_create_parent()", "docstring": "Creating a parent returns 201 with the created entity." }, "test_get_parent": { "name": "test_get_parent", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_get_parent", - "signature": "", + "signature": "test_get_parent()", "docstring": "Get parent by ID returns the entity." }, "test_update_parent": { "name": "test_update_parent", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_update_parent", - "signature": "", + "signature": "test_update_parent()", "docstring": "Update parent replaces its values." }, "test_delete_parent": { "name": "test_delete_parent", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_delete_parent", - "signature": "", + "signature": "test_delete_parent()", "docstring": "Delete parent returns 204 and removes the entity." }, "test_list_vets": { "name": "test_list_vets", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_list_vets", - "signature": "", + "signature": "test_list_vets()", "docstring": "List vets returns paginated response." }, "test_create_vet": { "name": "test_create_vet", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_create_vet", - "signature": "", + "signature": "test_create_vet()", "docstring": "Creating a vet returns 201." }, "test_list_treatments": { "name": "test_list_treatments", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_list_treatments", - "signature": "", + "signature": "test_list_treatments()", "docstring": "List treatments returns an array." }, "test_create_treatment": { "name": "test_create_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_create_treatment", - "signature": "", + "signature": "test_create_treatment()", "docstring": "Creating a treatment returns 201." }, "test_create_pet": { "name": "test_create_pet", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_create_pet", - "signature": "", + "signature": "test_create_pet()", "docstring": "Creating a pet links FK references." }, "test_upload_pet_photo": { "name": "test_upload_pet_photo", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_upload_pet_photo", - "signature": "", + "signature": "test_upload_pet_photo()", "docstring": "Upload pet photo returns 200." }, "test_list_appointments": { "name": "test_list_appointments", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_list_appointments", - "signature": "", + "signature": "test_list_appointments()", "docstring": "List appointments returns paginated response with filter params." }, "test_full_appointment_lifecycle": { "name": "test_full_appointment_lifecycle", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_full_appointment_lifecycle", - "signature": "", + "signature": "test_full_appointment_lifecycle()", "docstring": "Create a parent, vet, treatment, pet, then an appointment." } } diff --git a/openapi-first/mcp/modules/openapi_first.loader.json b/openapi-first/mcp/modules/openapi_first.loader.json index 0193335..4ddbe9c 100644 --- a/openapi-first/mcp/modules/openapi_first.loader.json +++ b/openapi-first/mcp/modules/openapi_first.loader.json @@ -4,60 +4,25 @@ "path": "openapi_first.loader", "docstring": "# Summary\n\nOpenAPI specification loading and validation utilities.\n\nThis module is responsible for loading an OpenAPI 3.x specification\nfrom disk and validating it before it is used by the application.\n\nIt enforces the principle that an invalid or malformed OpenAPI document\nmust never reach the routing or runtime layers.\n\nNotes:\n **Design Principles:**\n\n - OpenAPI is treated as an authoritative contract.\n - Invalid specifications fail fast at application startup.\n - Supported formats are JSON and YAML.\n - Validation errors are surfaced clearly and early.\n\n **Constraints:**\n\n - This module intentionally does NOT:\n - Modify the OpenAPI document.\n - Infer missing fields.\n - Generate models or code.\n - Perform request/response validation at runtime.", "objects": { - "json": { - "name": "json", - "kind": "alias", - "path": "openapi_first.loader.json", - "signature": "", - "docstring": null - }, - "Path": { - "name": "Path", - "kind": "alias", - "path": "openapi_first.loader.Path", - "signature": "", - "docstring": null - }, - "Any": { - "name": "Any", - "kind": "alias", - "path": "openapi_first.loader.Any", - "signature": "", - "docstring": null - }, - "yaml": { - "name": "yaml", - "kind": "alias", - "path": "openapi_first.loader.yaml", - "signature": "", - "docstring": null - }, - "validate_spec": { - "name": "validate_spec", - "kind": "alias", - "path": "openapi_first.loader.validate_spec", - "signature": "", - "docstring": null - }, "OpenAPIFirstError": { "name": "OpenAPIFirstError", "kind": "class", "path": "openapi_first.loader.OpenAPIFirstError", - "signature": "", + "signature": null, "docstring": "Base exception for all OpenAPI-first enforcement errors.\n\nNotes:\n **Responsibilities:**\n\n - This exception exists to allow callers, test suites, and CI\n pipelines to catch and distinguish OpenAPI contract violations\n from unrelated runtime errors.\n - All exceptions raised by the OpenAPI-first core should inherit\n from this type." }, "OpenAPISpecLoadError": { "name": "OpenAPISpecLoadError", "kind": "class", "path": "openapi_first.loader.OpenAPISpecLoadError", - "signature": "", + "signature": null, "docstring": "Raised when an OpenAPI specification cannot be loaded or validated.\n\nNotes:\n **Guarantees:**\n\n - This error indicates that the OpenAPI document is unreadable,\n malformed, or violates the OpenAPI 3.x specification." }, "load_openapi": { "name": "load_openapi", "kind": "function", "path": "openapi_first.loader.load_openapi", - "signature": "", + "signature": "load_openapi(path: str | Path) -> dict[str, Any]", "docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup." } } diff --git a/openapi-first/mcp/modules/openapi_first.security.json b/openapi-first/mcp/modules/openapi_first.security.json index f8f812d..63c9138 100644 --- a/openapi-first/mcp/modules/openapi_first.security.json +++ b/openapi-first/mcp/modules/openapi_first.security.json @@ -4,88 +4,18 @@ "path": "openapi_first.security", "docstring": "# Summary\n\nOpenAPI security scheme parsing and auto-generated auth dependencies.\n\nThis module reads `securitySchemes` and per-operation `security` from an\nOpenAPI spec, resolves `{ENV_VAR}` placeholders in `x-` extension fields,\nand generates FastAPI dependencies for token validation (e.g., Bearer JWT\nintrospection).", "objects": { - "os": { - "name": "os", - "kind": "alias", - "path": "openapi_first.security.os", - "signature": "", - "docstring": null - }, - "re": { - "name": "re", - "kind": "alias", - "path": "openapi_first.security.re", - "signature": "", - "docstring": null - }, - "Callable": { - "name": "Callable", - "kind": "alias", - "path": "openapi_first.security.Callable", - "signature": "", - "docstring": null - }, - "Any": { - "name": "Any", - "kind": "alias", - "path": "openapi_first.security.Any", - "signature": "", - "docstring": null - }, - "httpx": { - "name": "httpx", - "kind": "alias", - "path": "openapi_first.security.httpx", - "signature": "", - "docstring": null - }, - "Depends": { - "name": "Depends", - "kind": "alias", - "path": "openapi_first.security.Depends", - "signature": "", - "docstring": null - }, - "HTTPException": { - "name": "HTTPException", - "kind": "alias", - "path": "openapi_first.security.HTTPException", - "signature": "", - "docstring": null - }, - "Request": { - "name": "Request", - "kind": "alias", - "path": "openapi_first.security.Request", - "signature": "", - "docstring": null - }, - "HTTPAuthorizationCredentials": { - "name": "HTTPAuthorizationCredentials", - "kind": "alias", - "path": "openapi_first.security.HTTPAuthorizationCredentials", - "signature": "", - "docstring": null - }, - "HTTPBearer": { - "name": "HTTPBearer", - "kind": "alias", - "path": "openapi_first.security.HTTPBearer", - "signature": "", - "docstring": null - }, "parse_security_schemes": { "name": "parse_security_schemes", "kind": "function", "path": "openapi_first.security.parse_security_schemes", - "signature": "", + "signature": "parse_security_schemes(spec: dict[str, Any]) -> dict[str, dict[str, Any]]", "docstring": "Extract and resolve environment variables in security schemes." }, "make_security_dependencies": { "name": "make_security_dependencies", "kind": "function", "path": "openapi_first.security.make_security_dependencies", - "signature": "", + "signature": "make_security_dependencies(spec: dict[str, Any], security_schemes: dict[str, dict[str, Any]]) -> dict[str, list[Any]]", "docstring": "Build a mapping of ``METHOD:/path`` → list of ``Depends(...)``.\n\nThe effective security for each operation is resolved by:\n\n1. Using the operation-level ``security`` field if present.\n2. Falling back to the top-level ``security`` field.\n3. An empty list means *no auth required* for that operation." } } diff --git a/openapi-first/mcp/modules/openapi_first.templates.crud_app.data.json b/openapi-first/mcp/modules/openapi_first.templates.crud_app.data.json index 808ae4c..d75693e 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.crud_app.data.json +++ b/openapi-first/mcp/modules/openapi_first.templates.crud_app.data.json @@ -4,46 +4,39 @@ "path": "openapi_first.templates.crud_app.data", "docstring": "In-memory mock data store for CRUD example.\n\nThis module intentionally avoids persistence and concurrency guarantees.\nIt is suitable for demos, tests, and scaffolding only.\n\nIt intentionally avoids\n- persistence\n- concurrency guarantees\n- validation\n- error handling\n\nThe implementation is suitable for:\n- demonstrations\n- tests\n- scaffolding and example services\n\nIt is explicitly NOT suitable for production use.\n\nThis module is not part of the ``openapi_first`` library API surface.", "objects": { - "Dict": { - "name": "Dict", - "kind": "alias", - "path": "openapi_first.templates.crud_app.data.Dict", - "signature": "", - "docstring": null - }, "list_items": { "name": "list_items", "kind": "function", "path": "openapi_first.templates.crud_app.data.list_items", - "signature": "", + "signature": "list_items() -> list[dict]", "docstring": "Return all items in the data store.\n\nThis function performs no filtering, pagination, or sorting.\nThe returned collection reflects the current in-memory state.\n\nReturns\n-------\nlist[dict]\n A list of item representations." }, "get_item": { "name": "get_item", "kind": "function", "path": "openapi_first.templates.crud_app.data.get_item", - "signature": "", + "signature": "get_item(item_id: int) -> dict", "docstring": "Retrieve a single item by ID.\n\nThis function assumes the item exists and will raise ``KeyError``\nif the ID is not present in the store.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\ndict\n The stored item representation." }, "create_item": { "name": "create_item", "kind": "function", "path": "openapi_first.templates.crud_app.data.create_item", - "signature": "", + "signature": "create_item(payload: dict) -> dict", "docstring": "Create a new item in the data store.\n\nA new integer ID is assigned automatically. No validation is\nperformed on the provided payload.\n\nParameters\n----------\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The newly created item, including its assigned ID." }, "update_item": { "name": "update_item", "kind": "function", "path": "openapi_first.templates.crud_app.data.update_item", - "signature": "", + "signature": "update_item(item_id: int, payload: dict) -> dict", "docstring": "Replace an existing item in the data store.\n\nThis function overwrites the existing item entirely and does not\nperform partial updates or validation. If the item does not exist,\nit will be created implicitly.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The updated item representation." }, "delete_item": { "name": "delete_item", "kind": "function", "path": "openapi_first.templates.crud_app.data.delete_item", - "signature": "", + "signature": "delete_item(item_id: int)", "docstring": "Remove an item from the data store.\n\nThis function assumes the item exists and will raise ``KeyError``\nif the ID is not present.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete." } } diff --git a/openapi-first/mcp/modules/openapi_first.templates.crud_app.json b/openapi-first/mcp/modules/openapi_first.templates.crud_app.json index 9baef63..bf2d8ed 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.crud_app.json +++ b/openapi-first/mcp/modules/openapi_first.templates.crud_app.json @@ -2,7 +2,7 @@ "module": "openapi_first.templates.crud_app", "content": { "path": "openapi_first.templates.crud_app", - "docstring": "OpenAPI-first CRUD application template.\n\nThis package contains a complete, minimal example of an OpenAPI-first\nCRUD service built using the ``openapi_first`` library.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace implementing CRUD operations (``routes``)\n- an in-memory mock data store (``data``)\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification and enforced at application startup.\nNo decorator-driven routing or implicit framework behavior is used.\n\nThis template demonstrates:\n- operationId-driven server-side route binding\n- explicit HTTP status code control in handlers\n- operationId-driven client usage against the same OpenAPI contract\n- end-to-end validation using in-memory data and tests\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new CRUD example service using the bundled template:\n\n openapi-first crud_app\n\nCreate the service in a custom directory:\n\n openapi-first crud_app my-crud-service\n\nList all available application templates:\n\n openapi-first --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client calls for CRUD operations:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List items\n response = client.list_items()\n\n # Get item by ID\n response = client.get_item(\n path_params={\"item_id\": 1}\n )\n\n # Create item\n response = client.create_item(\n body={\"name\": \"Orange\", \"price\": 0.8}\n )\n\n # Update item\n response = client.update_item(\n path_params={\"item_id\": 1},\n body={\"name\": \"Green Apple\", \"price\": 0.6},\n )\n\n # Delete item\n response = client.delete_item(\n path_params={\"item_id\": 1}\n )\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Path and request parameters must match the OpenAPI specification\n- Invalid or incomplete OpenAPI specs fail at client construction time\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.", + "docstring": "OpenAPI-first CRUD application template.\n\nThis package contains a complete, minimal example of an OpenAPI-first\nCRUD service built using the ``openapi_first`` library.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace implementing CRUD operations (``routes``)\n- an in-memory mock data store (``data``)\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification and enforced at application startup.\nNo decorator-driven routing or implicit framework behavior is used.\n\nThis template demonstrates:\n- operationId-driven server-side route binding\n- explicit HTTP status code control in handlers\n- operationId-driven client usage against the same OpenAPI contract\n- end-to-end validation using in-memory data and tests\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new CRUD example service using the bundled template:\n\n openapi-first scaffold crud_app\n\nCreate the service in a custom directory:\n\n openapi-first scaffold crud_app my-crud-service\n\nList all available application templates:\n\n openapi-first scaffold --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client calls for CRUD operations:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List items\n response = client.list_items()\n\n # Get item by ID\n response = client.get_item(\n path_params={\"item_id\": 1}\n )\n\n # Create item\n response = client.create_item(\n body={\"name\": \"Orange\", \"price\": 0.8}\n )\n\n # Update item\n response = client.update_item(\n path_params={\"item_id\": 1},\n body={\"name\": \"Green Apple\", \"price\": 0.6},\n )\n\n # Delete item\n response = client.delete_item(\n path_params={\"item_id\": 1}\n )\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Path and request parameters must match the OpenAPI specification\n- Invalid or incomplete OpenAPI specs fail at client construction time\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.", "objects": { "data": { "name": "data", @@ -11,46 +11,39 @@ "signature": null, "docstring": "In-memory mock data store for CRUD example.\n\nThis module intentionally avoids persistence and concurrency guarantees.\nIt is suitable for demos, tests, and scaffolding only.\n\nIt intentionally avoids\n- persistence\n- concurrency guarantees\n- validation\n- error handling\n\nThe implementation is suitable for:\n- demonstrations\n- tests\n- scaffolding and example services\n\nIt is explicitly NOT suitable for production use.\n\nThis module is not part of the ``openapi_first`` library API surface.", "members": { - "Dict": { - "name": "Dict", - "kind": "alias", - "path": "openapi_first.templates.crud_app.data.Dict", - "signature": "", - "docstring": null - }, "list_items": { "name": "list_items", "kind": "function", "path": "openapi_first.templates.crud_app.data.list_items", - "signature": "", + "signature": "list_items() -> list[dict]", "docstring": "Return all items in the data store.\n\nThis function performs no filtering, pagination, or sorting.\nThe returned collection reflects the current in-memory state.\n\nReturns\n-------\nlist[dict]\n A list of item representations." }, "get_item": { "name": "get_item", "kind": "function", "path": "openapi_first.templates.crud_app.data.get_item", - "signature": "", + "signature": "get_item(item_id: int) -> dict", "docstring": "Retrieve a single item by ID.\n\nThis function assumes the item exists and will raise ``KeyError``\nif the ID is not present in the store.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\ndict\n The stored item representation." }, "create_item": { "name": "create_item", "kind": "function", "path": "openapi_first.templates.crud_app.data.create_item", - "signature": "", + "signature": "create_item(payload: dict) -> dict", "docstring": "Create a new item in the data store.\n\nA new integer ID is assigned automatically. No validation is\nperformed on the provided payload.\n\nParameters\n----------\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The newly created item, including its assigned ID." }, "update_item": { "name": "update_item", "kind": "function", "path": "openapi_first.templates.crud_app.data.update_item", - "signature": "", + "signature": "update_item(item_id: int, payload: dict) -> dict", "docstring": "Replace an existing item in the data store.\n\nThis function overwrites the existing item entirely and does not\nperform partial updates or validation. If the item does not exist,\nit will be created implicitly.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The updated item representation." }, "delete_item": { "name": "delete_item", "kind": "function", "path": "openapi_first.templates.crud_app.data.delete_item", - "signature": "", + "signature": "delete_item(item_id: int)", "docstring": "Remove an item from the data store.\n\nThis function assumes the item exists and will raise ``KeyError``\nif the ID is not present.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete." } } @@ -66,25 +59,18 @@ "name": "OpenAPIFirstApp", "kind": "class", "path": "openapi_first.templates.crud_app.main.OpenAPIFirstApp", - "signature": "", - "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", + "signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)", + "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", "members": { "openapi": { "name": "openapi", "kind": "attribute", "path": "openapi_first.templates.crud_app.main.OpenAPIFirstApp.openapi", - "signature": "", + "signature": null, "docstring": null } } }, - "routes": { - "name": "routes", - "kind": "alias", - "path": "openapi_first.templates.crud_app.main.routes", - "signature": "", - "docstring": null - }, "app": { "name": "app", "kind": "attribute", @@ -101,54 +87,40 @@ "signature": null, "docstring": "CRUD route handlers bound via OpenAPI operationId.\n\nThese handlers explicitly control HTTP status codes to ensure\nruntime behavior matches the OpenAPI contract.\n\nThis module defines OpenAPI-bound operation handlers for a simple CRUD\nservice. Functions in this module are bound to HTTP routes exclusively\nvia OpenAPI ``operationId`` values.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Error conditions are translated\ninto explicit HTTP responses rather than relying on implicit framework\nbehavior.\n\nNo routing decorators or path definitions appear in this module. All\nrouting, HTTP methods, and schemas are defined in the OpenAPI\nspecification.", "members": { - "Response": { - "name": "Response", - "kind": "alias", - "path": "openapi_first.templates.crud_app.routes.Response", - "signature": "", - "docstring": null - }, - "HTTPException": { - "name": "HTTPException", - "kind": "alias", - "path": "openapi_first.templates.crud_app.routes.HTTPException", - "signature": "", - "docstring": null - }, "list_items": { "name": "list_items", "kind": "function", "path": "openapi_first.templates.crud_app.routes.list_items", - "signature": "", + "signature": "list_items() -> list[dict]", "docstring": "List all items.\n\nImplements the OpenAPI operation identified by\n``operationId: list_items``.\n\nReturns\n-------\nlist[dict]\n A list of item representations." }, "get_item": { "name": "get_item", "kind": "function", "path": "openapi_first.templates.crud_app.routes.get_item", - "signature": "", + "signature": "get_item(item_id: int) -> dict", "docstring": "Retrieve a single item by ID.\n\nImplements the OpenAPI operation identified by\n``operationId: get_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\ndict\n The requested item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." }, "create_item": { "name": "create_item", "kind": "function", "path": "openapi_first.templates.crud_app.routes.create_item", - "signature": "", - "docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : dict\n Item attributes excluding the ``id`` field.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\ndict\n The newly created item." + "signature": "create_item(payload: dict, response: Response) -> dict", + "docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : dict\n Item attributes excluding the ``id`` field.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\ndict\n The newly created item." }, "update_item": { "name": "update_item", "kind": "function", "path": "openapi_first.templates.crud_app.routes.update_item", - "signature": "", + "signature": "update_item(item_id: int, payload: dict) -> dict", "docstring": "Update an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: update_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The updated item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." }, "delete_item": { "name": "delete_item", "kind": "function", "path": "openapi_first.templates.crud_app.routes.delete_item", - "signature": "", - "docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." + "signature": "delete_item(item_id: int, response: Response) -> None", + "docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." } } }, @@ -159,60 +131,46 @@ "signature": null, "docstring": "End-to-end tests for the OpenAPI-first CRUD example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store.\n- OpenAPI specification loading\n- OperationId-driven route binding on the server\n- OperationId-driven client invocation\n- Correct HTTP status codes and response payloads\n\nThe tests exercise all CRUD operations against an in-memory mock data\nstore and assume deterministic behavior within a single process.\n\nThe tests assume:\n- OpenAPI-first route binding\n- In-memory storage (no persistence guarantees)\n- Deterministic behavior in a single process\n- One-to-one correspondence between OpenAPI operationId values and\n server/client callables", "members": { - "TestClient": { - "name": "TestClient", - "kind": "alias", - "path": "openapi_first.templates.crud_app.test_crud_app.TestClient", - "signature": "", - "docstring": null - }, - "app": { - "name": "app", - "kind": "alias", - "path": "openapi_first.templates.crud_app.test_crud_app.app", - "signature": "", - "docstring": null - }, "load_openapi": { "name": "load_openapi", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.load_openapi", - "signature": "", + "signature": "load_openapi(path: str | Path)", "docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup." }, "OpenAPIClient": { "name": "OpenAPIClient", "kind": "class", "path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient", - "signature": "", - "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", + "signature": "OpenAPIClient(spec: dict[str, Any], base_url: str | None = None, client: httpx.Client | None = None)", + "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nArgs:\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification.\n base_url (str | None):\n Base URL of the target service. If omitted, the first entry in the\n OpenAPI `servers` list is used.\n client (httpx.Client | None):\n Optional preconfigured httpx client instance.\n\nRaises:\n OpenAPIClientError:\n If no servers are defined, spec has no paths, operationIds are\n missing/duplicate, or required parameters are missing.\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", "members": { "spec": { "name": "spec", "kind": "attribute", "path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.spec", - "signature": "", + "signature": null, "docstring": null }, "base_url": { "name": "base_url", "kind": "attribute", "path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.base_url", - "signature": "", + "signature": null, "docstring": null }, "client": { "name": "client", "kind": "attribute", "path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.client", - "signature": "", + "signature": null, "docstring": null }, "operations": { "name": "operations", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.operations", - "signature": "", + "signature": "operations()", "docstring": null } } @@ -235,35 +193,35 @@ "name": "test_list_items_initial", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.test_list_items_initial", - "signature": "", + "signature": "test_list_items_initial()", "docstring": "Initial items should be present." }, "test_get_item": { "name": "test_get_item", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.test_get_item", - "signature": "", + "signature": "test_get_item()", "docstring": "Existing item should be retrievable by ID." }, "test_create_item": { "name": "test_create_item", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.test_create_item", - "signature": "", + "signature": "test_create_item()", "docstring": "Creating a new item should return the created entity." }, "test_update_item": { "name": "test_update_item", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.test_update_item", - "signature": "", + "signature": "test_update_item()", "docstring": "Updating an item should replace its values." }, "test_delete_item": { "name": "test_delete_item", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.test_delete_item", - "signature": "", + "signature": "test_delete_item()", "docstring": "Deleting an item should remove it from the store." } } diff --git a/openapi-first/mcp/modules/openapi_first.templates.crud_app.main.json b/openapi-first/mcp/modules/openapi_first.templates.crud_app.main.json index 94afe60..79d1efa 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.crud_app.main.json +++ b/openapi-first/mcp/modules/openapi_first.templates.crud_app.main.json @@ -8,25 +8,18 @@ "name": "OpenAPIFirstApp", "kind": "class", "path": "openapi_first.templates.crud_app.main.OpenAPIFirstApp", - "signature": "", - "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", + "signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)", + "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", "members": { "openapi": { "name": "openapi", "kind": "attribute", "path": "openapi_first.templates.crud_app.main.OpenAPIFirstApp.openapi", - "signature": "", + "signature": null, "docstring": null } } }, - "routes": { - "name": "routes", - "kind": "alias", - "path": "openapi_first.templates.crud_app.main.routes", - "signature": "", - "docstring": null - }, "app": { "name": "app", "kind": "attribute", diff --git a/openapi-first/mcp/modules/openapi_first.templates.crud_app.routes.json b/openapi-first/mcp/modules/openapi_first.templates.crud_app.routes.json index a1bc37b..dd09419 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.crud_app.routes.json +++ b/openapi-first/mcp/modules/openapi_first.templates.crud_app.routes.json @@ -4,54 +4,40 @@ "path": "openapi_first.templates.crud_app.routes", "docstring": "CRUD route handlers bound via OpenAPI operationId.\n\nThese handlers explicitly control HTTP status codes to ensure\nruntime behavior matches the OpenAPI contract.\n\nThis module defines OpenAPI-bound operation handlers for a simple CRUD\nservice. Functions in this module are bound to HTTP routes exclusively\nvia OpenAPI ``operationId`` values.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Error conditions are translated\ninto explicit HTTP responses rather than relying on implicit framework\nbehavior.\n\nNo routing decorators or path definitions appear in this module. All\nrouting, HTTP methods, and schemas are defined in the OpenAPI\nspecification.", "objects": { - "Response": { - "name": "Response", - "kind": "alias", - "path": "openapi_first.templates.crud_app.routes.Response", - "signature": "", - "docstring": null - }, - "HTTPException": { - "name": "HTTPException", - "kind": "alias", - "path": "openapi_first.templates.crud_app.routes.HTTPException", - "signature": "", - "docstring": null - }, "list_items": { "name": "list_items", "kind": "function", "path": "openapi_first.templates.crud_app.routes.list_items", - "signature": "", + "signature": "list_items() -> list[dict]", "docstring": "List all items.\n\nImplements the OpenAPI operation identified by\n``operationId: list_items``.\n\nReturns\n-------\nlist[dict]\n A list of item representations." }, "get_item": { "name": "get_item", "kind": "function", "path": "openapi_first.templates.crud_app.routes.get_item", - "signature": "", + "signature": "get_item(item_id: int) -> dict", "docstring": "Retrieve a single item by ID.\n\nImplements the OpenAPI operation identified by\n``operationId: get_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\ndict\n The requested item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." }, "create_item": { "name": "create_item", "kind": "function", "path": "openapi_first.templates.crud_app.routes.create_item", - "signature": "", - "docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : dict\n Item attributes excluding the ``id`` field.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\ndict\n The newly created item." + "signature": "create_item(payload: dict, response: Response) -> dict", + "docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : dict\n Item attributes excluding the ``id`` field.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\ndict\n The newly created item." }, "update_item": { "name": "update_item", "kind": "function", "path": "openapi_first.templates.crud_app.routes.update_item", - "signature": "", + "signature": "update_item(item_id: int, payload: dict) -> dict", "docstring": "Update an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: update_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The updated item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." }, "delete_item": { "name": "delete_item", "kind": "function", "path": "openapi_first.templates.crud_app.routes.delete_item", - "signature": "", - "docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." + "signature": "delete_item(item_id: int, response: Response) -> None", + "docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." } } } diff --git a/openapi-first/mcp/modules/openapi_first.templates.crud_app.test_crud_app.json b/openapi-first/mcp/modules/openapi_first.templates.crud_app.test_crud_app.json index 19df95f..56f99d8 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.crud_app.test_crud_app.json +++ b/openapi-first/mcp/modules/openapi_first.templates.crud_app.test_crud_app.json @@ -4,60 +4,46 @@ "path": "openapi_first.templates.crud_app.test_crud_app", "docstring": "End-to-end tests for the OpenAPI-first CRUD example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store.\n- OpenAPI specification loading\n- OperationId-driven route binding on the server\n- OperationId-driven client invocation\n- Correct HTTP status codes and response payloads\n\nThe tests exercise all CRUD operations against an in-memory mock data\nstore and assume deterministic behavior within a single process.\n\nThe tests assume:\n- OpenAPI-first route binding\n- In-memory storage (no persistence guarantees)\n- Deterministic behavior in a single process\n- One-to-one correspondence between OpenAPI operationId values and\n server/client callables", "objects": { - "TestClient": { - "name": "TestClient", - "kind": "alias", - "path": "openapi_first.templates.crud_app.test_crud_app.TestClient", - "signature": "", - "docstring": null - }, - "app": { - "name": "app", - "kind": "alias", - "path": "openapi_first.templates.crud_app.test_crud_app.app", - "signature": "", - "docstring": null - }, "load_openapi": { "name": "load_openapi", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.load_openapi", - "signature": "", + "signature": "load_openapi(path: str | Path)", "docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup." }, "OpenAPIClient": { "name": "OpenAPIClient", "kind": "class", "path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient", - "signature": "", - "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", + "signature": "OpenAPIClient(spec: dict[str, Any], base_url: str | None = None, client: httpx.Client | None = None)", + "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nArgs:\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification.\n base_url (str | None):\n Base URL of the target service. If omitted, the first entry in the\n OpenAPI `servers` list is used.\n client (httpx.Client | None):\n Optional preconfigured httpx client instance.\n\nRaises:\n OpenAPIClientError:\n If no servers are defined, spec has no paths, operationIds are\n missing/duplicate, or required parameters are missing.\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", "members": { "spec": { "name": "spec", "kind": "attribute", "path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.spec", - "signature": "", + "signature": null, "docstring": null }, "base_url": { "name": "base_url", "kind": "attribute", "path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.base_url", - "signature": "", + "signature": null, "docstring": null }, "client": { "name": "client", "kind": "attribute", "path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.client", - "signature": "", + "signature": null, "docstring": null }, "operations": { "name": "operations", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.operations", - "signature": "", + "signature": "operations()", "docstring": null } } @@ -80,35 +66,35 @@ "name": "test_list_items_initial", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.test_list_items_initial", - "signature": "", + "signature": "test_list_items_initial()", "docstring": "Initial items should be present." }, "test_get_item": { "name": "test_get_item", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.test_get_item", - "signature": "", + "signature": "test_get_item()", "docstring": "Existing item should be retrievable by ID." }, "test_create_item": { "name": "test_create_item", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.test_create_item", - "signature": "", + "signature": "test_create_item()", "docstring": "Creating a new item should return the created entity." }, "test_update_item": { "name": "test_update_item", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.test_update_item", - "signature": "", + "signature": "test_update_item()", "docstring": "Updating an item should replace its values." }, "test_delete_item": { "name": "test_delete_item", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.test_delete_item", - "signature": "", + "signature": "test_delete_item()", "docstring": "Deleting an item should remove it from the store." } } diff --git a/openapi-first/mcp/modules/openapi_first.templates.health_app.json b/openapi-first/mcp/modules/openapi_first.templates.health_app.json index 3a7dcfe..5bff561 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.health_app.json +++ b/openapi-first/mcp/modules/openapi_first.templates.health_app.json @@ -2,7 +2,7 @@ "module": "openapi_first.templates.health_app", "content": { "path": "openapi_first.templates.health_app", - "docstring": "OpenAPI-first FastAPI application template.\n\nThis package contains a minimal, fully working example of an\nOpenAPI-first FastAPI service built using the ``openapi_first`` library.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace (``routes``)\n\nNo routing decorators, implicit behavior, or framework-specific\nconvenience abstractions are used. All HTTP routes, methods, and\noperation bindings are defined in OpenAPI and enforced at application\nstartup.\n\nThis package is intended to be copied as a starting point for new\nservices via the ``openapi-first`` CLI. It is not part of the\n``openapi_first`` library API surface.\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new OpenAPI-first health check service using the bundled\ntemplate:\n\n openapi-first health_app\n\nCreate the service in a custom directory:\n\n openapi-first health_app my-health-service\n\nList all available application templates:\n\n openapi-first --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client call for the ``get_health`` operation:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n response = client.get_health()\n\n assert response.status_code == 200\n assert response.json() == {\"status\": \"ok\"}\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Path and request parameters must match the OpenAPI specification\n- Invalid or incomplete OpenAPI specs fail at client construction time", + "docstring": "OpenAPI-first FastAPI application template.\n\nThis package contains a minimal, fully working example of an\nOpenAPI-first FastAPI service built using the ``openapi_first`` library.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace (``routes``)\n\nNo routing decorators, implicit behavior, or framework-specific\nconvenience abstractions are used. All HTTP routes, methods, and\noperation bindings are defined in OpenAPI and enforced at application\nstartup.\n\nThis package is intended to be copied as a starting point for new\nservices via the ``openapi-first`` CLI. It is not part of the\n``openapi_first`` library API surface.\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new OpenAPI-first health check service using the bundled\ntemplate:\n\n openapi-first scaffold health_app\n\nCreate the service in a custom directory:\n\n openapi-first scaffold health_app my-health-service\n\nList all available application templates:\n\n openapi-first scaffold --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client call for the ``get_health`` operation:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n response = client.get_health()\n\n assert response.status_code == 200\n assert response.json() == {\"status\": \"ok\"}\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Path and request parameters must match the OpenAPI specification\n- Invalid or incomplete OpenAPI specs fail at client construction time", "objects": { "main": { "name": "main", @@ -15,25 +15,18 @@ "name": "OpenAPIFirstApp", "kind": "class", "path": "openapi_first.templates.health_app.main.OpenAPIFirstApp", - "signature": "", - "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", + "signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)", + "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", "members": { "openapi": { "name": "openapi", "kind": "attribute", "path": "openapi_first.templates.health_app.main.OpenAPIFirstApp.openapi", - "signature": "", + "signature": null, "docstring": null } } }, - "routes": { - "name": "routes", - "kind": "alias", - "path": "openapi_first.templates.health_app.main.routes", - "signature": "", - "docstring": null - }, "app": { "name": "app", "kind": "attribute", @@ -54,7 +47,7 @@ "name": "get_health", "kind": "function", "path": "openapi_first.templates.health_app.routes.get_health", - "signature": "", + "signature": "get_health() -> dict", "docstring": "Health check operation handler.\n\nThis function implements the OpenAPI operation identified by\n``operationId: get_health``.\n\nIt contains no routing metadata or framework-specific logic.\nRequest binding, HTTP method, and response semantics are defined\nexclusively by the OpenAPI specification.\n\nReturns\n-------\ndict\n A minimal liveness payload indicating service health." } } diff --git a/openapi-first/mcp/modules/openapi_first.templates.health_app.main.json b/openapi-first/mcp/modules/openapi_first.templates.health_app.main.json index 6f2c9d5..5c681ae 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.health_app.main.json +++ b/openapi-first/mcp/modules/openapi_first.templates.health_app.main.json @@ -8,25 +8,18 @@ "name": "OpenAPIFirstApp", "kind": "class", "path": "openapi_first.templates.health_app.main.OpenAPIFirstApp", - "signature": "", - "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", + "signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)", + "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", "members": { "openapi": { "name": "openapi", "kind": "attribute", "path": "openapi_first.templates.health_app.main.OpenAPIFirstApp.openapi", - "signature": "", + "signature": null, "docstring": null } } }, - "routes": { - "name": "routes", - "kind": "alias", - "path": "openapi_first.templates.health_app.main.routes", - "signature": "", - "docstring": null - }, "app": { "name": "app", "kind": "attribute", diff --git a/openapi-first/mcp/modules/openapi_first.templates.health_app.routes.json b/openapi-first/mcp/modules/openapi_first.templates.health_app.routes.json index fbd08a9..5bba427 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.health_app.routes.json +++ b/openapi-first/mcp/modules/openapi_first.templates.health_app.routes.json @@ -8,7 +8,7 @@ "name": "get_health", "kind": "function", "path": "openapi_first.templates.health_app.routes.get_health", - "signature": "", + "signature": "get_health() -> dict", "docstring": "Health check operation handler.\n\nThis function implements the OpenAPI operation identified by\n``operationId: get_health``.\n\nIt contains no routing metadata or framework-specific logic.\nRequest binding, HTTP method, and response semantics are defined\nexclusively by the OpenAPI specification.\n\nReturns\n-------\ndict\n A minimal liveness payload indicating service health." } } diff --git a/openapi-first/mcp/modules/openapi_first.templates.json b/openapi-first/mcp/modules/openapi_first.templates.json index 353040a..1fe60df 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.json +++ b/openapi-first/mcp/modules/openapi_first.templates.json @@ -9,7 +9,7 @@ "kind": "module", "path": "openapi_first.templates.crud_app", "signature": null, - "docstring": "OpenAPI-first CRUD application template.\n\nThis package contains a complete, minimal example of an OpenAPI-first\nCRUD service built using the ``openapi_first`` library.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace implementing CRUD operations (``routes``)\n- an in-memory mock data store (``data``)\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification and enforced at application startup.\nNo decorator-driven routing or implicit framework behavior is used.\n\nThis template demonstrates:\n- operationId-driven server-side route binding\n- explicit HTTP status code control in handlers\n- operationId-driven client usage against the same OpenAPI contract\n- end-to-end validation using in-memory data and tests\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new CRUD example service using the bundled template:\n\n openapi-first crud_app\n\nCreate the service in a custom directory:\n\n openapi-first crud_app my-crud-service\n\nList all available application templates:\n\n openapi-first --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client calls for CRUD operations:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List items\n response = client.list_items()\n\n # Get item by ID\n response = client.get_item(\n path_params={\"item_id\": 1}\n )\n\n # Create item\n response = client.create_item(\n body={\"name\": \"Orange\", \"price\": 0.8}\n )\n\n # Update item\n response = client.update_item(\n path_params={\"item_id\": 1},\n body={\"name\": \"Green Apple\", \"price\": 0.6},\n )\n\n # Delete item\n response = client.delete_item(\n path_params={\"item_id\": 1}\n )\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Path and request parameters must match the OpenAPI specification\n- Invalid or incomplete OpenAPI specs fail at client construction time\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.", + "docstring": "OpenAPI-first CRUD application template.\n\nThis package contains a complete, minimal example of an OpenAPI-first\nCRUD service built using the ``openapi_first`` library.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace implementing CRUD operations (``routes``)\n- an in-memory mock data store (``data``)\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification and enforced at application startup.\nNo decorator-driven routing or implicit framework behavior is used.\n\nThis template demonstrates:\n- operationId-driven server-side route binding\n- explicit HTTP status code control in handlers\n- operationId-driven client usage against the same OpenAPI contract\n- end-to-end validation using in-memory data and tests\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new CRUD example service using the bundled template:\n\n openapi-first scaffold crud_app\n\nCreate the service in a custom directory:\n\n openapi-first scaffold crud_app my-crud-service\n\nList all available application templates:\n\n openapi-first scaffold --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client calls for CRUD operations:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List items\n response = client.list_items()\n\n # Get item by ID\n response = client.get_item(\n path_params={\"item_id\": 1}\n )\n\n # Create item\n response = client.create_item(\n body={\"name\": \"Orange\", \"price\": 0.8}\n )\n\n # Update item\n response = client.update_item(\n path_params={\"item_id\": 1},\n body={\"name\": \"Green Apple\", \"price\": 0.6},\n )\n\n # Delete item\n response = client.delete_item(\n path_params={\"item_id\": 1}\n )\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Path and request parameters must match the OpenAPI specification\n- Invalid or incomplete OpenAPI specs fail at client construction time\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.", "members": { "data": { "name": "data", @@ -18,46 +18,39 @@ "signature": null, "docstring": "In-memory mock data store for CRUD example.\n\nThis module intentionally avoids persistence and concurrency guarantees.\nIt is suitable for demos, tests, and scaffolding only.\n\nIt intentionally avoids\n- persistence\n- concurrency guarantees\n- validation\n- error handling\n\nThe implementation is suitable for:\n- demonstrations\n- tests\n- scaffolding and example services\n\nIt is explicitly NOT suitable for production use.\n\nThis module is not part of the ``openapi_first`` library API surface.", "members": { - "Dict": { - "name": "Dict", - "kind": "alias", - "path": "openapi_first.templates.crud_app.data.Dict", - "signature": "", - "docstring": null - }, "list_items": { "name": "list_items", "kind": "function", "path": "openapi_first.templates.crud_app.data.list_items", - "signature": "", + "signature": "list_items() -> list[dict]", "docstring": "Return all items in the data store.\n\nThis function performs no filtering, pagination, or sorting.\nThe returned collection reflects the current in-memory state.\n\nReturns\n-------\nlist[dict]\n A list of item representations." }, "get_item": { "name": "get_item", "kind": "function", "path": "openapi_first.templates.crud_app.data.get_item", - "signature": "", + "signature": "get_item(item_id: int) -> dict", "docstring": "Retrieve a single item by ID.\n\nThis function assumes the item exists and will raise ``KeyError``\nif the ID is not present in the store.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\ndict\n The stored item representation." }, "create_item": { "name": "create_item", "kind": "function", "path": "openapi_first.templates.crud_app.data.create_item", - "signature": "", + "signature": "create_item(payload: dict) -> dict", "docstring": "Create a new item in the data store.\n\nA new integer ID is assigned automatically. No validation is\nperformed on the provided payload.\n\nParameters\n----------\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The newly created item, including its assigned ID." }, "update_item": { "name": "update_item", "kind": "function", "path": "openapi_first.templates.crud_app.data.update_item", - "signature": "", + "signature": "update_item(item_id: int, payload: dict) -> dict", "docstring": "Replace an existing item in the data store.\n\nThis function overwrites the existing item entirely and does not\nperform partial updates or validation. If the item does not exist,\nit will be created implicitly.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The updated item representation." }, "delete_item": { "name": "delete_item", "kind": "function", "path": "openapi_first.templates.crud_app.data.delete_item", - "signature": "", + "signature": "delete_item(item_id: int)", "docstring": "Remove an item from the data store.\n\nThis function assumes the item exists and will raise ``KeyError``\nif the ID is not present.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete." } } @@ -73,25 +66,18 @@ "name": "OpenAPIFirstApp", "kind": "class", "path": "openapi_first.templates.crud_app.main.OpenAPIFirstApp", - "signature": "", - "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", + "signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)", + "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", "members": { "openapi": { "name": "openapi", "kind": "attribute", "path": "openapi_first.templates.crud_app.main.OpenAPIFirstApp.openapi", - "signature": "", + "signature": null, "docstring": null } } }, - "routes": { - "name": "routes", - "kind": "alias", - "path": "openapi_first.templates.crud_app.main.routes", - "signature": "", - "docstring": null - }, "app": { "name": "app", "kind": "attribute", @@ -108,54 +94,40 @@ "signature": null, "docstring": "CRUD route handlers bound via OpenAPI operationId.\n\nThese handlers explicitly control HTTP status codes to ensure\nruntime behavior matches the OpenAPI contract.\n\nThis module defines OpenAPI-bound operation handlers for a simple CRUD\nservice. Functions in this module are bound to HTTP routes exclusively\nvia OpenAPI ``operationId`` values.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Error conditions are translated\ninto explicit HTTP responses rather than relying on implicit framework\nbehavior.\n\nNo routing decorators or path definitions appear in this module. All\nrouting, HTTP methods, and schemas are defined in the OpenAPI\nspecification.", "members": { - "Response": { - "name": "Response", - "kind": "alias", - "path": "openapi_first.templates.crud_app.routes.Response", - "signature": "", - "docstring": null - }, - "HTTPException": { - "name": "HTTPException", - "kind": "alias", - "path": "openapi_first.templates.crud_app.routes.HTTPException", - "signature": "", - "docstring": null - }, "list_items": { "name": "list_items", "kind": "function", "path": "openapi_first.templates.crud_app.routes.list_items", - "signature": "", + "signature": "list_items() -> list[dict]", "docstring": "List all items.\n\nImplements the OpenAPI operation identified by\n``operationId: list_items``.\n\nReturns\n-------\nlist[dict]\n A list of item representations." }, "get_item": { "name": "get_item", "kind": "function", "path": "openapi_first.templates.crud_app.routes.get_item", - "signature": "", + "signature": "get_item(item_id: int) -> dict", "docstring": "Retrieve a single item by ID.\n\nImplements the OpenAPI operation identified by\n``operationId: get_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\ndict\n The requested item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." }, "create_item": { "name": "create_item", "kind": "function", "path": "openapi_first.templates.crud_app.routes.create_item", - "signature": "", - "docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : dict\n Item attributes excluding the ``id`` field.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\ndict\n The newly created item." + "signature": "create_item(payload: dict, response: Response) -> dict", + "docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : dict\n Item attributes excluding the ``id`` field.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\ndict\n The newly created item." }, "update_item": { "name": "update_item", "kind": "function", "path": "openapi_first.templates.crud_app.routes.update_item", - "signature": "", + "signature": "update_item(item_id: int, payload: dict) -> dict", "docstring": "Update an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: update_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The updated item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." }, "delete_item": { "name": "delete_item", "kind": "function", "path": "openapi_first.templates.crud_app.routes.delete_item", - "signature": "", - "docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." + "signature": "delete_item(item_id: int, response: Response) -> None", + "docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." } } }, @@ -166,60 +138,46 @@ "signature": null, "docstring": "End-to-end tests for the OpenAPI-first CRUD example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store.\n- OpenAPI specification loading\n- OperationId-driven route binding on the server\n- OperationId-driven client invocation\n- Correct HTTP status codes and response payloads\n\nThe tests exercise all CRUD operations against an in-memory mock data\nstore and assume deterministic behavior within a single process.\n\nThe tests assume:\n- OpenAPI-first route binding\n- In-memory storage (no persistence guarantees)\n- Deterministic behavior in a single process\n- One-to-one correspondence between OpenAPI operationId values and\n server/client callables", "members": { - "TestClient": { - "name": "TestClient", - "kind": "alias", - "path": "openapi_first.templates.crud_app.test_crud_app.TestClient", - "signature": "", - "docstring": null - }, - "app": { - "name": "app", - "kind": "alias", - "path": "openapi_first.templates.crud_app.test_crud_app.app", - "signature": "", - "docstring": null - }, "load_openapi": { "name": "load_openapi", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.load_openapi", - "signature": "", + "signature": "load_openapi(path: str | Path)", "docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup." }, "OpenAPIClient": { "name": "OpenAPIClient", "kind": "class", "path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient", - "signature": "", - "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", + "signature": "OpenAPIClient(spec: dict[str, Any], base_url: str | None = None, client: httpx.Client | None = None)", + "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nArgs:\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification.\n base_url (str | None):\n Base URL of the target service. If omitted, the first entry in the\n OpenAPI `servers` list is used.\n client (httpx.Client | None):\n Optional preconfigured httpx client instance.\n\nRaises:\n OpenAPIClientError:\n If no servers are defined, spec has no paths, operationIds are\n missing/duplicate, or required parameters are missing.\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", "members": { "spec": { "name": "spec", "kind": "attribute", "path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.spec", - "signature": "", + "signature": null, "docstring": null }, "base_url": { "name": "base_url", "kind": "attribute", "path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.base_url", - "signature": "", + "signature": null, "docstring": null }, "client": { "name": "client", "kind": "attribute", "path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.client", - "signature": "", + "signature": null, "docstring": null }, "operations": { "name": "operations", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.operations", - "signature": "", + "signature": "operations()", "docstring": null } } @@ -242,35 +200,35 @@ "name": "test_list_items_initial", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.test_list_items_initial", - "signature": "", + "signature": "test_list_items_initial()", "docstring": "Initial items should be present." }, "test_get_item": { "name": "test_get_item", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.test_get_item", - "signature": "", + "signature": "test_get_item()", "docstring": "Existing item should be retrievable by ID." }, "test_create_item": { "name": "test_create_item", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.test_create_item", - "signature": "", + "signature": "test_create_item()", "docstring": "Creating a new item should return the created entity." }, "test_update_item": { "name": "test_update_item", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.test_update_item", - "signature": "", + "signature": "test_update_item()", "docstring": "Updating an item should replace its values." }, "test_delete_item": { "name": "test_delete_item", "kind": "function", "path": "openapi_first.templates.crud_app.test_crud_app.test_delete_item", - "signature": "", + "signature": "test_delete_item()", "docstring": "Deleting an item should remove it from the store." } } @@ -282,7 +240,7 @@ "kind": "module", "path": "openapi_first.templates.health_app", "signature": null, - "docstring": "OpenAPI-first FastAPI application template.\n\nThis package contains a minimal, fully working example of an\nOpenAPI-first FastAPI service built using the ``openapi_first`` library.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace (``routes``)\n\nNo routing decorators, implicit behavior, or framework-specific\nconvenience abstractions are used. All HTTP routes, methods, and\noperation bindings are defined in OpenAPI and enforced at application\nstartup.\n\nThis package is intended to be copied as a starting point for new\nservices via the ``openapi-first`` CLI. It is not part of the\n``openapi_first`` library API surface.\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new OpenAPI-first health check service using the bundled\ntemplate:\n\n openapi-first health_app\n\nCreate the service in a custom directory:\n\n openapi-first health_app my-health-service\n\nList all available application templates:\n\n openapi-first --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client call for the ``get_health`` operation:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n response = client.get_health()\n\n assert response.status_code == 200\n assert response.json() == {\"status\": \"ok\"}\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Path and request parameters must match the OpenAPI specification\n- Invalid or incomplete OpenAPI specs fail at client construction time", + "docstring": "OpenAPI-first FastAPI application template.\n\nThis package contains a minimal, fully working example of an\nOpenAPI-first FastAPI service built using the ``openapi_first`` library.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace (``routes``)\n\nNo routing decorators, implicit behavior, or framework-specific\nconvenience abstractions are used. All HTTP routes, methods, and\noperation bindings are defined in OpenAPI and enforced at application\nstartup.\n\nThis package is intended to be copied as a starting point for new\nservices via the ``openapi-first`` CLI. It is not part of the\n``openapi_first`` library API surface.\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new OpenAPI-first health check service using the bundled\ntemplate:\n\n openapi-first scaffold health_app\n\nCreate the service in a custom directory:\n\n openapi-first scaffold health_app my-health-service\n\nList all available application templates:\n\n openapi-first scaffold --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client call for the ``get_health`` operation:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n response = client.get_health()\n\n assert response.status_code == 200\n assert response.json() == {\"status\": \"ok\"}\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Path and request parameters must match the OpenAPI specification\n- Invalid or incomplete OpenAPI specs fail at client construction time", "members": { "main": { "name": "main", @@ -295,25 +253,18 @@ "name": "OpenAPIFirstApp", "kind": "class", "path": "openapi_first.templates.health_app.main.OpenAPIFirstApp", - "signature": "", - "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", + "signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)", + "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", "members": { "openapi": { "name": "openapi", "kind": "attribute", "path": "openapi_first.templates.health_app.main.OpenAPIFirstApp.openapi", - "signature": "", + "signature": null, "docstring": null } } }, - "routes": { - "name": "routes", - "kind": "alias", - "path": "openapi_first.templates.health_app.main.routes", - "signature": "", - "docstring": null - }, "app": { "name": "app", "kind": "attribute", @@ -334,7 +285,7 @@ "name": "get_health", "kind": "function", "path": "openapi_first.templates.health_app.routes.get_health", - "signature": "", + "signature": "get_health() -> dict", "docstring": "Health check operation handler.\n\nThis function implements the OpenAPI operation identified by\n``operationId: get_health``.\n\nIt contains no routing metadata or framework-specific logic.\nRequest binding, HTTP method, and response semantics are defined\nexclusively by the OpenAPI specification.\n\nReturns\n-------\ndict\n A minimal liveness payload indicating service health." } } @@ -346,7 +297,7 @@ "kind": "module", "path": "openapi_first.templates.model_app", "signature": null, - "docstring": "OpenAPI-first model-based CRUD application template.\n\nThis package contains a complete, minimal example of an OpenAPI-first\nCRUD service that uses explicit Pydantic domain models for request and\nresponse schemas.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace implementing CRUD operations (``routes``)\n- Pydantic domain models (``models``)\n- an in-memory mock data store (``data``)\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification and enforced at application startup.\nNo decorator-driven routing or implicit framework behavior is used.\n\nThis template demonstrates:\n- operationId-driven server-side route binding\n- explicit request and response modeling with Pydantic\n- explicit HTTP status code control in handlers\n- operationId-driven client usage against the same OpenAPI contract\n- end-to-end validation using in-memory data and tests\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new model-based CRUD example service using the bundled template:\n\n openapi-first model_app\n\nCreate the service in a custom directory:\n\n openapi-first model_app my-model-service\n\nList all available application templates:\n\n openapi-first --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client calls for model-based CRUD operations:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List items\n response = client.list_items()\n\n # Get item by ID\n response = client.get_item(\n path_params={\"item_id\": 1}\n )\n\n # Create item\n response = client.create_item(\n body={\"name\": \"Orange\", \"price\": 0.8}\n )\n\n # Update item\n response = client.update_item(\n path_params={\"item_id\": 1},\n body={\"name\": \"Green Apple\", \"price\": 0.6},\n )\n\n # Delete item\n response = client.delete_item(\n path_params={\"item_id\": 1}\n )\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Request and response payloads conform to Pydantic models\n- Invalid or incomplete OpenAPI specs fail at client construction time\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.", + "docstring": "OpenAPI-first model-based CRUD application template.\n\nThis package contains a complete, minimal example of an OpenAPI-first\nCRUD service that uses explicit Pydantic domain models for request and\nresponse schemas.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace implementing CRUD operations (``routes``)\n- Pydantic domain models (``models``)\n- an in-memory mock data store (``data``)\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification and enforced at application startup.\nNo decorator-driven routing or implicit framework behavior is used.\n\nThis template demonstrates:\n- operationId-driven server-side route binding\n- explicit request and response modeling with Pydantic\n- explicit HTTP status code control in handlers\n- operationId-driven client usage against the same OpenAPI contract\n- end-to-end validation using in-memory data and tests\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new model-based CRUD example service using the bundled template:\n\n openapi-first scaffold model_app\n\nCreate the service in a custom directory:\n\n openapi-first scaffold model_app my-model-service\n\nList all available application templates:\n\n openapi-first scaffold --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client calls for model-based CRUD operations:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List items\n response = client.list_items()\n\n # Get item by ID\n response = client.get_item(\n path_params={\"item_id\": 1}\n )\n\n # Create item\n response = client.create_item(\n body={\"name\": \"Orange\", \"price\": 0.8}\n )\n\n # Update item\n response = client.update_item(\n path_params={\"item_id\": 1},\n body={\"name\": \"Green Apple\", \"price\": 0.6},\n )\n\n # Delete item\n response = client.delete_item(\n path_params={\"item_id\": 1}\n )\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Request and response payloads conform to Pydantic models\n- Invalid or incomplete OpenAPI specs fail at client construction time\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.", "members": { "data": { "name": "data", @@ -355,60 +306,39 @@ "signature": null, "docstring": "In-memory data store using Pydantic models.\n\nThis module is NOT thread-safe and is intended for demos and scaffolds only.\nThis module provides a minimal, process-local data store for the\nmodel-based CRUD example application. It stores and returns domain\nobjects defined using Pydantic models and is intended solely for\ndemonstration and scaffolding purposes.\n\nThe implementation intentionally avoids:\n- persistence\n- concurrency guarantees\n- transactional semantics\n- validation beyond what Pydantic provides\n\nIt is not part of the ``openapi_first`` library API surface.", "members": { - "Dict": { - "name": "Dict", - "kind": "alias", - "path": "openapi_first.templates.model_app.data.Dict", - "signature": "", - "docstring": null - }, - "Item": { - "name": "Item", - "kind": "alias", - "path": "openapi_first.templates.model_app.data.Item", - "signature": "", - "docstring": null - }, - "ItemCreate": { - "name": "ItemCreate", - "kind": "alias", - "path": "openapi_first.templates.model_app.data.ItemCreate", - "signature": "", - "docstring": null - }, "list_items": { "name": "list_items", "kind": "function", "path": "openapi_first.templates.model_app.data.list_items", - "signature": "", + "signature": "list_items() -> list[Item]", "docstring": "Return all items in the data store.\n\nReturns\n-------\nlist[Item]\n A list of item domain objects." }, "get_item": { "name": "get_item", "kind": "function", "path": "openapi_first.templates.model_app.data.get_item", - "signature": "", + "signature": "get_item(item_id: int) -> Item", "docstring": "Retrieve a single item by ID.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\nItem\n The requested item.\n\nRaises\n------\nKeyError\n If the item does not exist." }, "create_item": { "name": "create_item", "kind": "function", "path": "openapi_first.templates.model_app.data.create_item", - "signature": "", + "signature": "create_item(payload: ItemCreate) -> Item", "docstring": "Create a new item in the data store.\n\nA new identifier is assigned automatically. No additional validation\nis performed beyond Pydantic model validation.\n\nParameters\n----------\npayload : ItemCreate\n Data required to create a new item.\n\nReturns\n-------\nItem\n The newly created item." }, "update_item": { "name": "update_item", "kind": "function", "path": "openapi_first.templates.model_app.data.update_item", - "signature": "", + "signature": "update_item(item_id: int, payload: ItemCreate) -> Item", "docstring": "Replace an existing item in the data store.\n\nThis function performs a full replacement of the stored item.\nPartial updates are not supported.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : ItemCreate\n New item data.\n\nReturns\n-------\nItem\n The updated item.\n\nRaises\n------\nKeyError\n If the item does not exist." }, "delete_item": { "name": "delete_item", "kind": "function", "path": "openapi_first.templates.model_app.data.delete_item", - "signature": "", + "signature": "delete_item(item_id: int) -> None", "docstring": "Remove an item from the data store.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\n\nRaises\n------\nKeyError\n If the item does not exist." } } @@ -424,25 +354,18 @@ "name": "OpenAPIFirstApp", "kind": "class", "path": "openapi_first.templates.model_app.main.OpenAPIFirstApp", - "signature": "", - "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", + "signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)", + "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", "members": { "openapi": { "name": "openapi", "kind": "attribute", "path": "openapi_first.templates.model_app.main.OpenAPIFirstApp.openapi", - "signature": "", + "signature": null, "docstring": null } } }, - "routes": { - "name": "routes", - "kind": "alias", - "path": "openapi_first.templates.model_app.main.routes", - "signature": "", - "docstring": null - }, "app": { "name": "app", "kind": "attribute", @@ -459,18 +382,11 @@ "signature": null, "docstring": "Pydantic domain models for the CRUD example.\n\nThis module defines Pydantic models that represent the domain entities\nused by the service. These models are referenced by the OpenAPI\nspecification for request and response schemas.\n\nThe models are declarative and framework-agnostic. They contain no\npersistence logic, validation beyond type constraints, or business\nbehavior.\n\nThis module is not part of the ``openapi_first`` library API surface.\nIt exists solely to support the example application template.", "members": { - "BaseModel": { - "name": "BaseModel", - "kind": "alias", - "path": "openapi_first.templates.model_app.models.BaseModel", - "signature": "", - "docstring": null - }, "ItemBase": { "name": "ItemBase", "kind": "class", "path": "openapi_first.templates.model_app.models.ItemBase", - "signature": "", + "signature": null, "docstring": "Base domain model for an item.\n\nDefines fields common to all item representations.", "members": { "name": { @@ -493,14 +409,14 @@ "name": "ItemCreate", "kind": "class", "path": "openapi_first.templates.model_app.models.ItemCreate", - "signature": "", + "signature": null, "docstring": "Domain model for item creation requests.\n\nThis model is used for request bodies when creating new items.\nIt intentionally excludes the ``id`` field, which is assigned\nby the service." }, "Item": { "name": "Item", "kind": "class", "path": "openapi_first.templates.model_app.models.Item", - "signature": "", + "signature": null, "docstring": "Domain model for a persisted item.\n\nThis model represents the full item state returned in responses,\nincluding the server-assigned identifier.", "members": { "id": { @@ -521,61 +437,40 @@ "signature": null, "docstring": "CRUD route handlers bound via OpenAPI operationId.\n\nThis module defines OpenAPI-bound operation handlers for a model-based\nCRUD service. Functions in this module are bound to HTTP routes\nexclusively via OpenAPI ``operationId`` values.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Domain models defined using\nPydantic are used for request and response payloads.\n\nNo routing decorators, path definitions, or implicit framework behavior\nappear in this module. All routing, HTTP methods, and schemas are defined\nin the OpenAPI specification.", "members": { - "Response": { - "name": "Response", - "kind": "alias", - "path": "openapi_first.templates.model_app.routes.Response", - "signature": "", - "docstring": null - }, - "HTTPException": { - "name": "HTTPException", - "kind": "alias", - "path": "openapi_first.templates.model_app.routes.HTTPException", - "signature": "", - "docstring": null - }, - "ItemCreate": { - "name": "ItemCreate", - "kind": "alias", - "path": "openapi_first.templates.model_app.routes.ItemCreate", - "signature": "", - "docstring": null - }, "list_items": { "name": "list_items", "kind": "function", "path": "openapi_first.templates.model_app.routes.list_items", - "signature": "", + "signature": "list_items() -> list[Item]", "docstring": "List all items.\n\nImplements the OpenAPI operation identified by\n``operationId: list_items``.\n\nReturns\n-------\nlist[Item]\n A list of item domain objects." }, "get_item": { "name": "get_item", "kind": "function", "path": "openapi_first.templates.model_app.routes.get_item", - "signature": "", + "signature": "get_item(item_id: int) -> Item", "docstring": "Retrieve a single item by ID.\n\nImplements the OpenAPI operation identified by\n``operationId: get_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\nItem\n The requested item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." }, "create_item": { "name": "create_item", "kind": "function", "path": "openapi_first.templates.model_app.routes.create_item", - "signature": "", - "docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : ItemCreate\n Request body describing the item to create.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nItem\n The newly created item." + "signature": "create_item(payload: ItemCreate, response: Response) -> Item", + "docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : ItemCreate\n Request body describing the item to create.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nItem\n The newly created item." }, "update_item": { "name": "update_item", "kind": "function", "path": "openapi_first.templates.model_app.routes.update_item", - "signature": "", + "signature": "update_item(item_id: int, payload: ItemCreate) -> Item", "docstring": "Update an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: update_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : ItemCreate\n New item data.\n\nReturns\n-------\nItem\n The updated item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." }, "delete_item": { "name": "delete_item", "kind": "function", "path": "openapi_first.templates.model_app.routes.delete_item", - "signature": "", - "docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." + "signature": "delete_item(item_id: int, response: Response) -> None", + "docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." } } }, @@ -586,60 +481,46 @@ "signature": null, "docstring": "End-to-end tests for the OpenAPI-first model CRUD example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store using Pydantic models.\n- OpenAPI specification loading\n- OperationId-driven route binding on the server\n- OperationId-driven client invocation\n- Pydantic model-based request and response handling\n\nAll CRUD operations are exercised against an in-memory mock data store\nbacked by Pydantic domain models.\n\nThe tests assume:\n- OpenAPI-first route binding\n- Pydantic model validation\n- In-memory storage (no persistence guarantees)\n- Deterministic behavior in a single process", "members": { - "TestClient": { - "name": "TestClient", - "kind": "alias", - "path": "openapi_first.templates.model_app.test_model_app.TestClient", - "signature": "", - "docstring": null - }, - "app": { - "name": "app", - "kind": "alias", - "path": "openapi_first.templates.model_app.test_model_app.app", - "signature": "", - "docstring": null - }, "load_openapi": { "name": "load_openapi", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.load_openapi", - "signature": "", + "signature": "load_openapi(path: str | Path)", "docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup." }, "OpenAPIClient": { "name": "OpenAPIClient", "kind": "class", "path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient", - "signature": "", - "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", + "signature": "OpenAPIClient(spec: dict[str, Any], base_url: str | None = None, client: httpx.Client | None = None)", + "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nArgs:\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification.\n base_url (str | None):\n Base URL of the target service. If omitted, the first entry in the\n OpenAPI `servers` list is used.\n client (httpx.Client | None):\n Optional preconfigured httpx client instance.\n\nRaises:\n OpenAPIClientError:\n If no servers are defined, spec has no paths, operationIds are\n missing/duplicate, or required parameters are missing.\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", "members": { "spec": { "name": "spec", "kind": "attribute", "path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.spec", - "signature": "", + "signature": null, "docstring": null }, "base_url": { "name": "base_url", "kind": "attribute", "path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.base_url", - "signature": "", + "signature": null, "docstring": null }, "client": { "name": "client", "kind": "attribute", "path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.client", - "signature": "", + "signature": null, "docstring": null }, "operations": { "name": "operations", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.operations", - "signature": "", + "signature": "operations()", "docstring": null } } @@ -662,35 +543,35 @@ "name": "test_list_items_initial", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.test_list_items_initial", - "signature": "", + "signature": "test_list_items_initial()", "docstring": "Initial items should be present." }, "test_get_item": { "name": "test_get_item", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.test_get_item", - "signature": "", + "signature": "test_get_item()", "docstring": "Existing item should be retrievable by ID." }, "test_create_item": { "name": "test_create_item", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.test_create_item", - "signature": "", + "signature": "test_create_item()", "docstring": "Creating a new item should return the created entity." }, "test_update_item": { "name": "test_update_item", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.test_update_item", - "signature": "", + "signature": "test_update_item()", "docstring": "Updating an item should replace its values." }, "test_delete_item": { "name": "test_delete_item", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.test_delete_item", - "signature": "", + "signature": "test_delete_item()", "docstring": "Deleting an item should remove it from the store." } } @@ -702,7 +583,7 @@ "kind": "module", "path": "openapi_first.templates.vet_app", "signature": null, - "docstring": "OpenAPI-first Veterinary Clinic application template.\n\nThis package contains a complete, runnable example of an OpenAPI-first\nveterinary clinic management service. It demonstrates all ``x-`` extension\nfields consumed by the ``react-openapi`` admin panel renderer.\n\nThe application manages five resources:\n\n- **Parents** — pet owners with contact details\n- **Vets** — veterinarians with specializations\n- **Treatments** — medical procedure catalog\n- **Pets** — animals with species, age, weight, and photos\n- **Appointments** — scheduled visits linking pets, vets, and treatments\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification (``openapi.yaml``). Every operation has an\nexplicit ``operationId`` that maps to a Python handler in ``routes.py``.\n\nThis file is a copyable template. It is not part of the ``openapi_first``\nlibrary API surface.\n\n----------------------------------------------------------------------\nOpenAPI x- extension fields demonstrated\n----------------------------------------------------------------------\n\nSchema-level extensions (display metadata for resource endpoints):\n\n ``x-primary-key`` (REQUIRED) Primary key property name\n ``x-display-format`` (REQUIRED) Human-readable label template\n ``x-list-columns`` (REQUIRED) Columns for the datatable\n\nProperty-level extensions (control UI rendering):\n\n ``x-label`` (REQUIRED) Human-readable field label\n ``x-order`` (REQUIRED) Field ordering in forms/detail\n ``x-description`` (optional) Helper text below form fields\n ``x-hidden`` (optional) Visibility in form / list / detail\n ``x-filterable`` (optional) Allows column filtering\n ``x-sortable`` (optional) Allows column sorting\n ``x-fk`` (optional) Foreign key — renders as dropdown\n ``x-fk.resource`` (REQUIRED for FK) Target resource name\n ``x-fk.prefetch`` (optional) Preload all FK options on mount\n ``x-ui-type`` (optional) Custom UI type (e.g. image upload)\n ``x-upload-url`` (optional) Upload endpoint for binary fields\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new vet clinic service using the bundled template:\n\n openapi-first vet_app\n\nCreate the service in a custom directory:\n\n openapi-first vet_app my-vet-clinic\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List pets with pagination\n response = client.list_pets(query_params={\"limit\": 10, \"offset\": 0})\n\n # Create a pet with FK references\n response = client.create_pet(\n body={\"name\": \"Fido\", \"species\": \"dog\", \"parents\": [1, 2]}\n )\n\n # Upload a pet photo\n response = client.upload_pet_photo(\n path_params={\"id\": 1},\n body={\"file\": open(\"photo.jpg\", \"rb\")},\n )\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.", + "docstring": "OpenAPI-first Veterinary Clinic application template.\n\nThis package contains a complete, runnable example of an OpenAPI-first\nveterinary clinic management service. It demonstrates all ``x-`` extension\nfields consumed by the ``react-openapi`` admin panel renderer.\n\nThe application manages five resources:\n\n- **Parents** — pet owners with contact details\n- **Vets** — veterinarians with specializations\n- **Treatments** — medical procedure catalog\n- **Pets** — animals with species, age, weight, and photos\n- **Appointments** — scheduled visits linking pets, vets, and treatments\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification (``openapi.yaml``). Every operation has an\nexplicit ``operationId`` that maps to a Python handler in ``routes.py``.\n\nThis file is a copyable template. It is not part of the ``openapi_first``\nlibrary API surface.\n\n----------------------------------------------------------------------\nOpenAPI x- extension fields demonstrated\n----------------------------------------------------------------------\n\nSchema-level extensions (display metadata for resource endpoints):\n\n ``x-primary-key`` (REQUIRED) Primary key property name\n ``x-display-format`` (REQUIRED) Human-readable label template\n ``x-list-columns`` (REQUIRED) Columns for the datatable\n\nProperty-level extensions (control UI rendering):\n\n ``x-label`` (REQUIRED) Human-readable field label\n ``x-order`` (REQUIRED) Field ordering in forms/detail\n ``x-description`` (optional) Helper text below form fields\n ``x-hidden`` (optional) Visibility in form / list / detail\n ``x-filterable`` (optional) Allows column filtering\n ``x-sortable`` (optional) Allows column sorting\n ``x-fk`` (optional) Foreign key — renders as dropdown\n ``x-fk.resource`` (REQUIRED for FK) Target resource name\n ``x-fk.prefetch`` (optional) Preload all FK options on mount\n ``x-ui-type`` (optional) Custom UI type (e.g. image upload)\n ``x-upload-url`` (optional) Upload endpoint for binary fields\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new vet clinic service using the bundled template:\n\n openapi-first scaffold vet_app\n\nCreate the service in a custom directory:\n\n openapi-first scaffold vet_app my-vet-clinic\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List pets with pagination\n response = client.list_pets(query_params={\"limit\": 10, \"offset\": 0})\n\n # Create a pet with FK references\n response = client.create_pet(\n body={\"name\": \"Fido\", \"species\": \"dog\", \"parents\": [1, 2]}\n )\n\n # Upload a pet photo\n response = client.upload_pet_photo(\n path_params={\"id\": 1},\n body={\"file\": open(\"photo.jpg\", \"rb\")},\n )\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.", "members": { "data": { "name": "data", @@ -711,319 +592,179 @@ "signature": null, "docstring": "In-memory data store for the Veterinary Clinic example.\n\nThis module is NOT thread-safe and is intended for demos and scaffolds only.\n\nIt provides minimal, process-local data stores for the five veterinary\nclinic entities. Each store exposes standard CRUD operations backed by\na simple dictionary.\n\nThis module intentionally avoids:\n- persistence\n- concurrency guarantees\n- transactional semantics\n- validation beyond what Pydantic provides\n\nThis module is not part of the ``openapi_first`` library API surface.", "members": { - "date": { - "name": "date", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.date", - "signature": "", - "docstring": null - }, - "datetime": { - "name": "datetime", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.datetime", - "signature": "", - "docstring": null - }, - "timezone": { - "name": "timezone", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.timezone", - "signature": "", - "docstring": null - }, - "Parent": { - "name": "Parent", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Parent", - "signature": "", - "docstring": null - }, - "ParentCreate": { - "name": "ParentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.ParentCreate", - "signature": "", - "docstring": null - }, - "Vet": { - "name": "Vet", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Vet", - "signature": "", - "docstring": null - }, - "VetCreate": { - "name": "VetCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.VetCreate", - "signature": "", - "docstring": null - }, - "Treatment": { - "name": "Treatment", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Treatment", - "signature": "", - "docstring": null - }, - "TreatmentCreate": { - "name": "TreatmentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.TreatmentCreate", - "signature": "", - "docstring": null - }, - "Procedure": { - "name": "Procedure", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Procedure", - "signature": "", - "docstring": null - }, - "BasicNote": { - "name": "BasicNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.BasicNote", - "signature": "", - "docstring": null - }, - "HeartRateNote": { - "name": "HeartRateNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.HeartRateNote", - "signature": "", - "docstring": null - }, - "DentalNote": { - "name": "DentalNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.DentalNote", - "signature": "", - "docstring": null - }, - "VaccineNote": { - "name": "VaccineNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.VaccineNote", - "signature": "", - "docstring": null - }, - "PreOpNote": { - "name": "PreOpNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.PreOpNote", - "signature": "", - "docstring": null - }, - "SurgeryNote": { - "name": "SurgeryNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.SurgeryNote", - "signature": "", - "docstring": null - }, - "Pet": { - "name": "Pet", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Pet", - "signature": "", - "docstring": null - }, - "PetCreate": { - "name": "PetCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.PetCreate", - "signature": "", - "docstring": null - }, - "Appointment": { - "name": "Appointment", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Appointment", - "signature": "", - "docstring": null - }, - "AppointmentCreate": { - "name": "AppointmentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.AppointmentCreate", - "signature": "", - "docstring": null - }, "list_parents": { "name": "list_parents", "kind": "function", "path": "openapi_first.templates.vet_app.data.list_parents", - "signature": "", + "signature": "list_parents() -> list[Parent]", "docstring": null }, "get_parent": { "name": "get_parent", "kind": "function", "path": "openapi_first.templates.vet_app.data.get_parent", - "signature": "", + "signature": "get_parent(parent_id: int) -> Parent", "docstring": null }, "create_parent": { "name": "create_parent", "kind": "function", "path": "openapi_first.templates.vet_app.data.create_parent", - "signature": "", + "signature": "create_parent(payload: ParentCreate) -> Parent", "docstring": null }, "update_parent": { "name": "update_parent", "kind": "function", "path": "openapi_first.templates.vet_app.data.update_parent", - "signature": "", + "signature": "update_parent(parent_id: int, payload: ParentCreate) -> Parent", "docstring": null }, "delete_parent": { "name": "delete_parent", "kind": "function", "path": "openapi_first.templates.vet_app.data.delete_parent", - "signature": "", + "signature": "delete_parent(parent_id: int) -> None", "docstring": null }, "list_vets": { "name": "list_vets", "kind": "function", "path": "openapi_first.templates.vet_app.data.list_vets", - "signature": "", + "signature": "list_vets() -> list[Vet]", "docstring": null }, "get_vet": { "name": "get_vet", "kind": "function", "path": "openapi_first.templates.vet_app.data.get_vet", - "signature": "", + "signature": "get_vet(vet_id: int) -> Vet", "docstring": null }, "create_vet": { "name": "create_vet", "kind": "function", "path": "openapi_first.templates.vet_app.data.create_vet", - "signature": "", + "signature": "create_vet(payload: VetCreate) -> Vet", "docstring": null }, "update_vet": { "name": "update_vet", "kind": "function", "path": "openapi_first.templates.vet_app.data.update_vet", - "signature": "", + "signature": "update_vet(vet_id: int, payload: VetCreate) -> Vet", "docstring": null }, "delete_vet": { "name": "delete_vet", "kind": "function", "path": "openapi_first.templates.vet_app.data.delete_vet", - "signature": "", + "signature": "delete_vet(vet_id: int) -> None", "docstring": null }, "list_treatments": { "name": "list_treatments", "kind": "function", "path": "openapi_first.templates.vet_app.data.list_treatments", - "signature": "", + "signature": "list_treatments() -> list[Treatment]", "docstring": null }, "get_treatment": { "name": "get_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.data.get_treatment", - "signature": "", + "signature": "get_treatment(treatment_id: int) -> Treatment", "docstring": null }, "create_treatment": { "name": "create_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.data.create_treatment", - "signature": "", + "signature": "create_treatment(payload: TreatmentCreate) -> Treatment", "docstring": null }, "update_treatment": { "name": "update_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.data.update_treatment", - "signature": "", + "signature": "update_treatment(treatment_id: int, payload: TreatmentCreate) -> Treatment", "docstring": null }, "delete_treatment": { "name": "delete_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.data.delete_treatment", - "signature": "", + "signature": "delete_treatment(treatment_id: int) -> None", "docstring": null }, "list_pets": { "name": "list_pets", "kind": "function", "path": "openapi_first.templates.vet_app.data.list_pets", - "signature": "", + "signature": "list_pets() -> list[Pet]", "docstring": null }, "get_pet": { "name": "get_pet", "kind": "function", "path": "openapi_first.templates.vet_app.data.get_pet", - "signature": "", + "signature": "get_pet(pet_id: int) -> Pet", "docstring": null }, "create_pet": { "name": "create_pet", "kind": "function", "path": "openapi_first.templates.vet_app.data.create_pet", - "signature": "", + "signature": "create_pet(payload: PetCreate) -> Pet", "docstring": null }, "update_pet": { "name": "update_pet", "kind": "function", "path": "openapi_first.templates.vet_app.data.update_pet", - "signature": "", + "signature": "update_pet(pet_id: int, payload: PetCreate) -> Pet", "docstring": null }, "delete_pet": { "name": "delete_pet", "kind": "function", "path": "openapi_first.templates.vet_app.data.delete_pet", - "signature": "", + "signature": "delete_pet(pet_id: int) -> None", "docstring": null }, "list_appointments": { "name": "list_appointments", "kind": "function", "path": "openapi_first.templates.vet_app.data.list_appointments", - "signature": "", + "signature": "list_appointments() -> list[Appointment]", "docstring": null }, "get_appointment": { "name": "get_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.data.get_appointment", - "signature": "", + "signature": "get_appointment(appointment_id: int) -> Appointment", "docstring": null }, "create_appointment": { "name": "create_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.data.create_appointment", - "signature": "", + "signature": "create_appointment(payload: AppointmentCreate) -> Appointment", "docstring": null }, "update_appointment": { "name": "update_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.data.update_appointment", - "signature": "", + "signature": "update_appointment(appointment_id: int, payload: AppointmentCreate) -> Appointment", "docstring": null }, "delete_appointment": { "name": "delete_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.data.delete_appointment", - "signature": "", + "signature": "delete_appointment(appointment_id: int) -> None", "docstring": null } } @@ -1035,48 +776,27 @@ "signature": null, "docstring": "Application entry point for an OpenAPI-first Veterinary Clinic service.\n\nThis module constructs a FastAPI application exclusively from an\nOpenAPI specification and a handler namespace, without using\ndecorator-driven routing.\n\nAll HTTP routes, methods, request/response schemas, and operation\nbindings are defined in the OpenAPI document referenced by\n``openapi_path``. Python callables defined in the ``routes`` module are\nbound to OpenAPI operations strictly via ``operationId``.\n\nThis module contains no routing logic, persistence concerns, or\nframework configuration beyond application assembly.\n\nDesign guarantees:\n- OpenAPI is the single source of truth\n- No undocumented routes can exist\n- Every OpenAPI operationId must resolve to exactly one handler\n- All contract violations fail at application startup\n\nThis file is intended to be used as the ASGI entry point.\n\nExample:\n uvicorn main:app", "members": { - "asynccontextmanager": { - "name": "asynccontextmanager", - "kind": "alias", - "path": "openapi_first.templates.vet_app.main.asynccontextmanager", - "signature": "", - "docstring": null - }, - "CORSMiddleware": { - "name": "CORSMiddleware", - "kind": "alias", - "path": "openapi_first.templates.vet_app.main.CORSMiddleware", - "signature": "", - "docstring": null - }, "OpenAPIFirstApp": { "name": "OpenAPIFirstApp", "kind": "class", "path": "openapi_first.templates.vet_app.main.OpenAPIFirstApp", - "signature": "", - "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", + "signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)", + "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", "members": { "openapi": { "name": "openapi", "kind": "attribute", "path": "openapi_first.templates.vet_app.main.OpenAPIFirstApp.openapi", - "signature": "", + "signature": null, "docstring": null } } }, - "routes": { - "name": "routes", - "kind": "alias", - "path": "openapi_first.templates.vet_app.main.routes", - "signature": "", - "docstring": null - }, "lifespan": { "name": "lifespan", "kind": "function", "path": "openapi_first.templates.vet_app.main.lifespan", - "signature": "", + "signature": "lifespan(app)", "docstring": null }, "app": { @@ -1095,53 +815,11 @@ "signature": null, "docstring": null, "members": { - "date": { - "name": "date", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.date", - "signature": "", - "docstring": null - }, - "datetime": { - "name": "datetime", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.datetime", - "signature": "", - "docstring": null - }, - "Annotated": { - "name": "Annotated", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.Annotated", - "signature": "", - "docstring": null - }, - "Literal": { - "name": "Literal", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.Literal", - "signature": "", - "docstring": null - }, - "BaseModel": { - "name": "BaseModel", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.BaseModel", - "signature": "", - "docstring": null - }, - "Field": { - "name": "Field", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.Field", - "signature": "", - "docstring": null - }, "Metadata": { "name": "Metadata", "kind": "class", "path": "openapi_first.templates.vet_app.models.Metadata", - "signature": "", + "signature": null, "docstring": null, "members": { "createdOn": { @@ -1164,7 +842,7 @@ "name": "ParentBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.ParentBase", - "signature": "", + "signature": null, "docstring": null, "members": { "name": { @@ -1201,14 +879,14 @@ "name": "ParentCreate", "kind": "class", "path": "openapi_first.templates.vet_app.models.ParentCreate", - "signature": "", + "signature": null, "docstring": null }, "Parent": { "name": "Parent", "kind": "class", "path": "openapi_first.templates.vet_app.models.Parent", - "signature": "", + "signature": null, "docstring": null, "members": { "id": { @@ -1224,7 +902,7 @@ "name": "VetBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.VetBase", - "signature": "", + "signature": null, "docstring": null, "members": { "name": { @@ -1268,14 +946,14 @@ "name": "VetCreate", "kind": "class", "path": "openapi_first.templates.vet_app.models.VetCreate", - "signature": "", + "signature": null, "docstring": null }, "Vet": { "name": "Vet", "kind": "class", "path": "openapi_first.templates.vet_app.models.Vet", - "signature": "", + "signature": null, "docstring": null, "members": { "id": { @@ -1291,7 +969,7 @@ "name": "ProcedureNoteBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.ProcedureNoteBase", - "signature": "", + "signature": null, "docstring": null, "members": { "summary": { @@ -1314,7 +992,7 @@ "name": "BasicNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.BasicNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -1330,7 +1008,7 @@ "name": "HeartRateNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.HeartRateNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -1353,7 +1031,7 @@ "name": "DentalNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.DentalNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -1383,7 +1061,7 @@ "name": "VaccineNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.VaccineNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -1413,7 +1091,7 @@ "name": "PreOpNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.PreOpNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -1443,7 +1121,7 @@ "name": "SurgeryNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.SurgeryNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -1480,7 +1158,7 @@ "name": "Procedure", "kind": "class", "path": "openapi_first.templates.vet_app.models.Procedure", - "signature": "", + "signature": null, "docstring": null, "members": { "name": { @@ -1517,7 +1195,7 @@ "name": "TreatmentBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.TreatmentBase", - "signature": "", + "signature": null, "docstring": null, "members": { "label": { @@ -1547,14 +1225,14 @@ "name": "TreatmentCreate", "kind": "class", "path": "openapi_first.templates.vet_app.models.TreatmentCreate", - "signature": "", + "signature": null, "docstring": null }, "Treatment": { "name": "Treatment", "kind": "class", "path": "openapi_first.templates.vet_app.models.Treatment", - "signature": "", + "signature": null, "docstring": null, "members": { "id": { @@ -1570,7 +1248,7 @@ "name": "PetBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.PetBase", - "signature": "", + "signature": null, "docstring": null, "members": { "name": { @@ -1628,7 +1306,7 @@ "name": "PetCreate", "kind": "class", "path": "openapi_first.templates.vet_app.models.PetCreate", - "signature": "", + "signature": null, "docstring": null, "members": { "parent_ids": { @@ -1644,7 +1322,7 @@ "name": "Pet", "kind": "class", "path": "openapi_first.templates.vet_app.models.Pet", - "signature": "", + "signature": null, "docstring": null, "members": { "id": { @@ -1667,7 +1345,7 @@ "name": "AppointmentBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.AppointmentBase", - "signature": "", + "signature": null, "docstring": null, "members": { "date": { @@ -1704,7 +1382,7 @@ "name": "AppointmentCreate", "kind": "class", "path": "openapi_first.templates.vet_app.models.AppointmentCreate", - "signature": "", + "signature": null, "docstring": null, "members": { "pet_id": { @@ -1734,7 +1412,7 @@ "name": "Appointment", "kind": "class", "path": "openapi_first.templates.vet_app.models.Appointment", - "signature": "", + "signature": null, "docstring": null, "members": { "id": { @@ -1776,270 +1454,193 @@ "signature": null, "docstring": "Veterinary Clinic route handlers bound via OpenAPI operationId.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Domain models defined using\nPydantic are used for request and response payloads.\n\nNo routing decorators, path definitions, or implicit framework behavior\nappear in this module. All routing, HTTP methods, and schemas are defined\nin the OpenAPI specification.", "members": { - "Response": { - "name": "Response", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.Response", - "signature": "", - "docstring": null - }, - "HTTPException": { - "name": "HTTPException", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.HTTPException", - "signature": "", - "docstring": null - }, - "UploadFile": { - "name": "UploadFile", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.UploadFile", - "signature": "", - "docstring": null - }, - "StreamingResponse": { - "name": "StreamingResponse", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.StreamingResponse", - "signature": "", - "docstring": null - }, - "subscribe": { - "name": "subscribe", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.subscribe", - "signature": "", - "docstring": null - }, - "unsubscribe": { - "name": "unsubscribe", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.unsubscribe", - "signature": "", - "docstring": null - }, - "ParentCreate": { - "name": "ParentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.ParentCreate", - "signature": "", - "docstring": null - }, - "VetCreate": { - "name": "VetCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.VetCreate", - "signature": "", - "docstring": null - }, - "TreatmentCreate": { - "name": "TreatmentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.TreatmentCreate", - "signature": "", - "docstring": null - }, - "PetCreate": { - "name": "PetCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.PetCreate", - "signature": "", - "docstring": null - }, - "AppointmentCreate": { - "name": "AppointmentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.AppointmentCreate", - "signature": "", - "docstring": null - }, "list_parents": { "name": "list_parents", "kind": "function", "path": "openapi_first.templates.vet_app.routes.list_parents", - "signature": "", + "signature": "list_parents(limit: int = 20, offset: int = 0) -> dict", "docstring": "List parents (paginated).\n\nParameters\n----------\nlimit : int\n Maximum number of records to return.\noffset : int\n Number of records to skip.\n\nReturns\n-------\ndict\n Paginated response with ``total`` and ``items``." }, "create_parent": { "name": "create_parent", "kind": "function", "path": "openapi_first.templates.vet_app.routes.create_parent", - "signature": "", - "docstring": "Create a parent.\n\nParameters\n----------\npayload : ParentCreate\n Parent data excluding the ``id`` field.\n\nReturns\n-------\nParent\n The newly created parent." + "signature": "create_parent(payload: ParentCreate, response: Response) -> Parent", + "docstring": "Create a parent.\n\nParameters\n----------\npayload : ParentCreate\n Parent data excluding the ``id`` field.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nParent\n The newly created parent." }, "get_parent": { "name": "get_parent", "kind": "function", "path": "openapi_first.templates.vet_app.routes.get_parent", - "signature": "", + "signature": "get_parent(id: int) -> Parent", "docstring": "Retrieve a single parent by ID.\n\nParameters\n----------\nid : int\n Identifier of the parent.\n\nReturns\n-------\nParent\n The requested parent.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist." }, "update_parent": { "name": "update_parent", "kind": "function", "path": "openapi_first.templates.vet_app.routes.update_parent", - "signature": "", + "signature": "update_parent(id: int, payload: ParentCreate) -> Parent", "docstring": "Update an existing parent.\n\nParameters\n----------\nid : int\n Identifier of the parent.\npayload : ParentCreate\n Updated parent data.\n\nReturns\n-------\nParent\n The updated parent.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist." }, "delete_parent": { "name": "delete_parent", "kind": "function", "path": "openapi_first.templates.vet_app.routes.delete_parent", - "signature": "", - "docstring": "Delete an existing parent.\n\nParameters\n----------\nid : int\n Identifier of the parent.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist." + "signature": "delete_parent(id: int, response: Response) -> None", + "docstring": "Delete an existing parent.\n\nParameters\n----------\nid : int\n Identifier of the parent.\nresponse : Response\n Response object used to set the HTTP status code.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist." }, "list_vets": { "name": "list_vets", "kind": "function", "path": "openapi_first.templates.vet_app.routes.list_vets", - "signature": "", + "signature": "list_vets(limit: int = 20, offset: int = 0)", "docstring": "List vets (paginated)." }, "create_vet": { "name": "create_vet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.create_vet", - "signature": "", + "signature": "create_vet(payload: VetCreate, response: Response)", "docstring": "Create a vet." }, "get_vet": { "name": "get_vet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.get_vet", - "signature": "", + "signature": "get_vet(id: int)", "docstring": "Retrieve a single vet by ID." }, "update_vet": { "name": "update_vet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.update_vet", - "signature": "", + "signature": "update_vet(id: int, payload: VetCreate)", "docstring": "Update an existing vet." }, "delete_vet": { "name": "delete_vet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.delete_vet", - "signature": "", + "signature": "delete_vet(id: int, response: Response)", "docstring": "Delete an existing vet." }, "list_treatments": { "name": "list_treatments", "kind": "function", "path": "openapi_first.templates.vet_app.routes.list_treatments", - "signature": "", + "signature": "list_treatments() -> list[Treatment]", "docstring": "List treatments (catalogue).\n\nReturns\n-------\nlist[Treatment]\n A list of treatment domain objects." }, "create_treatment": { "name": "create_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.create_treatment", - "signature": "", + "signature": "create_treatment(payload: TreatmentCreate, response: Response)", "docstring": "Add a treatment (admin only)." }, "get_treatment": { "name": "get_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.get_treatment", - "signature": "", + "signature": "get_treatment(id: int)", "docstring": "Retrieve a single treatment by ID." }, "update_treatment": { "name": "update_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.update_treatment", - "signature": "", + "signature": "update_treatment(id: int, payload: TreatmentCreate)", "docstring": "Update an existing treatment." }, "delete_treatment": { "name": "delete_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.delete_treatment", - "signature": "", + "signature": "delete_treatment(id: int, response: Response)", "docstring": "Delete an existing treatment." }, "list_pets": { "name": "list_pets", "kind": "function", "path": "openapi_first.templates.vet_app.routes.list_pets", - "signature": "", + "signature": "list_pets(limit: int = 20, offset: int = 0)", "docstring": "List pets (paginated)." }, "create_pet": { "name": "create_pet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.create_pet", - "signature": "", + "signature": "create_pet(payload: PetCreate, response: Response)", "docstring": "Create a pet." }, "get_pet": { "name": "get_pet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.get_pet", - "signature": "", + "signature": "get_pet(id: int)", "docstring": "Retrieve a single pet by ID." }, "update_pet": { "name": "update_pet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.update_pet", - "signature": "", + "signature": "update_pet(id: int, payload: PetCreate)", "docstring": "Update an existing pet." }, "delete_pet": { "name": "delete_pet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.delete_pet", - "signature": "", + "signature": "delete_pet(id: int, response: Response)", "docstring": "Delete an existing pet." }, "upload_pet_photo": { "name": "upload_pet_photo", "kind": "function", "path": "openapi_first.templates.vet_app.routes.upload_pet_photo", - "signature": "", + "signature": "upload_pet_photo(id: int, file: UploadFile) -> dict", "docstring": "Upload a pet photo.\n\nParameters\n----------\nid : int\n Identifier of the pet.\nfile : UploadFile\n Image file to upload.\n\nReturns\n-------\ndict\n A confirmation with the pet ID." }, "list_appointments": { "name": "list_appointments", "kind": "function", "path": "openapi_first.templates.vet_app.routes.list_appointments", - "signature": "", + "signature": "list_appointments(limit: int = 20, offset: int = 0, date: str = None, vet: int = None, pet: int = None)", "docstring": "List appointments (paginated, filterable)." }, "create_appointment": { "name": "create_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.create_appointment", - "signature": "", + "signature": "create_appointment(payload: AppointmentCreate, response: Response)", "docstring": "Create an appointment." }, "get_appointment": { "name": "get_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.get_appointment", - "signature": "", + "signature": "get_appointment(id: int)", "docstring": "Retrieve a single appointment by ID." }, "update_appointment": { "name": "update_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.update_appointment", - "signature": "", + "signature": "update_appointment(id: int, payload: AppointmentCreate)", "docstring": "Update an existing appointment." }, "delete_appointment": { "name": "delete_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.delete_appointment", - "signature": "", + "signature": "delete_appointment(id: int, response: Response)", "docstring": "Delete an existing appointment." }, "stream_actions": { "name": "stream_actions", "kind": "function", "path": "openapi_first.templates.vet_app.routes.stream_actions", - "signature": "", + "signature": "stream_actions(id: int)", "docstring": "Stream animal actions via SSE, scoped to a pet's species." } } @@ -2051,39 +1652,18 @@ "signature": null, "docstring": null, "members": { - "asyncio": { - "name": "asyncio", - "kind": "alias", - "path": "openapi_first.templates.vet_app.sse.asyncio", - "signature": "", - "docstring": null - }, - "random": { - "name": "random", - "kind": "alias", - "path": "openapi_first.templates.vet_app.sse.random", - "signature": "", - "docstring": null - }, - "json": { - "name": "json", - "kind": "alias", - "path": "openapi_first.templates.vet_app.sse.json", - "signature": "", - "docstring": null - }, "subscribe": { "name": "subscribe", "kind": "function", "path": "openapi_first.templates.vet_app.sse.subscribe", - "signature": "", + "signature": "subscribe(pet_id: int, species: str) -> asyncio.Queue", "docstring": null }, "unsubscribe": { "name": "unsubscribe", "kind": "function", "path": "openapi_first.templates.vet_app.sse.unsubscribe", - "signature": "", + "signature": "unsubscribe(pet_id: int, q: asyncio.Queue)", "docstring": null } } @@ -2095,60 +1675,46 @@ "signature": null, "docstring": "End-to-end tests for the OpenAPI-first Veterinary Clinic example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store using Pydantic models.", "members": { - "TestClient": { - "name": "TestClient", - "kind": "alias", - "path": "openapi_first.templates.vet_app.test_vet_app.TestClient", - "signature": "", - "docstring": null - }, - "app": { - "name": "app", - "kind": "alias", - "path": "openapi_first.templates.vet_app.test_vet_app.app", - "signature": "", - "docstring": null - }, "load_openapi": { "name": "load_openapi", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.load_openapi", - "signature": "", + "signature": "load_openapi(path: str | Path)", "docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup." }, "OpenAPIClient": { "name": "OpenAPIClient", "kind": "class", "path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient", - "signature": "", - "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", + "signature": "OpenAPIClient(spec: dict[str, Any], base_url: str | None = None, client: httpx.Client | None = None)", + "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nArgs:\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification.\n base_url (str | None):\n Base URL of the target service. If omitted, the first entry in the\n OpenAPI `servers` list is used.\n client (httpx.Client | None):\n Optional preconfigured httpx client instance.\n\nRaises:\n OpenAPIClientError:\n If no servers are defined, spec has no paths, operationIds are\n missing/duplicate, or required parameters are missing.\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", "members": { "spec": { "name": "spec", "kind": "attribute", "path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.spec", - "signature": "", + "signature": null, "docstring": null }, "base_url": { "name": "base_url", "kind": "attribute", "path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.base_url", - "signature": "", + "signature": null, "docstring": null }, "client": { "name": "client", "kind": "attribute", "path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.client", - "signature": "", + "signature": null, "docstring": null }, "operations": { "name": "operations", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.operations", - "signature": "", + "signature": "operations()", "docstring": null } } @@ -2178,91 +1744,91 @@ "name": "test_list_parents", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_list_parents", - "signature": "", + "signature": "test_list_parents()", "docstring": "List parents returns paginated response." }, "test_create_parent": { "name": "test_create_parent", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_create_parent", - "signature": "", + "signature": "test_create_parent()", "docstring": "Creating a parent returns 201 with the created entity." }, "test_get_parent": { "name": "test_get_parent", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_get_parent", - "signature": "", + "signature": "test_get_parent()", "docstring": "Get parent by ID returns the entity." }, "test_update_parent": { "name": "test_update_parent", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_update_parent", - "signature": "", + "signature": "test_update_parent()", "docstring": "Update parent replaces its values." }, "test_delete_parent": { "name": "test_delete_parent", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_delete_parent", - "signature": "", + "signature": "test_delete_parent()", "docstring": "Delete parent returns 204 and removes the entity." }, "test_list_vets": { "name": "test_list_vets", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_list_vets", - "signature": "", + "signature": "test_list_vets()", "docstring": "List vets returns paginated response." }, "test_create_vet": { "name": "test_create_vet", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_create_vet", - "signature": "", + "signature": "test_create_vet()", "docstring": "Creating a vet returns 201." }, "test_list_treatments": { "name": "test_list_treatments", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_list_treatments", - "signature": "", + "signature": "test_list_treatments()", "docstring": "List treatments returns an array." }, "test_create_treatment": { "name": "test_create_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_create_treatment", - "signature": "", + "signature": "test_create_treatment()", "docstring": "Creating a treatment returns 201." }, "test_create_pet": { "name": "test_create_pet", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_create_pet", - "signature": "", + "signature": "test_create_pet()", "docstring": "Creating a pet links FK references." }, "test_upload_pet_photo": { "name": "test_upload_pet_photo", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_upload_pet_photo", - "signature": "", + "signature": "test_upload_pet_photo()", "docstring": "Upload pet photo returns 200." }, "test_list_appointments": { "name": "test_list_appointments", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_list_appointments", - "signature": "", + "signature": "test_list_appointments()", "docstring": "List appointments returns paginated response with filter params." }, "test_full_appointment_lifecycle": { "name": "test_full_appointment_lifecycle", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_full_appointment_lifecycle", - "signature": "", + "signature": "test_full_appointment_lifecycle()", "docstring": "Create a parent, vet, treatment, pet, then an appointment." } } diff --git a/openapi-first/mcp/modules/openapi_first.templates.model_app.data.json b/openapi-first/mcp/modules/openapi_first.templates.model_app.data.json index cb91536..faf7db1 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.model_app.data.json +++ b/openapi-first/mcp/modules/openapi_first.templates.model_app.data.json @@ -4,60 +4,39 @@ "path": "openapi_first.templates.model_app.data", "docstring": "In-memory data store using Pydantic models.\n\nThis module is NOT thread-safe and is intended for demos and scaffolds only.\nThis module provides a minimal, process-local data store for the\nmodel-based CRUD example application. It stores and returns domain\nobjects defined using Pydantic models and is intended solely for\ndemonstration and scaffolding purposes.\n\nThe implementation intentionally avoids:\n- persistence\n- concurrency guarantees\n- transactional semantics\n- validation beyond what Pydantic provides\n\nIt is not part of the ``openapi_first`` library API surface.", "objects": { - "Dict": { - "name": "Dict", - "kind": "alias", - "path": "openapi_first.templates.model_app.data.Dict", - "signature": "", - "docstring": null - }, - "Item": { - "name": "Item", - "kind": "alias", - "path": "openapi_first.templates.model_app.data.Item", - "signature": "", - "docstring": null - }, - "ItemCreate": { - "name": "ItemCreate", - "kind": "alias", - "path": "openapi_first.templates.model_app.data.ItemCreate", - "signature": "", - "docstring": null - }, "list_items": { "name": "list_items", "kind": "function", "path": "openapi_first.templates.model_app.data.list_items", - "signature": "", + "signature": "list_items() -> list[Item]", "docstring": "Return all items in the data store.\n\nReturns\n-------\nlist[Item]\n A list of item domain objects." }, "get_item": { "name": "get_item", "kind": "function", "path": "openapi_first.templates.model_app.data.get_item", - "signature": "", + "signature": "get_item(item_id: int) -> Item", "docstring": "Retrieve a single item by ID.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\nItem\n The requested item.\n\nRaises\n------\nKeyError\n If the item does not exist." }, "create_item": { "name": "create_item", "kind": "function", "path": "openapi_first.templates.model_app.data.create_item", - "signature": "", + "signature": "create_item(payload: ItemCreate) -> Item", "docstring": "Create a new item in the data store.\n\nA new identifier is assigned automatically. No additional validation\nis performed beyond Pydantic model validation.\n\nParameters\n----------\npayload : ItemCreate\n Data required to create a new item.\n\nReturns\n-------\nItem\n The newly created item." }, "update_item": { "name": "update_item", "kind": "function", "path": "openapi_first.templates.model_app.data.update_item", - "signature": "", + "signature": "update_item(item_id: int, payload: ItemCreate) -> Item", "docstring": "Replace an existing item in the data store.\n\nThis function performs a full replacement of the stored item.\nPartial updates are not supported.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : ItemCreate\n New item data.\n\nReturns\n-------\nItem\n The updated item.\n\nRaises\n------\nKeyError\n If the item does not exist." }, "delete_item": { "name": "delete_item", "kind": "function", "path": "openapi_first.templates.model_app.data.delete_item", - "signature": "", + "signature": "delete_item(item_id: int) -> None", "docstring": "Remove an item from the data store.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\n\nRaises\n------\nKeyError\n If the item does not exist." } } diff --git a/openapi-first/mcp/modules/openapi_first.templates.model_app.json b/openapi-first/mcp/modules/openapi_first.templates.model_app.json index 156663b..38bc071 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.model_app.json +++ b/openapi-first/mcp/modules/openapi_first.templates.model_app.json @@ -2,7 +2,7 @@ "module": "openapi_first.templates.model_app", "content": { "path": "openapi_first.templates.model_app", - "docstring": "OpenAPI-first model-based CRUD application template.\n\nThis package contains a complete, minimal example of an OpenAPI-first\nCRUD service that uses explicit Pydantic domain models for request and\nresponse schemas.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace implementing CRUD operations (``routes``)\n- Pydantic domain models (``models``)\n- an in-memory mock data store (``data``)\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification and enforced at application startup.\nNo decorator-driven routing or implicit framework behavior is used.\n\nThis template demonstrates:\n- operationId-driven server-side route binding\n- explicit request and response modeling with Pydantic\n- explicit HTTP status code control in handlers\n- operationId-driven client usage against the same OpenAPI contract\n- end-to-end validation using in-memory data and tests\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new model-based CRUD example service using the bundled template:\n\n openapi-first model_app\n\nCreate the service in a custom directory:\n\n openapi-first model_app my-model-service\n\nList all available application templates:\n\n openapi-first --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client calls for model-based CRUD operations:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List items\n response = client.list_items()\n\n # Get item by ID\n response = client.get_item(\n path_params={\"item_id\": 1}\n )\n\n # Create item\n response = client.create_item(\n body={\"name\": \"Orange\", \"price\": 0.8}\n )\n\n # Update item\n response = client.update_item(\n path_params={\"item_id\": 1},\n body={\"name\": \"Green Apple\", \"price\": 0.6},\n )\n\n # Delete item\n response = client.delete_item(\n path_params={\"item_id\": 1}\n )\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Request and response payloads conform to Pydantic models\n- Invalid or incomplete OpenAPI specs fail at client construction time\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.", + "docstring": "OpenAPI-first model-based CRUD application template.\n\nThis package contains a complete, minimal example of an OpenAPI-first\nCRUD service that uses explicit Pydantic domain models for request and\nresponse schemas.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace implementing CRUD operations (``routes``)\n- Pydantic domain models (``models``)\n- an in-memory mock data store (``data``)\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification and enforced at application startup.\nNo decorator-driven routing or implicit framework behavior is used.\n\nThis template demonstrates:\n- operationId-driven server-side route binding\n- explicit request and response modeling with Pydantic\n- explicit HTTP status code control in handlers\n- operationId-driven client usage against the same OpenAPI contract\n- end-to-end validation using in-memory data and tests\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new model-based CRUD example service using the bundled template:\n\n openapi-first scaffold model_app\n\nCreate the service in a custom directory:\n\n openapi-first scaffold model_app my-model-service\n\nList all available application templates:\n\n openapi-first scaffold --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client calls for model-based CRUD operations:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List items\n response = client.list_items()\n\n # Get item by ID\n response = client.get_item(\n path_params={\"item_id\": 1}\n )\n\n # Create item\n response = client.create_item(\n body={\"name\": \"Orange\", \"price\": 0.8}\n )\n\n # Update item\n response = client.update_item(\n path_params={\"item_id\": 1},\n body={\"name\": \"Green Apple\", \"price\": 0.6},\n )\n\n # Delete item\n response = client.delete_item(\n path_params={\"item_id\": 1}\n )\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Request and response payloads conform to Pydantic models\n- Invalid or incomplete OpenAPI specs fail at client construction time\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.", "objects": { "data": { "name": "data", @@ -11,60 +11,39 @@ "signature": null, "docstring": "In-memory data store using Pydantic models.\n\nThis module is NOT thread-safe and is intended for demos and scaffolds only.\nThis module provides a minimal, process-local data store for the\nmodel-based CRUD example application. It stores and returns domain\nobjects defined using Pydantic models and is intended solely for\ndemonstration and scaffolding purposes.\n\nThe implementation intentionally avoids:\n- persistence\n- concurrency guarantees\n- transactional semantics\n- validation beyond what Pydantic provides\n\nIt is not part of the ``openapi_first`` library API surface.", "members": { - "Dict": { - "name": "Dict", - "kind": "alias", - "path": "openapi_first.templates.model_app.data.Dict", - "signature": "", - "docstring": null - }, - "Item": { - "name": "Item", - "kind": "alias", - "path": "openapi_first.templates.model_app.data.Item", - "signature": "", - "docstring": null - }, - "ItemCreate": { - "name": "ItemCreate", - "kind": "alias", - "path": "openapi_first.templates.model_app.data.ItemCreate", - "signature": "", - "docstring": null - }, "list_items": { "name": "list_items", "kind": "function", "path": "openapi_first.templates.model_app.data.list_items", - "signature": "", + "signature": "list_items() -> list[Item]", "docstring": "Return all items in the data store.\n\nReturns\n-------\nlist[Item]\n A list of item domain objects." }, "get_item": { "name": "get_item", "kind": "function", "path": "openapi_first.templates.model_app.data.get_item", - "signature": "", + "signature": "get_item(item_id: int) -> Item", "docstring": "Retrieve a single item by ID.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\nItem\n The requested item.\n\nRaises\n------\nKeyError\n If the item does not exist." }, "create_item": { "name": "create_item", "kind": "function", "path": "openapi_first.templates.model_app.data.create_item", - "signature": "", + "signature": "create_item(payload: ItemCreate) -> Item", "docstring": "Create a new item in the data store.\n\nA new identifier is assigned automatically. No additional validation\nis performed beyond Pydantic model validation.\n\nParameters\n----------\npayload : ItemCreate\n Data required to create a new item.\n\nReturns\n-------\nItem\n The newly created item." }, "update_item": { "name": "update_item", "kind": "function", "path": "openapi_first.templates.model_app.data.update_item", - "signature": "", + "signature": "update_item(item_id: int, payload: ItemCreate) -> Item", "docstring": "Replace an existing item in the data store.\n\nThis function performs a full replacement of the stored item.\nPartial updates are not supported.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : ItemCreate\n New item data.\n\nReturns\n-------\nItem\n The updated item.\n\nRaises\n------\nKeyError\n If the item does not exist." }, "delete_item": { "name": "delete_item", "kind": "function", "path": "openapi_first.templates.model_app.data.delete_item", - "signature": "", + "signature": "delete_item(item_id: int) -> None", "docstring": "Remove an item from the data store.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\n\nRaises\n------\nKeyError\n If the item does not exist." } } @@ -80,25 +59,18 @@ "name": "OpenAPIFirstApp", "kind": "class", "path": "openapi_first.templates.model_app.main.OpenAPIFirstApp", - "signature": "", - "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", + "signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)", + "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", "members": { "openapi": { "name": "openapi", "kind": "attribute", "path": "openapi_first.templates.model_app.main.OpenAPIFirstApp.openapi", - "signature": "", + "signature": null, "docstring": null } } }, - "routes": { - "name": "routes", - "kind": "alias", - "path": "openapi_first.templates.model_app.main.routes", - "signature": "", - "docstring": null - }, "app": { "name": "app", "kind": "attribute", @@ -115,18 +87,11 @@ "signature": null, "docstring": "Pydantic domain models for the CRUD example.\n\nThis module defines Pydantic models that represent the domain entities\nused by the service. These models are referenced by the OpenAPI\nspecification for request and response schemas.\n\nThe models are declarative and framework-agnostic. They contain no\npersistence logic, validation beyond type constraints, or business\nbehavior.\n\nThis module is not part of the ``openapi_first`` library API surface.\nIt exists solely to support the example application template.", "members": { - "BaseModel": { - "name": "BaseModel", - "kind": "alias", - "path": "openapi_first.templates.model_app.models.BaseModel", - "signature": "", - "docstring": null - }, "ItemBase": { "name": "ItemBase", "kind": "class", "path": "openapi_first.templates.model_app.models.ItemBase", - "signature": "", + "signature": null, "docstring": "Base domain model for an item.\n\nDefines fields common to all item representations.", "members": { "name": { @@ -149,14 +114,14 @@ "name": "ItemCreate", "kind": "class", "path": "openapi_first.templates.model_app.models.ItemCreate", - "signature": "", + "signature": null, "docstring": "Domain model for item creation requests.\n\nThis model is used for request bodies when creating new items.\nIt intentionally excludes the ``id`` field, which is assigned\nby the service." }, "Item": { "name": "Item", "kind": "class", "path": "openapi_first.templates.model_app.models.Item", - "signature": "", + "signature": null, "docstring": "Domain model for a persisted item.\n\nThis model represents the full item state returned in responses,\nincluding the server-assigned identifier.", "members": { "id": { @@ -177,61 +142,40 @@ "signature": null, "docstring": "CRUD route handlers bound via OpenAPI operationId.\n\nThis module defines OpenAPI-bound operation handlers for a model-based\nCRUD service. Functions in this module are bound to HTTP routes\nexclusively via OpenAPI ``operationId`` values.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Domain models defined using\nPydantic are used for request and response payloads.\n\nNo routing decorators, path definitions, or implicit framework behavior\nappear in this module. All routing, HTTP methods, and schemas are defined\nin the OpenAPI specification.", "members": { - "Response": { - "name": "Response", - "kind": "alias", - "path": "openapi_first.templates.model_app.routes.Response", - "signature": "", - "docstring": null - }, - "HTTPException": { - "name": "HTTPException", - "kind": "alias", - "path": "openapi_first.templates.model_app.routes.HTTPException", - "signature": "", - "docstring": null - }, - "ItemCreate": { - "name": "ItemCreate", - "kind": "alias", - "path": "openapi_first.templates.model_app.routes.ItemCreate", - "signature": "", - "docstring": null - }, "list_items": { "name": "list_items", "kind": "function", "path": "openapi_first.templates.model_app.routes.list_items", - "signature": "", + "signature": "list_items() -> list[Item]", "docstring": "List all items.\n\nImplements the OpenAPI operation identified by\n``operationId: list_items``.\n\nReturns\n-------\nlist[Item]\n A list of item domain objects." }, "get_item": { "name": "get_item", "kind": "function", "path": "openapi_first.templates.model_app.routes.get_item", - "signature": "", + "signature": "get_item(item_id: int) -> Item", "docstring": "Retrieve a single item by ID.\n\nImplements the OpenAPI operation identified by\n``operationId: get_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\nItem\n The requested item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." }, "create_item": { "name": "create_item", "kind": "function", "path": "openapi_first.templates.model_app.routes.create_item", - "signature": "", - "docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : ItemCreate\n Request body describing the item to create.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nItem\n The newly created item." + "signature": "create_item(payload: ItemCreate, response: Response) -> Item", + "docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : ItemCreate\n Request body describing the item to create.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nItem\n The newly created item." }, "update_item": { "name": "update_item", "kind": "function", "path": "openapi_first.templates.model_app.routes.update_item", - "signature": "", + "signature": "update_item(item_id: int, payload: ItemCreate) -> Item", "docstring": "Update an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: update_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : ItemCreate\n New item data.\n\nReturns\n-------\nItem\n The updated item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." }, "delete_item": { "name": "delete_item", "kind": "function", "path": "openapi_first.templates.model_app.routes.delete_item", - "signature": "", - "docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." + "signature": "delete_item(item_id: int, response: Response) -> None", + "docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." } } }, @@ -242,60 +186,46 @@ "signature": null, "docstring": "End-to-end tests for the OpenAPI-first model CRUD example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store using Pydantic models.\n- OpenAPI specification loading\n- OperationId-driven route binding on the server\n- OperationId-driven client invocation\n- Pydantic model-based request and response handling\n\nAll CRUD operations are exercised against an in-memory mock data store\nbacked by Pydantic domain models.\n\nThe tests assume:\n- OpenAPI-first route binding\n- Pydantic model validation\n- In-memory storage (no persistence guarantees)\n- Deterministic behavior in a single process", "members": { - "TestClient": { - "name": "TestClient", - "kind": "alias", - "path": "openapi_first.templates.model_app.test_model_app.TestClient", - "signature": "", - "docstring": null - }, - "app": { - "name": "app", - "kind": "alias", - "path": "openapi_first.templates.model_app.test_model_app.app", - "signature": "", - "docstring": null - }, "load_openapi": { "name": "load_openapi", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.load_openapi", - "signature": "", + "signature": "load_openapi(path: str | Path)", "docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup." }, "OpenAPIClient": { "name": "OpenAPIClient", "kind": "class", "path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient", - "signature": "", - "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", + "signature": "OpenAPIClient(spec: dict[str, Any], base_url: str | None = None, client: httpx.Client | None = None)", + "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nArgs:\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification.\n base_url (str | None):\n Base URL of the target service. If omitted, the first entry in the\n OpenAPI `servers` list is used.\n client (httpx.Client | None):\n Optional preconfigured httpx client instance.\n\nRaises:\n OpenAPIClientError:\n If no servers are defined, spec has no paths, operationIds are\n missing/duplicate, or required parameters are missing.\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", "members": { "spec": { "name": "spec", "kind": "attribute", "path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.spec", - "signature": "", + "signature": null, "docstring": null }, "base_url": { "name": "base_url", "kind": "attribute", "path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.base_url", - "signature": "", + "signature": null, "docstring": null }, "client": { "name": "client", "kind": "attribute", "path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.client", - "signature": "", + "signature": null, "docstring": null }, "operations": { "name": "operations", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.operations", - "signature": "", + "signature": "operations()", "docstring": null } } @@ -318,35 +248,35 @@ "name": "test_list_items_initial", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.test_list_items_initial", - "signature": "", + "signature": "test_list_items_initial()", "docstring": "Initial items should be present." }, "test_get_item": { "name": "test_get_item", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.test_get_item", - "signature": "", + "signature": "test_get_item()", "docstring": "Existing item should be retrievable by ID." }, "test_create_item": { "name": "test_create_item", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.test_create_item", - "signature": "", + "signature": "test_create_item()", "docstring": "Creating a new item should return the created entity." }, "test_update_item": { "name": "test_update_item", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.test_update_item", - "signature": "", + "signature": "test_update_item()", "docstring": "Updating an item should replace its values." }, "test_delete_item": { "name": "test_delete_item", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.test_delete_item", - "signature": "", + "signature": "test_delete_item()", "docstring": "Deleting an item should remove it from the store." } } diff --git a/openapi-first/mcp/modules/openapi_first.templates.model_app.main.json b/openapi-first/mcp/modules/openapi_first.templates.model_app.main.json index 0c438db..cf7e886 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.model_app.main.json +++ b/openapi-first/mcp/modules/openapi_first.templates.model_app.main.json @@ -8,25 +8,18 @@ "name": "OpenAPIFirstApp", "kind": "class", "path": "openapi_first.templates.model_app.main.OpenAPIFirstApp", - "signature": "", - "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", + "signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)", + "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", "members": { "openapi": { "name": "openapi", "kind": "attribute", "path": "openapi_first.templates.model_app.main.OpenAPIFirstApp.openapi", - "signature": "", + "signature": null, "docstring": null } } }, - "routes": { - "name": "routes", - "kind": "alias", - "path": "openapi_first.templates.model_app.main.routes", - "signature": "", - "docstring": null - }, "app": { "name": "app", "kind": "attribute", diff --git a/openapi-first/mcp/modules/openapi_first.templates.model_app.models.json b/openapi-first/mcp/modules/openapi_first.templates.model_app.models.json index c01446c..99469e2 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.model_app.models.json +++ b/openapi-first/mcp/modules/openapi_first.templates.model_app.models.json @@ -4,18 +4,11 @@ "path": "openapi_first.templates.model_app.models", "docstring": "Pydantic domain models for the CRUD example.\n\nThis module defines Pydantic models that represent the domain entities\nused by the service. These models are referenced by the OpenAPI\nspecification for request and response schemas.\n\nThe models are declarative and framework-agnostic. They contain no\npersistence logic, validation beyond type constraints, or business\nbehavior.\n\nThis module is not part of the ``openapi_first`` library API surface.\nIt exists solely to support the example application template.", "objects": { - "BaseModel": { - "name": "BaseModel", - "kind": "alias", - "path": "openapi_first.templates.model_app.models.BaseModel", - "signature": "", - "docstring": null - }, "ItemBase": { "name": "ItemBase", "kind": "class", "path": "openapi_first.templates.model_app.models.ItemBase", - "signature": "", + "signature": null, "docstring": "Base domain model for an item.\n\nDefines fields common to all item representations.", "members": { "name": { @@ -38,14 +31,14 @@ "name": "ItemCreate", "kind": "class", "path": "openapi_first.templates.model_app.models.ItemCreate", - "signature": "", + "signature": null, "docstring": "Domain model for item creation requests.\n\nThis model is used for request bodies when creating new items.\nIt intentionally excludes the ``id`` field, which is assigned\nby the service." }, "Item": { "name": "Item", "kind": "class", "path": "openapi_first.templates.model_app.models.Item", - "signature": "", + "signature": null, "docstring": "Domain model for a persisted item.\n\nThis model represents the full item state returned in responses,\nincluding the server-assigned identifier.", "members": { "id": { diff --git a/openapi-first/mcp/modules/openapi_first.templates.model_app.routes.json b/openapi-first/mcp/modules/openapi_first.templates.model_app.routes.json index ccc7d74..4200243 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.model_app.routes.json +++ b/openapi-first/mcp/modules/openapi_first.templates.model_app.routes.json @@ -4,61 +4,40 @@ "path": "openapi_first.templates.model_app.routes", "docstring": "CRUD route handlers bound via OpenAPI operationId.\n\nThis module defines OpenAPI-bound operation handlers for a model-based\nCRUD service. Functions in this module are bound to HTTP routes\nexclusively via OpenAPI ``operationId`` values.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Domain models defined using\nPydantic are used for request and response payloads.\n\nNo routing decorators, path definitions, or implicit framework behavior\nappear in this module. All routing, HTTP methods, and schemas are defined\nin the OpenAPI specification.", "objects": { - "Response": { - "name": "Response", - "kind": "alias", - "path": "openapi_first.templates.model_app.routes.Response", - "signature": "", - "docstring": null - }, - "HTTPException": { - "name": "HTTPException", - "kind": "alias", - "path": "openapi_first.templates.model_app.routes.HTTPException", - "signature": "", - "docstring": null - }, - "ItemCreate": { - "name": "ItemCreate", - "kind": "alias", - "path": "openapi_first.templates.model_app.routes.ItemCreate", - "signature": "", - "docstring": null - }, "list_items": { "name": "list_items", "kind": "function", "path": "openapi_first.templates.model_app.routes.list_items", - "signature": "", + "signature": "list_items() -> list[Item]", "docstring": "List all items.\n\nImplements the OpenAPI operation identified by\n``operationId: list_items``.\n\nReturns\n-------\nlist[Item]\n A list of item domain objects." }, "get_item": { "name": "get_item", "kind": "function", "path": "openapi_first.templates.model_app.routes.get_item", - "signature": "", + "signature": "get_item(item_id: int) -> Item", "docstring": "Retrieve a single item by ID.\n\nImplements the OpenAPI operation identified by\n``operationId: get_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\nItem\n The requested item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." }, "create_item": { "name": "create_item", "kind": "function", "path": "openapi_first.templates.model_app.routes.create_item", - "signature": "", - "docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : ItemCreate\n Request body describing the item to create.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nItem\n The newly created item." + "signature": "create_item(payload: ItemCreate, response: Response) -> Item", + "docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : ItemCreate\n Request body describing the item to create.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nItem\n The newly created item." }, "update_item": { "name": "update_item", "kind": "function", "path": "openapi_first.templates.model_app.routes.update_item", - "signature": "", + "signature": "update_item(item_id: int, payload: ItemCreate) -> Item", "docstring": "Update an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: update_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : ItemCreate\n New item data.\n\nReturns\n-------\nItem\n The updated item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." }, "delete_item": { "name": "delete_item", "kind": "function", "path": "openapi_first.templates.model_app.routes.delete_item", - "signature": "", - "docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." + "signature": "delete_item(item_id: int, response: Response) -> None", + "docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist." } } } diff --git a/openapi-first/mcp/modules/openapi_first.templates.model_app.test_model_app.json b/openapi-first/mcp/modules/openapi_first.templates.model_app.test_model_app.json index 5dcf998..8e48679 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.model_app.test_model_app.json +++ b/openapi-first/mcp/modules/openapi_first.templates.model_app.test_model_app.json @@ -4,60 +4,46 @@ "path": "openapi_first.templates.model_app.test_model_app", "docstring": "End-to-end tests for the OpenAPI-first model CRUD example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store using Pydantic models.\n- OpenAPI specification loading\n- OperationId-driven route binding on the server\n- OperationId-driven client invocation\n- Pydantic model-based request and response handling\n\nAll CRUD operations are exercised against an in-memory mock data store\nbacked by Pydantic domain models.\n\nThe tests assume:\n- OpenAPI-first route binding\n- Pydantic model validation\n- In-memory storage (no persistence guarantees)\n- Deterministic behavior in a single process", "objects": { - "TestClient": { - "name": "TestClient", - "kind": "alias", - "path": "openapi_first.templates.model_app.test_model_app.TestClient", - "signature": "", - "docstring": null - }, - "app": { - "name": "app", - "kind": "alias", - "path": "openapi_first.templates.model_app.test_model_app.app", - "signature": "", - "docstring": null - }, "load_openapi": { "name": "load_openapi", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.load_openapi", - "signature": "", + "signature": "load_openapi(path: str | Path)", "docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup." }, "OpenAPIClient": { "name": "OpenAPIClient", "kind": "class", "path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient", - "signature": "", - "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", + "signature": "OpenAPIClient(spec: dict[str, Any], base_url: str | None = None, client: httpx.Client | None = None)", + "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nArgs:\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification.\n base_url (str | None):\n Base URL of the target service. If omitted, the first entry in the\n OpenAPI `servers` list is used.\n client (httpx.Client | None):\n Optional preconfigured httpx client instance.\n\nRaises:\n OpenAPIClientError:\n If no servers are defined, spec has no paths, operationIds are\n missing/duplicate, or required parameters are missing.\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", "members": { "spec": { "name": "spec", "kind": "attribute", "path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.spec", - "signature": "", + "signature": null, "docstring": null }, "base_url": { "name": "base_url", "kind": "attribute", "path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.base_url", - "signature": "", + "signature": null, "docstring": null }, "client": { "name": "client", "kind": "attribute", "path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.client", - "signature": "", + "signature": null, "docstring": null }, "operations": { "name": "operations", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.operations", - "signature": "", + "signature": "operations()", "docstring": null } } @@ -80,35 +66,35 @@ "name": "test_list_items_initial", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.test_list_items_initial", - "signature": "", + "signature": "test_list_items_initial()", "docstring": "Initial items should be present." }, "test_get_item": { "name": "test_get_item", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.test_get_item", - "signature": "", + "signature": "test_get_item()", "docstring": "Existing item should be retrievable by ID." }, "test_create_item": { "name": "test_create_item", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.test_create_item", - "signature": "", + "signature": "test_create_item()", "docstring": "Creating a new item should return the created entity." }, "test_update_item": { "name": "test_update_item", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.test_update_item", - "signature": "", + "signature": "test_update_item()", "docstring": "Updating an item should replace its values." }, "test_delete_item": { "name": "test_delete_item", "kind": "function", "path": "openapi_first.templates.model_app.test_model_app.test_delete_item", - "signature": "", + "signature": "test_delete_item()", "docstring": "Deleting an item should remove it from the store." } } diff --git a/openapi-first/mcp/modules/openapi_first.templates.vet_app.data.json b/openapi-first/mcp/modules/openapi_first.templates.vet_app.data.json index bc4563b..3600a44 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.vet_app.data.json +++ b/openapi-first/mcp/modules/openapi_first.templates.vet_app.data.json @@ -4,319 +4,179 @@ "path": "openapi_first.templates.vet_app.data", "docstring": "In-memory data store for the Veterinary Clinic example.\n\nThis module is NOT thread-safe and is intended for demos and scaffolds only.\n\nIt provides minimal, process-local data stores for the five veterinary\nclinic entities. Each store exposes standard CRUD operations backed by\na simple dictionary.\n\nThis module intentionally avoids:\n- persistence\n- concurrency guarantees\n- transactional semantics\n- validation beyond what Pydantic provides\n\nThis module is not part of the ``openapi_first`` library API surface.", "objects": { - "date": { - "name": "date", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.date", - "signature": "", - "docstring": null - }, - "datetime": { - "name": "datetime", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.datetime", - "signature": "", - "docstring": null - }, - "timezone": { - "name": "timezone", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.timezone", - "signature": "", - "docstring": null - }, - "Parent": { - "name": "Parent", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Parent", - "signature": "", - "docstring": null - }, - "ParentCreate": { - "name": "ParentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.ParentCreate", - "signature": "", - "docstring": null - }, - "Vet": { - "name": "Vet", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Vet", - "signature": "", - "docstring": null - }, - "VetCreate": { - "name": "VetCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.VetCreate", - "signature": "", - "docstring": null - }, - "Treatment": { - "name": "Treatment", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Treatment", - "signature": "", - "docstring": null - }, - "TreatmentCreate": { - "name": "TreatmentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.TreatmentCreate", - "signature": "", - "docstring": null - }, - "Procedure": { - "name": "Procedure", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Procedure", - "signature": "", - "docstring": null - }, - "BasicNote": { - "name": "BasicNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.BasicNote", - "signature": "", - "docstring": null - }, - "HeartRateNote": { - "name": "HeartRateNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.HeartRateNote", - "signature": "", - "docstring": null - }, - "DentalNote": { - "name": "DentalNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.DentalNote", - "signature": "", - "docstring": null - }, - "VaccineNote": { - "name": "VaccineNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.VaccineNote", - "signature": "", - "docstring": null - }, - "PreOpNote": { - "name": "PreOpNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.PreOpNote", - "signature": "", - "docstring": null - }, - "SurgeryNote": { - "name": "SurgeryNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.SurgeryNote", - "signature": "", - "docstring": null - }, - "Pet": { - "name": "Pet", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Pet", - "signature": "", - "docstring": null - }, - "PetCreate": { - "name": "PetCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.PetCreate", - "signature": "", - "docstring": null - }, - "Appointment": { - "name": "Appointment", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Appointment", - "signature": "", - "docstring": null - }, - "AppointmentCreate": { - "name": "AppointmentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.AppointmentCreate", - "signature": "", - "docstring": null - }, "list_parents": { "name": "list_parents", "kind": "function", "path": "openapi_first.templates.vet_app.data.list_parents", - "signature": "", + "signature": "list_parents() -> list[Parent]", "docstring": null }, "get_parent": { "name": "get_parent", "kind": "function", "path": "openapi_first.templates.vet_app.data.get_parent", - "signature": "", + "signature": "get_parent(parent_id: int) -> Parent", "docstring": null }, "create_parent": { "name": "create_parent", "kind": "function", "path": "openapi_first.templates.vet_app.data.create_parent", - "signature": "", + "signature": "create_parent(payload: ParentCreate) -> Parent", "docstring": null }, "update_parent": { "name": "update_parent", "kind": "function", "path": "openapi_first.templates.vet_app.data.update_parent", - "signature": "", + "signature": "update_parent(parent_id: int, payload: ParentCreate) -> Parent", "docstring": null }, "delete_parent": { "name": "delete_parent", "kind": "function", "path": "openapi_first.templates.vet_app.data.delete_parent", - "signature": "", + "signature": "delete_parent(parent_id: int) -> None", "docstring": null }, "list_vets": { "name": "list_vets", "kind": "function", "path": "openapi_first.templates.vet_app.data.list_vets", - "signature": "", + "signature": "list_vets() -> list[Vet]", "docstring": null }, "get_vet": { "name": "get_vet", "kind": "function", "path": "openapi_first.templates.vet_app.data.get_vet", - "signature": "", + "signature": "get_vet(vet_id: int) -> Vet", "docstring": null }, "create_vet": { "name": "create_vet", "kind": "function", "path": "openapi_first.templates.vet_app.data.create_vet", - "signature": "", + "signature": "create_vet(payload: VetCreate) -> Vet", "docstring": null }, "update_vet": { "name": "update_vet", "kind": "function", "path": "openapi_first.templates.vet_app.data.update_vet", - "signature": "", + "signature": "update_vet(vet_id: int, payload: VetCreate) -> Vet", "docstring": null }, "delete_vet": { "name": "delete_vet", "kind": "function", "path": "openapi_first.templates.vet_app.data.delete_vet", - "signature": "", + "signature": "delete_vet(vet_id: int) -> None", "docstring": null }, "list_treatments": { "name": "list_treatments", "kind": "function", "path": "openapi_first.templates.vet_app.data.list_treatments", - "signature": "", + "signature": "list_treatments() -> list[Treatment]", "docstring": null }, "get_treatment": { "name": "get_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.data.get_treatment", - "signature": "", + "signature": "get_treatment(treatment_id: int) -> Treatment", "docstring": null }, "create_treatment": { "name": "create_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.data.create_treatment", - "signature": "", + "signature": "create_treatment(payload: TreatmentCreate) -> Treatment", "docstring": null }, "update_treatment": { "name": "update_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.data.update_treatment", - "signature": "", + "signature": "update_treatment(treatment_id: int, payload: TreatmentCreate) -> Treatment", "docstring": null }, "delete_treatment": { "name": "delete_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.data.delete_treatment", - "signature": "", + "signature": "delete_treatment(treatment_id: int) -> None", "docstring": null }, "list_pets": { "name": "list_pets", "kind": "function", "path": "openapi_first.templates.vet_app.data.list_pets", - "signature": "", + "signature": "list_pets() -> list[Pet]", "docstring": null }, "get_pet": { "name": "get_pet", "kind": "function", "path": "openapi_first.templates.vet_app.data.get_pet", - "signature": "", + "signature": "get_pet(pet_id: int) -> Pet", "docstring": null }, "create_pet": { "name": "create_pet", "kind": "function", "path": "openapi_first.templates.vet_app.data.create_pet", - "signature": "", + "signature": "create_pet(payload: PetCreate) -> Pet", "docstring": null }, "update_pet": { "name": "update_pet", "kind": "function", "path": "openapi_first.templates.vet_app.data.update_pet", - "signature": "", + "signature": "update_pet(pet_id: int, payload: PetCreate) -> Pet", "docstring": null }, "delete_pet": { "name": "delete_pet", "kind": "function", "path": "openapi_first.templates.vet_app.data.delete_pet", - "signature": "", + "signature": "delete_pet(pet_id: int) -> None", "docstring": null }, "list_appointments": { "name": "list_appointments", "kind": "function", "path": "openapi_first.templates.vet_app.data.list_appointments", - "signature": "", + "signature": "list_appointments() -> list[Appointment]", "docstring": null }, "get_appointment": { "name": "get_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.data.get_appointment", - "signature": "", + "signature": "get_appointment(appointment_id: int) -> Appointment", "docstring": null }, "create_appointment": { "name": "create_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.data.create_appointment", - "signature": "", + "signature": "create_appointment(payload: AppointmentCreate) -> Appointment", "docstring": null }, "update_appointment": { "name": "update_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.data.update_appointment", - "signature": "", + "signature": "update_appointment(appointment_id: int, payload: AppointmentCreate) -> Appointment", "docstring": null }, "delete_appointment": { "name": "delete_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.data.delete_appointment", - "signature": "", + "signature": "delete_appointment(appointment_id: int) -> None", "docstring": null } } diff --git a/openapi-first/mcp/modules/openapi_first.templates.vet_app.json b/openapi-first/mcp/modules/openapi_first.templates.vet_app.json index 656100e..07b853e 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.vet_app.json +++ b/openapi-first/mcp/modules/openapi_first.templates.vet_app.json @@ -2,7 +2,7 @@ "module": "openapi_first.templates.vet_app", "content": { "path": "openapi_first.templates.vet_app", - "docstring": "OpenAPI-first Veterinary Clinic application template.\n\nThis package contains a complete, runnable example of an OpenAPI-first\nveterinary clinic management service. It demonstrates all ``x-`` extension\nfields consumed by the ``react-openapi`` admin panel renderer.\n\nThe application manages five resources:\n\n- **Parents** — pet owners with contact details\n- **Vets** — veterinarians with specializations\n- **Treatments** — medical procedure catalog\n- **Pets** — animals with species, age, weight, and photos\n- **Appointments** — scheduled visits linking pets, vets, and treatments\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification (``openapi.yaml``). Every operation has an\nexplicit ``operationId`` that maps to a Python handler in ``routes.py``.\n\nThis file is a copyable template. It is not part of the ``openapi_first``\nlibrary API surface.\n\n----------------------------------------------------------------------\nOpenAPI x- extension fields demonstrated\n----------------------------------------------------------------------\n\nSchema-level extensions (display metadata for resource endpoints):\n\n ``x-primary-key`` (REQUIRED) Primary key property name\n ``x-display-format`` (REQUIRED) Human-readable label template\n ``x-list-columns`` (REQUIRED) Columns for the datatable\n\nProperty-level extensions (control UI rendering):\n\n ``x-label`` (REQUIRED) Human-readable field label\n ``x-order`` (REQUIRED) Field ordering in forms/detail\n ``x-description`` (optional) Helper text below form fields\n ``x-hidden`` (optional) Visibility in form / list / detail\n ``x-filterable`` (optional) Allows column filtering\n ``x-sortable`` (optional) Allows column sorting\n ``x-fk`` (optional) Foreign key — renders as dropdown\n ``x-fk.resource`` (REQUIRED for FK) Target resource name\n ``x-fk.prefetch`` (optional) Preload all FK options on mount\n ``x-ui-type`` (optional) Custom UI type (e.g. image upload)\n ``x-upload-url`` (optional) Upload endpoint for binary fields\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new vet clinic service using the bundled template:\n\n openapi-first vet_app\n\nCreate the service in a custom directory:\n\n openapi-first vet_app my-vet-clinic\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List pets with pagination\n response = client.list_pets(query_params={\"limit\": 10, \"offset\": 0})\n\n # Create a pet with FK references\n response = client.create_pet(\n body={\"name\": \"Fido\", \"species\": \"dog\", \"parents\": [1, 2]}\n )\n\n # Upload a pet photo\n response = client.upload_pet_photo(\n path_params={\"id\": 1},\n body={\"file\": open(\"photo.jpg\", \"rb\")},\n )\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.", + "docstring": "OpenAPI-first Veterinary Clinic application template.\n\nThis package contains a complete, runnable example of an OpenAPI-first\nveterinary clinic management service. It demonstrates all ``x-`` extension\nfields consumed by the ``react-openapi`` admin panel renderer.\n\nThe application manages five resources:\n\n- **Parents** — pet owners with contact details\n- **Vets** — veterinarians with specializations\n- **Treatments** — medical procedure catalog\n- **Pets** — animals with species, age, weight, and photos\n- **Appointments** — scheduled visits linking pets, vets, and treatments\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification (``openapi.yaml``). Every operation has an\nexplicit ``operationId`` that maps to a Python handler in ``routes.py``.\n\nThis file is a copyable template. It is not part of the ``openapi_first``\nlibrary API surface.\n\n----------------------------------------------------------------------\nOpenAPI x- extension fields demonstrated\n----------------------------------------------------------------------\n\nSchema-level extensions (display metadata for resource endpoints):\n\n ``x-primary-key`` (REQUIRED) Primary key property name\n ``x-display-format`` (REQUIRED) Human-readable label template\n ``x-list-columns`` (REQUIRED) Columns for the datatable\n\nProperty-level extensions (control UI rendering):\n\n ``x-label`` (REQUIRED) Human-readable field label\n ``x-order`` (REQUIRED) Field ordering in forms/detail\n ``x-description`` (optional) Helper text below form fields\n ``x-hidden`` (optional) Visibility in form / list / detail\n ``x-filterable`` (optional) Allows column filtering\n ``x-sortable`` (optional) Allows column sorting\n ``x-fk`` (optional) Foreign key — renders as dropdown\n ``x-fk.resource`` (REQUIRED for FK) Target resource name\n ``x-fk.prefetch`` (optional) Preload all FK options on mount\n ``x-ui-type`` (optional) Custom UI type (e.g. image upload)\n ``x-upload-url`` (optional) Upload endpoint for binary fields\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new vet clinic service using the bundled template:\n\n openapi-first scaffold vet_app\n\nCreate the service in a custom directory:\n\n openapi-first scaffold vet_app my-vet-clinic\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List pets with pagination\n response = client.list_pets(query_params={\"limit\": 10, \"offset\": 0})\n\n # Create a pet with FK references\n response = client.create_pet(\n body={\"name\": \"Fido\", \"species\": \"dog\", \"parents\": [1, 2]}\n )\n\n # Upload a pet photo\n response = client.upload_pet_photo(\n path_params={\"id\": 1},\n body={\"file\": open(\"photo.jpg\", \"rb\")},\n )\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.", "objects": { "data": { "name": "data", @@ -11,319 +11,179 @@ "signature": null, "docstring": "In-memory data store for the Veterinary Clinic example.\n\nThis module is NOT thread-safe and is intended for demos and scaffolds only.\n\nIt provides minimal, process-local data stores for the five veterinary\nclinic entities. Each store exposes standard CRUD operations backed by\na simple dictionary.\n\nThis module intentionally avoids:\n- persistence\n- concurrency guarantees\n- transactional semantics\n- validation beyond what Pydantic provides\n\nThis module is not part of the ``openapi_first`` library API surface.", "members": { - "date": { - "name": "date", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.date", - "signature": "", - "docstring": null - }, - "datetime": { - "name": "datetime", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.datetime", - "signature": "", - "docstring": null - }, - "timezone": { - "name": "timezone", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.timezone", - "signature": "", - "docstring": null - }, - "Parent": { - "name": "Parent", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Parent", - "signature": "", - "docstring": null - }, - "ParentCreate": { - "name": "ParentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.ParentCreate", - "signature": "", - "docstring": null - }, - "Vet": { - "name": "Vet", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Vet", - "signature": "", - "docstring": null - }, - "VetCreate": { - "name": "VetCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.VetCreate", - "signature": "", - "docstring": null - }, - "Treatment": { - "name": "Treatment", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Treatment", - "signature": "", - "docstring": null - }, - "TreatmentCreate": { - "name": "TreatmentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.TreatmentCreate", - "signature": "", - "docstring": null - }, - "Procedure": { - "name": "Procedure", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Procedure", - "signature": "", - "docstring": null - }, - "BasicNote": { - "name": "BasicNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.BasicNote", - "signature": "", - "docstring": null - }, - "HeartRateNote": { - "name": "HeartRateNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.HeartRateNote", - "signature": "", - "docstring": null - }, - "DentalNote": { - "name": "DentalNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.DentalNote", - "signature": "", - "docstring": null - }, - "VaccineNote": { - "name": "VaccineNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.VaccineNote", - "signature": "", - "docstring": null - }, - "PreOpNote": { - "name": "PreOpNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.PreOpNote", - "signature": "", - "docstring": null - }, - "SurgeryNote": { - "name": "SurgeryNote", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.SurgeryNote", - "signature": "", - "docstring": null - }, - "Pet": { - "name": "Pet", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Pet", - "signature": "", - "docstring": null - }, - "PetCreate": { - "name": "PetCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.PetCreate", - "signature": "", - "docstring": null - }, - "Appointment": { - "name": "Appointment", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.Appointment", - "signature": "", - "docstring": null - }, - "AppointmentCreate": { - "name": "AppointmentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.data.AppointmentCreate", - "signature": "", - "docstring": null - }, "list_parents": { "name": "list_parents", "kind": "function", "path": "openapi_first.templates.vet_app.data.list_parents", - "signature": "", + "signature": "list_parents() -> list[Parent]", "docstring": null }, "get_parent": { "name": "get_parent", "kind": "function", "path": "openapi_first.templates.vet_app.data.get_parent", - "signature": "", + "signature": "get_parent(parent_id: int) -> Parent", "docstring": null }, "create_parent": { "name": "create_parent", "kind": "function", "path": "openapi_first.templates.vet_app.data.create_parent", - "signature": "", + "signature": "create_parent(payload: ParentCreate) -> Parent", "docstring": null }, "update_parent": { "name": "update_parent", "kind": "function", "path": "openapi_first.templates.vet_app.data.update_parent", - "signature": "", + "signature": "update_parent(parent_id: int, payload: ParentCreate) -> Parent", "docstring": null }, "delete_parent": { "name": "delete_parent", "kind": "function", "path": "openapi_first.templates.vet_app.data.delete_parent", - "signature": "", + "signature": "delete_parent(parent_id: int) -> None", "docstring": null }, "list_vets": { "name": "list_vets", "kind": "function", "path": "openapi_first.templates.vet_app.data.list_vets", - "signature": "", + "signature": "list_vets() -> list[Vet]", "docstring": null }, "get_vet": { "name": "get_vet", "kind": "function", "path": "openapi_first.templates.vet_app.data.get_vet", - "signature": "", + "signature": "get_vet(vet_id: int) -> Vet", "docstring": null }, "create_vet": { "name": "create_vet", "kind": "function", "path": "openapi_first.templates.vet_app.data.create_vet", - "signature": "", + "signature": "create_vet(payload: VetCreate) -> Vet", "docstring": null }, "update_vet": { "name": "update_vet", "kind": "function", "path": "openapi_first.templates.vet_app.data.update_vet", - "signature": "", + "signature": "update_vet(vet_id: int, payload: VetCreate) -> Vet", "docstring": null }, "delete_vet": { "name": "delete_vet", "kind": "function", "path": "openapi_first.templates.vet_app.data.delete_vet", - "signature": "", + "signature": "delete_vet(vet_id: int) -> None", "docstring": null }, "list_treatments": { "name": "list_treatments", "kind": "function", "path": "openapi_first.templates.vet_app.data.list_treatments", - "signature": "", + "signature": "list_treatments() -> list[Treatment]", "docstring": null }, "get_treatment": { "name": "get_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.data.get_treatment", - "signature": "", + "signature": "get_treatment(treatment_id: int) -> Treatment", "docstring": null }, "create_treatment": { "name": "create_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.data.create_treatment", - "signature": "", + "signature": "create_treatment(payload: TreatmentCreate) -> Treatment", "docstring": null }, "update_treatment": { "name": "update_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.data.update_treatment", - "signature": "", + "signature": "update_treatment(treatment_id: int, payload: TreatmentCreate) -> Treatment", "docstring": null }, "delete_treatment": { "name": "delete_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.data.delete_treatment", - "signature": "", + "signature": "delete_treatment(treatment_id: int) -> None", "docstring": null }, "list_pets": { "name": "list_pets", "kind": "function", "path": "openapi_first.templates.vet_app.data.list_pets", - "signature": "", + "signature": "list_pets() -> list[Pet]", "docstring": null }, "get_pet": { "name": "get_pet", "kind": "function", "path": "openapi_first.templates.vet_app.data.get_pet", - "signature": "", + "signature": "get_pet(pet_id: int) -> Pet", "docstring": null }, "create_pet": { "name": "create_pet", "kind": "function", "path": "openapi_first.templates.vet_app.data.create_pet", - "signature": "", + "signature": "create_pet(payload: PetCreate) -> Pet", "docstring": null }, "update_pet": { "name": "update_pet", "kind": "function", "path": "openapi_first.templates.vet_app.data.update_pet", - "signature": "", + "signature": "update_pet(pet_id: int, payload: PetCreate) -> Pet", "docstring": null }, "delete_pet": { "name": "delete_pet", "kind": "function", "path": "openapi_first.templates.vet_app.data.delete_pet", - "signature": "", + "signature": "delete_pet(pet_id: int) -> None", "docstring": null }, "list_appointments": { "name": "list_appointments", "kind": "function", "path": "openapi_first.templates.vet_app.data.list_appointments", - "signature": "", + "signature": "list_appointments() -> list[Appointment]", "docstring": null }, "get_appointment": { "name": "get_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.data.get_appointment", - "signature": "", + "signature": "get_appointment(appointment_id: int) -> Appointment", "docstring": null }, "create_appointment": { "name": "create_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.data.create_appointment", - "signature": "", + "signature": "create_appointment(payload: AppointmentCreate) -> Appointment", "docstring": null }, "update_appointment": { "name": "update_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.data.update_appointment", - "signature": "", + "signature": "update_appointment(appointment_id: int, payload: AppointmentCreate) -> Appointment", "docstring": null }, "delete_appointment": { "name": "delete_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.data.delete_appointment", - "signature": "", + "signature": "delete_appointment(appointment_id: int) -> None", "docstring": null } } @@ -335,48 +195,27 @@ "signature": null, "docstring": "Application entry point for an OpenAPI-first Veterinary Clinic service.\n\nThis module constructs a FastAPI application exclusively from an\nOpenAPI specification and a handler namespace, without using\ndecorator-driven routing.\n\nAll HTTP routes, methods, request/response schemas, and operation\nbindings are defined in the OpenAPI document referenced by\n``openapi_path``. Python callables defined in the ``routes`` module are\nbound to OpenAPI operations strictly via ``operationId``.\n\nThis module contains no routing logic, persistence concerns, or\nframework configuration beyond application assembly.\n\nDesign guarantees:\n- OpenAPI is the single source of truth\n- No undocumented routes can exist\n- Every OpenAPI operationId must resolve to exactly one handler\n- All contract violations fail at application startup\n\nThis file is intended to be used as the ASGI entry point.\n\nExample:\n uvicorn main:app", "members": { - "asynccontextmanager": { - "name": "asynccontextmanager", - "kind": "alias", - "path": "openapi_first.templates.vet_app.main.asynccontextmanager", - "signature": "", - "docstring": null - }, - "CORSMiddleware": { - "name": "CORSMiddleware", - "kind": "alias", - "path": "openapi_first.templates.vet_app.main.CORSMiddleware", - "signature": "", - "docstring": null - }, "OpenAPIFirstApp": { "name": "OpenAPIFirstApp", "kind": "class", "path": "openapi_first.templates.vet_app.main.OpenAPIFirstApp", - "signature": "", - "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", + "signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)", + "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", "members": { "openapi": { "name": "openapi", "kind": "attribute", "path": "openapi_first.templates.vet_app.main.OpenAPIFirstApp.openapi", - "signature": "", + "signature": null, "docstring": null } } }, - "routes": { - "name": "routes", - "kind": "alias", - "path": "openapi_first.templates.vet_app.main.routes", - "signature": "", - "docstring": null - }, "lifespan": { "name": "lifespan", "kind": "function", "path": "openapi_first.templates.vet_app.main.lifespan", - "signature": "", + "signature": "lifespan(app)", "docstring": null }, "app": { @@ -395,53 +234,11 @@ "signature": null, "docstring": null, "members": { - "date": { - "name": "date", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.date", - "signature": "", - "docstring": null - }, - "datetime": { - "name": "datetime", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.datetime", - "signature": "", - "docstring": null - }, - "Annotated": { - "name": "Annotated", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.Annotated", - "signature": "", - "docstring": null - }, - "Literal": { - "name": "Literal", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.Literal", - "signature": "", - "docstring": null - }, - "BaseModel": { - "name": "BaseModel", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.BaseModel", - "signature": "", - "docstring": null - }, - "Field": { - "name": "Field", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.Field", - "signature": "", - "docstring": null - }, "Metadata": { "name": "Metadata", "kind": "class", "path": "openapi_first.templates.vet_app.models.Metadata", - "signature": "", + "signature": null, "docstring": null, "members": { "createdOn": { @@ -464,7 +261,7 @@ "name": "ParentBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.ParentBase", - "signature": "", + "signature": null, "docstring": null, "members": { "name": { @@ -501,14 +298,14 @@ "name": "ParentCreate", "kind": "class", "path": "openapi_first.templates.vet_app.models.ParentCreate", - "signature": "", + "signature": null, "docstring": null }, "Parent": { "name": "Parent", "kind": "class", "path": "openapi_first.templates.vet_app.models.Parent", - "signature": "", + "signature": null, "docstring": null, "members": { "id": { @@ -524,7 +321,7 @@ "name": "VetBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.VetBase", - "signature": "", + "signature": null, "docstring": null, "members": { "name": { @@ -568,14 +365,14 @@ "name": "VetCreate", "kind": "class", "path": "openapi_first.templates.vet_app.models.VetCreate", - "signature": "", + "signature": null, "docstring": null }, "Vet": { "name": "Vet", "kind": "class", "path": "openapi_first.templates.vet_app.models.Vet", - "signature": "", + "signature": null, "docstring": null, "members": { "id": { @@ -591,7 +388,7 @@ "name": "ProcedureNoteBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.ProcedureNoteBase", - "signature": "", + "signature": null, "docstring": null, "members": { "summary": { @@ -614,7 +411,7 @@ "name": "BasicNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.BasicNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -630,7 +427,7 @@ "name": "HeartRateNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.HeartRateNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -653,7 +450,7 @@ "name": "DentalNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.DentalNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -683,7 +480,7 @@ "name": "VaccineNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.VaccineNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -713,7 +510,7 @@ "name": "PreOpNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.PreOpNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -743,7 +540,7 @@ "name": "SurgeryNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.SurgeryNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -780,7 +577,7 @@ "name": "Procedure", "kind": "class", "path": "openapi_first.templates.vet_app.models.Procedure", - "signature": "", + "signature": null, "docstring": null, "members": { "name": { @@ -817,7 +614,7 @@ "name": "TreatmentBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.TreatmentBase", - "signature": "", + "signature": null, "docstring": null, "members": { "label": { @@ -847,14 +644,14 @@ "name": "TreatmentCreate", "kind": "class", "path": "openapi_first.templates.vet_app.models.TreatmentCreate", - "signature": "", + "signature": null, "docstring": null }, "Treatment": { "name": "Treatment", "kind": "class", "path": "openapi_first.templates.vet_app.models.Treatment", - "signature": "", + "signature": null, "docstring": null, "members": { "id": { @@ -870,7 +667,7 @@ "name": "PetBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.PetBase", - "signature": "", + "signature": null, "docstring": null, "members": { "name": { @@ -928,7 +725,7 @@ "name": "PetCreate", "kind": "class", "path": "openapi_first.templates.vet_app.models.PetCreate", - "signature": "", + "signature": null, "docstring": null, "members": { "parent_ids": { @@ -944,7 +741,7 @@ "name": "Pet", "kind": "class", "path": "openapi_first.templates.vet_app.models.Pet", - "signature": "", + "signature": null, "docstring": null, "members": { "id": { @@ -967,7 +764,7 @@ "name": "AppointmentBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.AppointmentBase", - "signature": "", + "signature": null, "docstring": null, "members": { "date": { @@ -1004,7 +801,7 @@ "name": "AppointmentCreate", "kind": "class", "path": "openapi_first.templates.vet_app.models.AppointmentCreate", - "signature": "", + "signature": null, "docstring": null, "members": { "pet_id": { @@ -1034,7 +831,7 @@ "name": "Appointment", "kind": "class", "path": "openapi_first.templates.vet_app.models.Appointment", - "signature": "", + "signature": null, "docstring": null, "members": { "id": { @@ -1076,270 +873,193 @@ "signature": null, "docstring": "Veterinary Clinic route handlers bound via OpenAPI operationId.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Domain models defined using\nPydantic are used for request and response payloads.\n\nNo routing decorators, path definitions, or implicit framework behavior\nappear in this module. All routing, HTTP methods, and schemas are defined\nin the OpenAPI specification.", "members": { - "Response": { - "name": "Response", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.Response", - "signature": "", - "docstring": null - }, - "HTTPException": { - "name": "HTTPException", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.HTTPException", - "signature": "", - "docstring": null - }, - "UploadFile": { - "name": "UploadFile", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.UploadFile", - "signature": "", - "docstring": null - }, - "StreamingResponse": { - "name": "StreamingResponse", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.StreamingResponse", - "signature": "", - "docstring": null - }, - "subscribe": { - "name": "subscribe", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.subscribe", - "signature": "", - "docstring": null - }, - "unsubscribe": { - "name": "unsubscribe", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.unsubscribe", - "signature": "", - "docstring": null - }, - "ParentCreate": { - "name": "ParentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.ParentCreate", - "signature": "", - "docstring": null - }, - "VetCreate": { - "name": "VetCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.VetCreate", - "signature": "", - "docstring": null - }, - "TreatmentCreate": { - "name": "TreatmentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.TreatmentCreate", - "signature": "", - "docstring": null - }, - "PetCreate": { - "name": "PetCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.PetCreate", - "signature": "", - "docstring": null - }, - "AppointmentCreate": { - "name": "AppointmentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.AppointmentCreate", - "signature": "", - "docstring": null - }, "list_parents": { "name": "list_parents", "kind": "function", "path": "openapi_first.templates.vet_app.routes.list_parents", - "signature": "", + "signature": "list_parents(limit: int = 20, offset: int = 0) -> dict", "docstring": "List parents (paginated).\n\nParameters\n----------\nlimit : int\n Maximum number of records to return.\noffset : int\n Number of records to skip.\n\nReturns\n-------\ndict\n Paginated response with ``total`` and ``items``." }, "create_parent": { "name": "create_parent", "kind": "function", "path": "openapi_first.templates.vet_app.routes.create_parent", - "signature": "", - "docstring": "Create a parent.\n\nParameters\n----------\npayload : ParentCreate\n Parent data excluding the ``id`` field.\n\nReturns\n-------\nParent\n The newly created parent." + "signature": "create_parent(payload: ParentCreate, response: Response) -> Parent", + "docstring": "Create a parent.\n\nParameters\n----------\npayload : ParentCreate\n Parent data excluding the ``id`` field.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nParent\n The newly created parent." }, "get_parent": { "name": "get_parent", "kind": "function", "path": "openapi_first.templates.vet_app.routes.get_parent", - "signature": "", + "signature": "get_parent(id: int) -> Parent", "docstring": "Retrieve a single parent by ID.\n\nParameters\n----------\nid : int\n Identifier of the parent.\n\nReturns\n-------\nParent\n The requested parent.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist." }, "update_parent": { "name": "update_parent", "kind": "function", "path": "openapi_first.templates.vet_app.routes.update_parent", - "signature": "", + "signature": "update_parent(id: int, payload: ParentCreate) -> Parent", "docstring": "Update an existing parent.\n\nParameters\n----------\nid : int\n Identifier of the parent.\npayload : ParentCreate\n Updated parent data.\n\nReturns\n-------\nParent\n The updated parent.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist." }, "delete_parent": { "name": "delete_parent", "kind": "function", "path": "openapi_first.templates.vet_app.routes.delete_parent", - "signature": "", - "docstring": "Delete an existing parent.\n\nParameters\n----------\nid : int\n Identifier of the parent.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist." + "signature": "delete_parent(id: int, response: Response) -> None", + "docstring": "Delete an existing parent.\n\nParameters\n----------\nid : int\n Identifier of the parent.\nresponse : Response\n Response object used to set the HTTP status code.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist." }, "list_vets": { "name": "list_vets", "kind": "function", "path": "openapi_first.templates.vet_app.routes.list_vets", - "signature": "", + "signature": "list_vets(limit: int = 20, offset: int = 0)", "docstring": "List vets (paginated)." }, "create_vet": { "name": "create_vet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.create_vet", - "signature": "", + "signature": "create_vet(payload: VetCreate, response: Response)", "docstring": "Create a vet." }, "get_vet": { "name": "get_vet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.get_vet", - "signature": "", + "signature": "get_vet(id: int)", "docstring": "Retrieve a single vet by ID." }, "update_vet": { "name": "update_vet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.update_vet", - "signature": "", + "signature": "update_vet(id: int, payload: VetCreate)", "docstring": "Update an existing vet." }, "delete_vet": { "name": "delete_vet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.delete_vet", - "signature": "", + "signature": "delete_vet(id: int, response: Response)", "docstring": "Delete an existing vet." }, "list_treatments": { "name": "list_treatments", "kind": "function", "path": "openapi_first.templates.vet_app.routes.list_treatments", - "signature": "", + "signature": "list_treatments() -> list[Treatment]", "docstring": "List treatments (catalogue).\n\nReturns\n-------\nlist[Treatment]\n A list of treatment domain objects." }, "create_treatment": { "name": "create_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.create_treatment", - "signature": "", + "signature": "create_treatment(payload: TreatmentCreate, response: Response)", "docstring": "Add a treatment (admin only)." }, "get_treatment": { "name": "get_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.get_treatment", - "signature": "", + "signature": "get_treatment(id: int)", "docstring": "Retrieve a single treatment by ID." }, "update_treatment": { "name": "update_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.update_treatment", - "signature": "", + "signature": "update_treatment(id: int, payload: TreatmentCreate)", "docstring": "Update an existing treatment." }, "delete_treatment": { "name": "delete_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.delete_treatment", - "signature": "", + "signature": "delete_treatment(id: int, response: Response)", "docstring": "Delete an existing treatment." }, "list_pets": { "name": "list_pets", "kind": "function", "path": "openapi_first.templates.vet_app.routes.list_pets", - "signature": "", + "signature": "list_pets(limit: int = 20, offset: int = 0)", "docstring": "List pets (paginated)." }, "create_pet": { "name": "create_pet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.create_pet", - "signature": "", + "signature": "create_pet(payload: PetCreate, response: Response)", "docstring": "Create a pet." }, "get_pet": { "name": "get_pet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.get_pet", - "signature": "", + "signature": "get_pet(id: int)", "docstring": "Retrieve a single pet by ID." }, "update_pet": { "name": "update_pet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.update_pet", - "signature": "", + "signature": "update_pet(id: int, payload: PetCreate)", "docstring": "Update an existing pet." }, "delete_pet": { "name": "delete_pet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.delete_pet", - "signature": "", + "signature": "delete_pet(id: int, response: Response)", "docstring": "Delete an existing pet." }, "upload_pet_photo": { "name": "upload_pet_photo", "kind": "function", "path": "openapi_first.templates.vet_app.routes.upload_pet_photo", - "signature": "", + "signature": "upload_pet_photo(id: int, file: UploadFile) -> dict", "docstring": "Upload a pet photo.\n\nParameters\n----------\nid : int\n Identifier of the pet.\nfile : UploadFile\n Image file to upload.\n\nReturns\n-------\ndict\n A confirmation with the pet ID." }, "list_appointments": { "name": "list_appointments", "kind": "function", "path": "openapi_first.templates.vet_app.routes.list_appointments", - "signature": "", + "signature": "list_appointments(limit: int = 20, offset: int = 0, date: str = None, vet: int = None, pet: int = None)", "docstring": "List appointments (paginated, filterable)." }, "create_appointment": { "name": "create_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.create_appointment", - "signature": "", + "signature": "create_appointment(payload: AppointmentCreate, response: Response)", "docstring": "Create an appointment." }, "get_appointment": { "name": "get_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.get_appointment", - "signature": "", + "signature": "get_appointment(id: int)", "docstring": "Retrieve a single appointment by ID." }, "update_appointment": { "name": "update_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.update_appointment", - "signature": "", + "signature": "update_appointment(id: int, payload: AppointmentCreate)", "docstring": "Update an existing appointment." }, "delete_appointment": { "name": "delete_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.delete_appointment", - "signature": "", + "signature": "delete_appointment(id: int, response: Response)", "docstring": "Delete an existing appointment." }, "stream_actions": { "name": "stream_actions", "kind": "function", "path": "openapi_first.templates.vet_app.routes.stream_actions", - "signature": "", + "signature": "stream_actions(id: int)", "docstring": "Stream animal actions via SSE, scoped to a pet's species." } } @@ -1351,39 +1071,18 @@ "signature": null, "docstring": null, "members": { - "asyncio": { - "name": "asyncio", - "kind": "alias", - "path": "openapi_first.templates.vet_app.sse.asyncio", - "signature": "", - "docstring": null - }, - "random": { - "name": "random", - "kind": "alias", - "path": "openapi_first.templates.vet_app.sse.random", - "signature": "", - "docstring": null - }, - "json": { - "name": "json", - "kind": "alias", - "path": "openapi_first.templates.vet_app.sse.json", - "signature": "", - "docstring": null - }, "subscribe": { "name": "subscribe", "kind": "function", "path": "openapi_first.templates.vet_app.sse.subscribe", - "signature": "", + "signature": "subscribe(pet_id: int, species: str) -> asyncio.Queue", "docstring": null }, "unsubscribe": { "name": "unsubscribe", "kind": "function", "path": "openapi_first.templates.vet_app.sse.unsubscribe", - "signature": "", + "signature": "unsubscribe(pet_id: int, q: asyncio.Queue)", "docstring": null } } @@ -1395,60 +1094,46 @@ "signature": null, "docstring": "End-to-end tests for the OpenAPI-first Veterinary Clinic example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store using Pydantic models.", "members": { - "TestClient": { - "name": "TestClient", - "kind": "alias", - "path": "openapi_first.templates.vet_app.test_vet_app.TestClient", - "signature": "", - "docstring": null - }, - "app": { - "name": "app", - "kind": "alias", - "path": "openapi_first.templates.vet_app.test_vet_app.app", - "signature": "", - "docstring": null - }, "load_openapi": { "name": "load_openapi", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.load_openapi", - "signature": "", + "signature": "load_openapi(path: str | Path)", "docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup." }, "OpenAPIClient": { "name": "OpenAPIClient", "kind": "class", "path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient", - "signature": "", - "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", + "signature": "OpenAPIClient(spec: dict[str, Any], base_url: str | None = None, client: httpx.Client | None = None)", + "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nArgs:\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification.\n base_url (str | None):\n Base URL of the target service. If omitted, the first entry in the\n OpenAPI `servers` list is used.\n client (httpx.Client | None):\n Optional preconfigured httpx client instance.\n\nRaises:\n OpenAPIClientError:\n If no servers are defined, spec has no paths, operationIds are\n missing/duplicate, or required parameters are missing.\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", "members": { "spec": { "name": "spec", "kind": "attribute", "path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.spec", - "signature": "", + "signature": null, "docstring": null }, "base_url": { "name": "base_url", "kind": "attribute", "path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.base_url", - "signature": "", + "signature": null, "docstring": null }, "client": { "name": "client", "kind": "attribute", "path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.client", - "signature": "", + "signature": null, "docstring": null }, "operations": { "name": "operations", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.operations", - "signature": "", + "signature": "operations()", "docstring": null } } @@ -1478,91 +1163,91 @@ "name": "test_list_parents", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_list_parents", - "signature": "", + "signature": "test_list_parents()", "docstring": "List parents returns paginated response." }, "test_create_parent": { "name": "test_create_parent", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_create_parent", - "signature": "", + "signature": "test_create_parent()", "docstring": "Creating a parent returns 201 with the created entity." }, "test_get_parent": { "name": "test_get_parent", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_get_parent", - "signature": "", + "signature": "test_get_parent()", "docstring": "Get parent by ID returns the entity." }, "test_update_parent": { "name": "test_update_parent", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_update_parent", - "signature": "", + "signature": "test_update_parent()", "docstring": "Update parent replaces its values." }, "test_delete_parent": { "name": "test_delete_parent", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_delete_parent", - "signature": "", + "signature": "test_delete_parent()", "docstring": "Delete parent returns 204 and removes the entity." }, "test_list_vets": { "name": "test_list_vets", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_list_vets", - "signature": "", + "signature": "test_list_vets()", "docstring": "List vets returns paginated response." }, "test_create_vet": { "name": "test_create_vet", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_create_vet", - "signature": "", + "signature": "test_create_vet()", "docstring": "Creating a vet returns 201." }, "test_list_treatments": { "name": "test_list_treatments", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_list_treatments", - "signature": "", + "signature": "test_list_treatments()", "docstring": "List treatments returns an array." }, "test_create_treatment": { "name": "test_create_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_create_treatment", - "signature": "", + "signature": "test_create_treatment()", "docstring": "Creating a treatment returns 201." }, "test_create_pet": { "name": "test_create_pet", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_create_pet", - "signature": "", + "signature": "test_create_pet()", "docstring": "Creating a pet links FK references." }, "test_upload_pet_photo": { "name": "test_upload_pet_photo", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_upload_pet_photo", - "signature": "", + "signature": "test_upload_pet_photo()", "docstring": "Upload pet photo returns 200." }, "test_list_appointments": { "name": "test_list_appointments", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_list_appointments", - "signature": "", + "signature": "test_list_appointments()", "docstring": "List appointments returns paginated response with filter params." }, "test_full_appointment_lifecycle": { "name": "test_full_appointment_lifecycle", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_full_appointment_lifecycle", - "signature": "", + "signature": "test_full_appointment_lifecycle()", "docstring": "Create a parent, vet, treatment, pet, then an appointment." } } diff --git a/openapi-first/mcp/modules/openapi_first.templates.vet_app.main.json b/openapi-first/mcp/modules/openapi_first.templates.vet_app.main.json index 6d5af0f..788be53 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.vet_app.main.json +++ b/openapi-first/mcp/modules/openapi_first.templates.vet_app.main.json @@ -4,48 +4,27 @@ "path": "openapi_first.templates.vet_app.main", "docstring": "Application entry point for an OpenAPI-first Veterinary Clinic service.\n\nThis module constructs a FastAPI application exclusively from an\nOpenAPI specification and a handler namespace, without using\ndecorator-driven routing.\n\nAll HTTP routes, methods, request/response schemas, and operation\nbindings are defined in the OpenAPI document referenced by\n``openapi_path``. Python callables defined in the ``routes`` module are\nbound to OpenAPI operations strictly via ``operationId``.\n\nThis module contains no routing logic, persistence concerns, or\nframework configuration beyond application assembly.\n\nDesign guarantees:\n- OpenAPI is the single source of truth\n- No undocumented routes can exist\n- Every OpenAPI operationId must resolve to exactly one handler\n- All contract violations fail at application startup\n\nThis file is intended to be used as the ASGI entry point.\n\nExample:\n uvicorn main:app", "objects": { - "asynccontextmanager": { - "name": "asynccontextmanager", - "kind": "alias", - "path": "openapi_first.templates.vet_app.main.asynccontextmanager", - "signature": "", - "docstring": null - }, - "CORSMiddleware": { - "name": "CORSMiddleware", - "kind": "alias", - "path": "openapi_first.templates.vet_app.main.CORSMiddleware", - "signature": "", - "docstring": null - }, "OpenAPIFirstApp": { "name": "OpenAPIFirstApp", "kind": "class", "path": "openapi_first.templates.vet_app.main.OpenAPIFirstApp", - "signature": "", - "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", + "signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)", + "docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```", "members": { "openapi": { "name": "openapi", "kind": "attribute", "path": "openapi_first.templates.vet_app.main.OpenAPIFirstApp.openapi", - "signature": "", + "signature": null, "docstring": null } } }, - "routes": { - "name": "routes", - "kind": "alias", - "path": "openapi_first.templates.vet_app.main.routes", - "signature": "", - "docstring": null - }, "lifespan": { "name": "lifespan", "kind": "function", "path": "openapi_first.templates.vet_app.main.lifespan", - "signature": "", + "signature": "lifespan(app)", "docstring": null }, "app": { diff --git a/openapi-first/mcp/modules/openapi_first.templates.vet_app.models.json b/openapi-first/mcp/modules/openapi_first.templates.vet_app.models.json index ecc0e8a..bb82da1 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.vet_app.models.json +++ b/openapi-first/mcp/modules/openapi_first.templates.vet_app.models.json @@ -4,53 +4,11 @@ "path": "openapi_first.templates.vet_app.models", "docstring": null, "objects": { - "date": { - "name": "date", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.date", - "signature": "", - "docstring": null - }, - "datetime": { - "name": "datetime", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.datetime", - "signature": "", - "docstring": null - }, - "Annotated": { - "name": "Annotated", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.Annotated", - "signature": "", - "docstring": null - }, - "Literal": { - "name": "Literal", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.Literal", - "signature": "", - "docstring": null - }, - "BaseModel": { - "name": "BaseModel", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.BaseModel", - "signature": "", - "docstring": null - }, - "Field": { - "name": "Field", - "kind": "alias", - "path": "openapi_first.templates.vet_app.models.Field", - "signature": "", - "docstring": null - }, "Metadata": { "name": "Metadata", "kind": "class", "path": "openapi_first.templates.vet_app.models.Metadata", - "signature": "", + "signature": null, "docstring": null, "members": { "createdOn": { @@ -73,7 +31,7 @@ "name": "ParentBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.ParentBase", - "signature": "", + "signature": null, "docstring": null, "members": { "name": { @@ -110,14 +68,14 @@ "name": "ParentCreate", "kind": "class", "path": "openapi_first.templates.vet_app.models.ParentCreate", - "signature": "", + "signature": null, "docstring": null }, "Parent": { "name": "Parent", "kind": "class", "path": "openapi_first.templates.vet_app.models.Parent", - "signature": "", + "signature": null, "docstring": null, "members": { "id": { @@ -133,7 +91,7 @@ "name": "VetBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.VetBase", - "signature": "", + "signature": null, "docstring": null, "members": { "name": { @@ -177,14 +135,14 @@ "name": "VetCreate", "kind": "class", "path": "openapi_first.templates.vet_app.models.VetCreate", - "signature": "", + "signature": null, "docstring": null }, "Vet": { "name": "Vet", "kind": "class", "path": "openapi_first.templates.vet_app.models.Vet", - "signature": "", + "signature": null, "docstring": null, "members": { "id": { @@ -200,7 +158,7 @@ "name": "ProcedureNoteBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.ProcedureNoteBase", - "signature": "", + "signature": null, "docstring": null, "members": { "summary": { @@ -223,7 +181,7 @@ "name": "BasicNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.BasicNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -239,7 +197,7 @@ "name": "HeartRateNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.HeartRateNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -262,7 +220,7 @@ "name": "DentalNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.DentalNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -292,7 +250,7 @@ "name": "VaccineNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.VaccineNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -322,7 +280,7 @@ "name": "PreOpNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.PreOpNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -352,7 +310,7 @@ "name": "SurgeryNote", "kind": "class", "path": "openapi_first.templates.vet_app.models.SurgeryNote", - "signature": "", + "signature": null, "docstring": null, "members": { "noteType": { @@ -389,7 +347,7 @@ "name": "Procedure", "kind": "class", "path": "openapi_first.templates.vet_app.models.Procedure", - "signature": "", + "signature": null, "docstring": null, "members": { "name": { @@ -426,7 +384,7 @@ "name": "TreatmentBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.TreatmentBase", - "signature": "", + "signature": null, "docstring": null, "members": { "label": { @@ -456,14 +414,14 @@ "name": "TreatmentCreate", "kind": "class", "path": "openapi_first.templates.vet_app.models.TreatmentCreate", - "signature": "", + "signature": null, "docstring": null }, "Treatment": { "name": "Treatment", "kind": "class", "path": "openapi_first.templates.vet_app.models.Treatment", - "signature": "", + "signature": null, "docstring": null, "members": { "id": { @@ -479,7 +437,7 @@ "name": "PetBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.PetBase", - "signature": "", + "signature": null, "docstring": null, "members": { "name": { @@ -537,7 +495,7 @@ "name": "PetCreate", "kind": "class", "path": "openapi_first.templates.vet_app.models.PetCreate", - "signature": "", + "signature": null, "docstring": null, "members": { "parent_ids": { @@ -553,7 +511,7 @@ "name": "Pet", "kind": "class", "path": "openapi_first.templates.vet_app.models.Pet", - "signature": "", + "signature": null, "docstring": null, "members": { "id": { @@ -576,7 +534,7 @@ "name": "AppointmentBase", "kind": "class", "path": "openapi_first.templates.vet_app.models.AppointmentBase", - "signature": "", + "signature": null, "docstring": null, "members": { "date": { @@ -613,7 +571,7 @@ "name": "AppointmentCreate", "kind": "class", "path": "openapi_first.templates.vet_app.models.AppointmentCreate", - "signature": "", + "signature": null, "docstring": null, "members": { "pet_id": { @@ -643,7 +601,7 @@ "name": "Appointment", "kind": "class", "path": "openapi_first.templates.vet_app.models.Appointment", - "signature": "", + "signature": null, "docstring": null, "members": { "id": { diff --git a/openapi-first/mcp/modules/openapi_first.templates.vet_app.routes.json b/openapi-first/mcp/modules/openapi_first.templates.vet_app.routes.json index 8067f66..dbb1f5a 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.vet_app.routes.json +++ b/openapi-first/mcp/modules/openapi_first.templates.vet_app.routes.json @@ -4,270 +4,193 @@ "path": "openapi_first.templates.vet_app.routes", "docstring": "Veterinary Clinic route handlers bound via OpenAPI operationId.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Domain models defined using\nPydantic are used for request and response payloads.\n\nNo routing decorators, path definitions, or implicit framework behavior\nappear in this module. All routing, HTTP methods, and schemas are defined\nin the OpenAPI specification.", "objects": { - "Response": { - "name": "Response", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.Response", - "signature": "", - "docstring": null - }, - "HTTPException": { - "name": "HTTPException", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.HTTPException", - "signature": "", - "docstring": null - }, - "UploadFile": { - "name": "UploadFile", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.UploadFile", - "signature": "", - "docstring": null - }, - "StreamingResponse": { - "name": "StreamingResponse", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.StreamingResponse", - "signature": "", - "docstring": null - }, - "subscribe": { - "name": "subscribe", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.subscribe", - "signature": "", - "docstring": null - }, - "unsubscribe": { - "name": "unsubscribe", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.unsubscribe", - "signature": "", - "docstring": null - }, - "ParentCreate": { - "name": "ParentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.ParentCreate", - "signature": "", - "docstring": null - }, - "VetCreate": { - "name": "VetCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.VetCreate", - "signature": "", - "docstring": null - }, - "TreatmentCreate": { - "name": "TreatmentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.TreatmentCreate", - "signature": "", - "docstring": null - }, - "PetCreate": { - "name": "PetCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.PetCreate", - "signature": "", - "docstring": null - }, - "AppointmentCreate": { - "name": "AppointmentCreate", - "kind": "alias", - "path": "openapi_first.templates.vet_app.routes.AppointmentCreate", - "signature": "", - "docstring": null - }, "list_parents": { "name": "list_parents", "kind": "function", "path": "openapi_first.templates.vet_app.routes.list_parents", - "signature": "", + "signature": "list_parents(limit: int = 20, offset: int = 0) -> dict", "docstring": "List parents (paginated).\n\nParameters\n----------\nlimit : int\n Maximum number of records to return.\noffset : int\n Number of records to skip.\n\nReturns\n-------\ndict\n Paginated response with ``total`` and ``items``." }, "create_parent": { "name": "create_parent", "kind": "function", "path": "openapi_first.templates.vet_app.routes.create_parent", - "signature": "", - "docstring": "Create a parent.\n\nParameters\n----------\npayload : ParentCreate\n Parent data excluding the ``id`` field.\n\nReturns\n-------\nParent\n The newly created parent." + "signature": "create_parent(payload: ParentCreate, response: Response) -> Parent", + "docstring": "Create a parent.\n\nParameters\n----------\npayload : ParentCreate\n Parent data excluding the ``id`` field.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nParent\n The newly created parent." }, "get_parent": { "name": "get_parent", "kind": "function", "path": "openapi_first.templates.vet_app.routes.get_parent", - "signature": "", + "signature": "get_parent(id: int) -> Parent", "docstring": "Retrieve a single parent by ID.\n\nParameters\n----------\nid : int\n Identifier of the parent.\n\nReturns\n-------\nParent\n The requested parent.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist." }, "update_parent": { "name": "update_parent", "kind": "function", "path": "openapi_first.templates.vet_app.routes.update_parent", - "signature": "", + "signature": "update_parent(id: int, payload: ParentCreate) -> Parent", "docstring": "Update an existing parent.\n\nParameters\n----------\nid : int\n Identifier of the parent.\npayload : ParentCreate\n Updated parent data.\n\nReturns\n-------\nParent\n The updated parent.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist." }, "delete_parent": { "name": "delete_parent", "kind": "function", "path": "openapi_first.templates.vet_app.routes.delete_parent", - "signature": "", - "docstring": "Delete an existing parent.\n\nParameters\n----------\nid : int\n Identifier of the parent.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist." + "signature": "delete_parent(id: int, response: Response) -> None", + "docstring": "Delete an existing parent.\n\nParameters\n----------\nid : int\n Identifier of the parent.\nresponse : Response\n Response object used to set the HTTP status code.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist." }, "list_vets": { "name": "list_vets", "kind": "function", "path": "openapi_first.templates.vet_app.routes.list_vets", - "signature": "", + "signature": "list_vets(limit: int = 20, offset: int = 0)", "docstring": "List vets (paginated)." }, "create_vet": { "name": "create_vet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.create_vet", - "signature": "", + "signature": "create_vet(payload: VetCreate, response: Response)", "docstring": "Create a vet." }, "get_vet": { "name": "get_vet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.get_vet", - "signature": "", + "signature": "get_vet(id: int)", "docstring": "Retrieve a single vet by ID." }, "update_vet": { "name": "update_vet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.update_vet", - "signature": "", + "signature": "update_vet(id: int, payload: VetCreate)", "docstring": "Update an existing vet." }, "delete_vet": { "name": "delete_vet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.delete_vet", - "signature": "", + "signature": "delete_vet(id: int, response: Response)", "docstring": "Delete an existing vet." }, "list_treatments": { "name": "list_treatments", "kind": "function", "path": "openapi_first.templates.vet_app.routes.list_treatments", - "signature": "", + "signature": "list_treatments() -> list[Treatment]", "docstring": "List treatments (catalogue).\n\nReturns\n-------\nlist[Treatment]\n A list of treatment domain objects." }, "create_treatment": { "name": "create_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.create_treatment", - "signature": "", + "signature": "create_treatment(payload: TreatmentCreate, response: Response)", "docstring": "Add a treatment (admin only)." }, "get_treatment": { "name": "get_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.get_treatment", - "signature": "", + "signature": "get_treatment(id: int)", "docstring": "Retrieve a single treatment by ID." }, "update_treatment": { "name": "update_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.update_treatment", - "signature": "", + "signature": "update_treatment(id: int, payload: TreatmentCreate)", "docstring": "Update an existing treatment." }, "delete_treatment": { "name": "delete_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.delete_treatment", - "signature": "", + "signature": "delete_treatment(id: int, response: Response)", "docstring": "Delete an existing treatment." }, "list_pets": { "name": "list_pets", "kind": "function", "path": "openapi_first.templates.vet_app.routes.list_pets", - "signature": "", + "signature": "list_pets(limit: int = 20, offset: int = 0)", "docstring": "List pets (paginated)." }, "create_pet": { "name": "create_pet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.create_pet", - "signature": "", + "signature": "create_pet(payload: PetCreate, response: Response)", "docstring": "Create a pet." }, "get_pet": { "name": "get_pet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.get_pet", - "signature": "", + "signature": "get_pet(id: int)", "docstring": "Retrieve a single pet by ID." }, "update_pet": { "name": "update_pet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.update_pet", - "signature": "", + "signature": "update_pet(id: int, payload: PetCreate)", "docstring": "Update an existing pet." }, "delete_pet": { "name": "delete_pet", "kind": "function", "path": "openapi_first.templates.vet_app.routes.delete_pet", - "signature": "", + "signature": "delete_pet(id: int, response: Response)", "docstring": "Delete an existing pet." }, "upload_pet_photo": { "name": "upload_pet_photo", "kind": "function", "path": "openapi_first.templates.vet_app.routes.upload_pet_photo", - "signature": "", + "signature": "upload_pet_photo(id: int, file: UploadFile) -> dict", "docstring": "Upload a pet photo.\n\nParameters\n----------\nid : int\n Identifier of the pet.\nfile : UploadFile\n Image file to upload.\n\nReturns\n-------\ndict\n A confirmation with the pet ID." }, "list_appointments": { "name": "list_appointments", "kind": "function", "path": "openapi_first.templates.vet_app.routes.list_appointments", - "signature": "", + "signature": "list_appointments(limit: int = 20, offset: int = 0, date: str = None, vet: int = None, pet: int = None)", "docstring": "List appointments (paginated, filterable)." }, "create_appointment": { "name": "create_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.create_appointment", - "signature": "", + "signature": "create_appointment(payload: AppointmentCreate, response: Response)", "docstring": "Create an appointment." }, "get_appointment": { "name": "get_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.get_appointment", - "signature": "", + "signature": "get_appointment(id: int)", "docstring": "Retrieve a single appointment by ID." }, "update_appointment": { "name": "update_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.update_appointment", - "signature": "", + "signature": "update_appointment(id: int, payload: AppointmentCreate)", "docstring": "Update an existing appointment." }, "delete_appointment": { "name": "delete_appointment", "kind": "function", "path": "openapi_first.templates.vet_app.routes.delete_appointment", - "signature": "", + "signature": "delete_appointment(id: int, response: Response)", "docstring": "Delete an existing appointment." }, "stream_actions": { "name": "stream_actions", "kind": "function", "path": "openapi_first.templates.vet_app.routes.stream_actions", - "signature": "", + "signature": "stream_actions(id: int)", "docstring": "Stream animal actions via SSE, scoped to a pet's species." } } diff --git a/openapi-first/mcp/modules/openapi_first.templates.vet_app.sse.json b/openapi-first/mcp/modules/openapi_first.templates.vet_app.sse.json index 8d41190..b3a3f31 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.vet_app.sse.json +++ b/openapi-first/mcp/modules/openapi_first.templates.vet_app.sse.json @@ -4,39 +4,18 @@ "path": "openapi_first.templates.vet_app.sse", "docstring": null, "objects": { - "asyncio": { - "name": "asyncio", - "kind": "alias", - "path": "openapi_first.templates.vet_app.sse.asyncio", - "signature": "", - "docstring": null - }, - "random": { - "name": "random", - "kind": "alias", - "path": "openapi_first.templates.vet_app.sse.random", - "signature": "", - "docstring": null - }, - "json": { - "name": "json", - "kind": "alias", - "path": "openapi_first.templates.vet_app.sse.json", - "signature": "", - "docstring": null - }, "subscribe": { "name": "subscribe", "kind": "function", "path": "openapi_first.templates.vet_app.sse.subscribe", - "signature": "", + "signature": "subscribe(pet_id: int, species: str) -> asyncio.Queue", "docstring": null }, "unsubscribe": { "name": "unsubscribe", "kind": "function", "path": "openapi_first.templates.vet_app.sse.unsubscribe", - "signature": "", + "signature": "unsubscribe(pet_id: int, q: asyncio.Queue)", "docstring": null } } diff --git a/openapi-first/mcp/modules/openapi_first.templates.vet_app.test_vet_app.json b/openapi-first/mcp/modules/openapi_first.templates.vet_app.test_vet_app.json index 54da939..b7392e9 100644 --- a/openapi-first/mcp/modules/openapi_first.templates.vet_app.test_vet_app.json +++ b/openapi-first/mcp/modules/openapi_first.templates.vet_app.test_vet_app.json @@ -4,60 +4,46 @@ "path": "openapi_first.templates.vet_app.test_vet_app", "docstring": "End-to-end tests for the OpenAPI-first Veterinary Clinic example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store using Pydantic models.", "objects": { - "TestClient": { - "name": "TestClient", - "kind": "alias", - "path": "openapi_first.templates.vet_app.test_vet_app.TestClient", - "signature": "", - "docstring": null - }, - "app": { - "name": "app", - "kind": "alias", - "path": "openapi_first.templates.vet_app.test_vet_app.app", - "signature": "", - "docstring": null - }, "load_openapi": { "name": "load_openapi", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.load_openapi", - "signature": "", + "signature": "load_openapi(path: str | Path)", "docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup." }, "OpenAPIClient": { "name": "OpenAPIClient", "kind": "class", "path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient", - "signature": "", - "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", + "signature": "OpenAPIClient(spec: dict[str, Any], base_url: str | None = None, client: httpx.Client | None = None)", + "docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nArgs:\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification.\n base_url (str | None):\n Base URL of the target service. If omitted, the first entry in the\n OpenAPI `servers` list is used.\n client (httpx.Client | None):\n Optional preconfigured httpx client instance.\n\nRaises:\n OpenAPIClientError:\n If no servers are defined, spec has no paths, operationIds are\n missing/duplicate, or required parameters are missing.\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```", "members": { "spec": { "name": "spec", "kind": "attribute", "path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.spec", - "signature": "", + "signature": null, "docstring": null }, "base_url": { "name": "base_url", "kind": "attribute", "path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.base_url", - "signature": "", + "signature": null, "docstring": null }, "client": { "name": "client", "kind": "attribute", "path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.client", - "signature": "", + "signature": null, "docstring": null }, "operations": { "name": "operations", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.operations", - "signature": "", + "signature": "operations()", "docstring": null } } @@ -87,91 +73,91 @@ "name": "test_list_parents", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_list_parents", - "signature": "", + "signature": "test_list_parents()", "docstring": "List parents returns paginated response." }, "test_create_parent": { "name": "test_create_parent", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_create_parent", - "signature": "", + "signature": "test_create_parent()", "docstring": "Creating a parent returns 201 with the created entity." }, "test_get_parent": { "name": "test_get_parent", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_get_parent", - "signature": "", + "signature": "test_get_parent()", "docstring": "Get parent by ID returns the entity." }, "test_update_parent": { "name": "test_update_parent", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_update_parent", - "signature": "", + "signature": "test_update_parent()", "docstring": "Update parent replaces its values." }, "test_delete_parent": { "name": "test_delete_parent", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_delete_parent", - "signature": "", + "signature": "test_delete_parent()", "docstring": "Delete parent returns 204 and removes the entity." }, "test_list_vets": { "name": "test_list_vets", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_list_vets", - "signature": "", + "signature": "test_list_vets()", "docstring": "List vets returns paginated response." }, "test_create_vet": { "name": "test_create_vet", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_create_vet", - "signature": "", + "signature": "test_create_vet()", "docstring": "Creating a vet returns 201." }, "test_list_treatments": { "name": "test_list_treatments", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_list_treatments", - "signature": "", + "signature": "test_list_treatments()", "docstring": "List treatments returns an array." }, "test_create_treatment": { "name": "test_create_treatment", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_create_treatment", - "signature": "", + "signature": "test_create_treatment()", "docstring": "Creating a treatment returns 201." }, "test_create_pet": { "name": "test_create_pet", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_create_pet", - "signature": "", + "signature": "test_create_pet()", "docstring": "Creating a pet links FK references." }, "test_upload_pet_photo": { "name": "test_upload_pet_photo", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_upload_pet_photo", - "signature": "", + "signature": "test_upload_pet_photo()", "docstring": "Upload pet photo returns 200." }, "test_list_appointments": { "name": "test_list_appointments", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_list_appointments", - "signature": "", + "signature": "test_list_appointments()", "docstring": "List appointments returns paginated response with filter params." }, "test_full_appointment_lifecycle": { "name": "test_full_appointment_lifecycle", "kind": "function", "path": "openapi_first.templates.vet_app.test_vet_app.test_full_appointment_lifecycle", - "signature": "", + "signature": "test_full_appointment_lifecycle()", "docstring": "Create a parent, vet, treatment, pet, then an appointment." } } diff --git a/openapi-first/wiki/01_overview/index.html b/openapi-first/wiki/01_overview/index.html index 001a006..0f6aef7 100644 --- a/openapi-first/wiki/01_overview/index.html +++ b/openapi-first/wiki/01_overview/index.html @@ -879,7 +879,7 @@ get: operationId: get_health responses: - "200": + "200": description: OK content: application/json: @@ -894,8 +894,8 @@ 3 4
# routes.py
 def get_health():
-    """Health check operation handler."""
-    return {"status": "ok"}
+    """Health check operation handler."""
+    return {"status": "ok"}
 

openapi_first resolves operationId: get_health → routes.get_health and registers the route. No decorators, no @app.get, no routing metadata in code.

Guarantees this binding provides:

@@ -959,9 +959,9 @@
import routes app = OpenAPIFirstApp( - openapi_path="openapi.yaml", + openapi_path="openapi.yaml", routes_module=routes, - title="My Service", + title="My Service", )

Run with your FastAPI-compatible server (ASGI):

@@ -982,12 +982,12 @@ from openapi_first.loader import load_openapi from openapi_first.client import OpenAPIClient -spec = load_openapi("openapi.yaml") +spec = load_openapi("openapi.yaml") client = OpenAPIClient(spec) response = client.get_health() # operationId-driven call print(response.status_code) # 200 -print(response.json()) # {"status": "ok"} +print(response.json()) # {"status": "ok"}

OpenAPIClient builds one callable per operationId from the same spec, so the client can never drift from the server.


diff --git a/openapi-first/wiki/02_components/index.html b/openapi-first/wiki/02_components/index.html index e1f94cc..17261a1 100644 --- a/openapi-first/wiki/02_components/index.html +++ b/openapi-first/wiki/02_components/index.html @@ -934,7 +934,7 @@ 2 3
from openapi_first.loader import load_openapi
 
-spec = load_openapi("openapi.yaml")
+spec = load_openapi("openapi.yaml")
 

Behavior:

    @@ -958,9 +958,9 @@ import routes app = OpenAPIFirstApp( - openapi_path="openapi.yaml", + openapi_path="openapi.yaml", routes_module=routes, - title="My Service", + title="My Service", )

    Startup pipeline (fail-fast, in this order):

    @@ -1027,12 +1027,12 @@ 9
    from openapi_first.loader import load_openapi
     from openapi_first.client import OpenAPIClient
     
    -spec = load_openapi("openapi.yaml")
    +spec = load_openapi("openapi.yaml")
     client = OpenAPIClient(spec)
     
     response = client.get_health()
    -response = client.get_user(path_params={"user_id": 1})
    -response = client.create_user(body={"name": "Ada"})
    +response = client.get_user(path_params={"user_id": 1})
    +response = client.create_user(body={"name": "Ada"})
     

    How operations become methods:

      diff --git a/openapi-first/wiki/03_use_cases/01_quickstart/index.html b/openapi-first/wiki/03_use_cases/01_quickstart/index.html index b4dae84..f1ed671 100644 --- a/openapi-first/wiki/03_use_cases/01_quickstart/index.html +++ b/openapi-first/wiki/03_use_cases/01_quickstart/index.html @@ -858,7 +858,7 @@ schema: type: string responses: - "200": + "200": description: A greeting content: application/json: @@ -877,8 +877,8 @@ 3 4
      # routes.py
       def get_greeting(name: str) -> dict:
      -    """Return a greeting for the given name."""
      -    return {"greeting": f"Hello, {name}!"}
      +    """Return a greeting for the given name."""
      +    return {"greeting": f"Hello, {name}!"}
       

      If a handler is missing at startup, the app refuses to boot (MissingOperationHandler) — the fail-fast guarantee catches contract drift immediately.


      @@ -897,9 +897,9 @@
      import routes app = OpenAPIFirstApp( - openapi_path="openapi.yaml", + openapi_path="openapi.yaml", routes_module=routes, - title="Greeting Service", + title="Greeting Service", )

      Run it:

      @@ -922,12 +922,12 @@ from openapi_first.loader import load_openapi from openapi_first.client import OpenAPIClient -spec = load_openapi("openapi.yaml") +spec = load_openapi("openapi.yaml") client = OpenAPIClient(spec) -response = client.get_greeting(path_params={"name": "Ada"}) +response = client.get_greeting(path_params={"name": "Ada"}) print(response.status_code) # 200 -print(response.json()) # {"greeting": "Hello, Ada!"} +print(response.json()) # {"greeting": "Hello, Ada!"}

      💡 6. Next Steps

      diff --git a/openapi-first/wiki/03_use_cases/02_templates/index.html b/openapi-first/wiki/03_use_cases/02_templates/index.html index 4bb8cc6..de64b40 100644 --- a/openapi-first/wiki/03_use_cases/02_templates/index.html +++ b/openapi-first/wiki/03_use_cases/02_templates/index.html @@ -899,7 +899,7 @@ 4
      pip install -e .
       uvicorn main:app
       curl http://localhost:8000/health
      -# → {"status": "ok"}
      +# → {"status": "ok"}
       

      2.2 crud_app — dict-based CRUD

      openapi-first scaffold crud_app my-crud-service
      diff --git a/openapi-first/wiki/03_use_cases/03_client/index.html b/openapi-first/wiki/03_use_cases/03_client/index.html
      index 0653ee7..79a3409 100644
      --- a/openapi-first/wiki/03_use_cases/03_client/index.html
      +++ b/openapi-first/wiki/03_use_cases/03_client/index.html
      @@ -832,11 +832,11 @@
       5
      from openapi_first.loader import load_openapi
       from openapi_first.client import OpenAPIClient
       
      -spec = load_openapi("openapi.yaml")
      +spec = load_openapi("openapi.yaml")
       client = OpenAPIClient(spec)
       

      The base URL comes from the spec's servers list (first entry) unless you pass base_url explicitly:

      -
      client = OpenAPIClient(spec, base_url="https://api.internal.myco/v1")
      +
      client = OpenAPIClient(spec, base_url="https://api.internal.myco/v1")
       

      You can also hand over a preconfigured httpx.Client (custom transport, TLS, retries):

      1
      @@ -930,18 +930,18 @@
       assert response.status_code == 200
       
       # Path parameter
      -response = client.get_item(path_params={"item_id": 3})
      +response = client.get_item(path_params={"item_id": 3})
       
       # Query parameters
      -response = client.list_items(query={"limit": 10, "offset": 20})
      +response = client.list_items(query={"limit": 10, "offset": 20})
       
       # JSON request body
      -response = client.create_item(body={"name": "Orange", "price": 0.8})
      +response = client.create_item(body={"name": "Orange", "price": 0.8})
       
       # Custom headers / timeout
       response = client.get_user(
      -    path_params={"user_id": 1},
      -    headers={"X-Internal-Key": "..."},
      +    path_params={"user_id": 1},
      +    headers={"X-Internal-Key": "..."},
           timeout=30,
       )
       
      @@ -982,7 +982,7 @@ 2 3
      # One directory, two processes
       uvicorn main:app --port 8000                                  # server
      -python -c "import asyncio; from client_script import run; asyncio.run(run())"  # client
      +python -c "import asyncio; from client_script import run; asyncio.run(run())"  # client
       

      Or the same client against a remote environment:

      1
      @@ -990,7 +990,7 @@
       3
       4
      client = OpenAPIClient(
           spec,
      -    base_url="https://staging.internal.myco",
      +    base_url="https://staging.internal.myco",
       )
       

      The URL is the only thing that changes between environments — the contract never does.

      diff --git a/openapi-first/wiki/03_use_cases/04_codegen/index.html b/openapi-first/wiki/03_use_cases/04_codegen/index.html index 082ea3b..1101686 100644 --- a/openapi-first/wiki/03_use_cases/04_codegen/index.html +++ b/openapi-first/wiki/03_use_cases/04_codegen/index.html @@ -655,12 +655,27 @@
    • - + - 🔁 3. Route Generation / Verification + 🔁 3. Route Generation + +
    • @@ -803,21 +818,21 @@

      Codegen — Generate Models & Routes From Your Spec

      -

      TL;DR — point openapi-first codegen at your spec and get models + routes scaffolds you can bind with one line. The codegen output is deterministic, and it's a starting point — not a maintained artifact.

      +

      TL;DR — point openapi-first models / openapi-first routes at your spec and get Pydantic models plus operation-matching route stubs. The output is deterministic and is a starting point — not a maintained artifact.


      🧭 1. Why Celebrate Codegen?

      Two pain points kill OpenAPI projects:

      1. The "write the spec, then write the same thing as Pydantic models" step — exactly where server/client param types drift (your route says item_id: int, your client sends a string…).
      2. -
      3. Writing the docs/spec catalog by hand, once you have more than a handful of operations.
      4. +
      5. Typing handler names by hand when bind_routes requires each operationId to resolve to a function of the exact same name.
      -

      Codegen collapses both. One command, one source file, same generated shapes everywhere.

      +

      Codegen collapses both. One command, one source file, the same shapes everywhere.


      🚀 2. Model Generation

      -
      openapi-first codegen models --module my_project.models --input openapi.yaml
      +
      openapi-first models openapi.yaml -o app/_gen/models.py
       
      -

      The generated model module mirrors the spec's components.schemas by name:

      +

      The generated module mirrors the spec's components.schemas by name:

      @@ -835,33 +850,61 @@ - - - -
      class Item(BaseModel)
      every $ref (schema)a type: ClassVar alias ✓
      required + type from schema Pydantic Field(...) / type hints ✓
      -

      Never hand-edit those files. If the spec changes, regenerate — just like you'd re-run cargo build after editing Cargo.toml.

      +

      Never hand-edit those files. If the spec changes, regenerate — just like re-running a compiler after editing source.

      Why constructor-time validation still applies

      Codegen doesn't change the design: the generated models are plain Pydantic, and the client/server still validate against the spec at startup. Codegen is a convenience accelerator on top of the fail-fast guarantees in 04 – Design and 06 – Error Handling.

      +
      +

      Wire model vs. domain model. Generated models are the wire contract. Keep them separate from your persisted/domain model (see the standards CRUD Template): give the wire schema an id: str, but model the Mongo document so the id is optional.

      +

      -

      🔁 3. Route Generation / Verification

      +

      🔁 3. Route Generation

      1
       2
      -3
      -4
      -5
      # dry-run verification against your routes module
      -openapi-first codegen routes --module my_routes --input openapi.yaml --check
      -
      -# scaffold an operation skeleton (generates the handler with a TODO)
      -openapi-first codegen routes --module my_routes --input openapi.yaml
      +3
      openapi-first routes openapi.yaml -o app/interfaces \
      +  --use-models --models-module app._gen.models \
      +  --inject-request --package
       
      -

      Why bother? Because bind_routes (in 02 – Components) needs an operationId → handler exactly matching the spec. Codegen guarantees you never type a handler name wrong — it wears the same operationId as the spec says.

      +

      One file per resource (derived from the first path segment). Flags:

      + + + + + + + + + + + + + + + + + + + + + + + + + +
      FlagEffect
      --use-modelsImport request-body $ref models from --models-module
      --models-module MODDotted module for those imports (default models)
      --inject-requestPrepend request: Request to every handler (DI convention)
      --packageEmit <resource>/routes.py packages (with __init__.py) instead of flat <resource>.py
      +

      Why bother? Because bind_routes (in 02 – Components) needs an operationId → handler exactly matching the spec. Codegen guarantees you never type a handler name wrong — it wears the same operationId the spec declares.

      +

      Resource names and extra media types

      +
        +
      • A path such as /fetch-requests yields a sanitized module name fetch_requests.py (a raw hyphen could never be imported).
      • +
      • multipart/form-data and application/x-www-form-urlencoded bodies expand to one parameter per schema property: UploadFile = File(...) for format: binary, Form(...) otherwise — not an opaque payload: dict. Fields are required only when listed in the schema's required.
      • +
      +

      The stubs raise NotImplementedError; fill each body in (typically by resolving a service from request.app.state).

      -

      Note: codegen is build-time tooling. It does not run at runtime, and templates (the Bake-your-stuff section of 02 – Templates) make scaffolding a server out-of-the-box even simpler for greenfield projects.

      +

      Note: models / routes are build-time tooling. They do not run at runtime, and templates (the Bake-your-stuff section of 02 – Templates) scaffold a working server out-of-the-box for greenfield projects.


      🧪 4. Idempotent Output

      diff --git a/openapi-first/wiki/05_security/index.html b/openapi-first/wiki/05_security/index.html index 2c66d09..56b8584 100644 --- a/openapi-first/wiki/05_security/index.html +++ b/openapi-first/wiki/05_security/index.html @@ -868,8 +868,8 @@
      type: http scheme: bearer bearerFormat: JWT - x-server-url: "{AUTH_SERVER_URL}" - x-introspect-path: "/introspect" + x-server-url: "{AUTH_SERVER_URL}" + x-introspect-path: "/introspect"

      x-server-url and x-introspect-path are library extensions that point at a JWT introspection endpoint (see Introspection).

      1.2 security — per-operation (or global) requirements

      @@ -965,12 +965,12 @@ from openapi_first.app import OpenAPIFirstApp import routes -spec = load_openapi("openapi.yaml") +spec = load_openapi("openapi.yaml") schemes = parse_security_schemes(spec) security_deps = make_security_dependencies(spec, schemes) app = OpenAPIFirstApp( - openapi_path="openapi.yaml", + openapi_path="openapi.yaml", routes_module=routes, ) @@ -993,13 +993,13 @@ def test_unauthenticated_is_401(app, overrides): with TestClient(app) as client: - r = client.get("/pets") + r = client.get("/pets") assert r.status_code == 401 def test_invalid_token_using_fake_introspector(app): # Point x-introspect-path at a stub uvicorn/TestServer returning active:false with TestClient(app) as client: - r = client.get("/pets", headers={"Authorization": "Bearer nope"}) + r = client.get("/pets", headers={"Authorization": "Bearer nope"}) assert r.status_code == 401

      See Testing for the full recipe, including how tests stub the introspection server.

      diff --git a/openapi-first/wiki/06_error_handling/index.html b/openapi-first/wiki/06_error_handling/index.html index 8835188..bf19725 100644 --- a/openapi-first/wiki/06_error_handling/index.html +++ b/openapi-first/wiki/06_error_handling/index.html @@ -908,7 +908,7 @@

      2.3 At call time (client)

      Runtime transport errors surface as httpx exceptions (httpx.RequestError family), not swallowed or remapped. Missing required args fail before any HTTP request is made:

      1
      -2
      client.get_user(path_params={"user_id": ...})     # OK
      +2
      client.get_user(path_params={"user_id": ...})     # OK
       client.get_user()                                  # ValueError — user_id required
       

      @@ -934,7 +934,7 @@ 17
      from fastapi import HTTPException
       
       def get_item(item_id: int):
      -    """Retrieve an item by ID.
      +    """Retrieve an item by ID.
       
           Implements the OpenAPI operation ``get_item``.
       
      @@ -943,11 +943,11 @@
       
           Raises:
               HTTPException: If the item does not exist (404).
      -    """
      +    """
           try:
               return _get_item(item_id)
           except KeyError:
      -        raise HTTPException(status_code=404, detail="Item not found")
      +        raise HTTPException(status_code=404, detail="Item not found")
       

      3.2 Client-side

      1
      @@ -958,7 +958,7 @@
       6
      import httpx
       
       try:
      -    response = client.get_item(path_params={"item_id": 1})
      +    response = client.get_item(path_params={"item_id": 1})
       except httpx.HTTPStatusError as exc:
           ...  # 4xx/5xx from the server
       
      diff --git a/openapi-first/wiki/07_testing/index.html b/openapi-first/wiki/07_testing/index.html index 95e8823..f921d4b 100644 --- a/openapi-first/wiki/07_testing/index.html +++ b/openapi-first/wiki/07_testing/index.html @@ -885,8 +885,8 @@ 17
      # conftest.py (abridged)
       @pytest.fixture
       def spec_file(tmp_path):
      -    path = tmp_path / "openapi.json"
      -    path.write_text(json.dumps(SPEC), encoding="utf-8")
      +    path = tmp_path / "openapi.json"
      +    path.write_text(json.dumps(SPEC), encoding="utf-8")
           return str(path)
       
       @pytest.fixture
      @@ -909,7 +909,7 @@
       4
      def test_app_routes_served(spec_file):
           app = OpenAPIFirstApp(openapi_path=spec_file, routes_module=routes)
           client = TestClient(app)
      -    assert client.get("/health").json() == {"status": "ok"}
      +    assert client.get("/health").json() == {"status": "ok"}
       

      3.2 Fail-fast (the heart)

      1
      @@ -923,7 +923,7 @@
       2
       3
      def test_loader_invalid_spec_fails():
           with pytest.raises(OpenAPISpecLoadError):
      -        load_openapi("broken.yaml")
      +        load_openapi("broken.yaml")
       

      3.4 Client contract drift

      1
      diff --git a/openapi-first/wiki/search/search_index.json b/openapi-first/wiki/search/search_index.json
      index cd490d3..87f9436 100644
      --- a/openapi-first/wiki/search/search_index.json
      +++ b/openapi-first/wiki/search/search_index.json
      @@ -1 +1 @@
      -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"\ud83e\udde9 openapi-first \u2014 OpenAPI as the Single Source of Truth","text":"

      openapi-first is a small, strict library for OpenAPI-first FastAPI bootstrapping: the OpenAPI document is the application contract \u2014 not an afterthought generated from decorators)Skip. Routes, schemas, security, and even the HTTP client are all derived from one specification.

      Doc model: this wiki is written for humans \u2014 how-to guides, examples, and usage recipes. The authoritative API contracts live in the code (docstrings) and the machine-readable bundle under docs/mcp/.

      "},{"location":"#key-features","title":"\ud83d\ude80 Key Features","text":"
      • \ud83d\udcdc One spec, two sides \u2014 the same OpenAPI document boots both a FastAPI server (OpenAPIFirstApp) and a strict HTTP client (OpenAPIClient)
      • \ud83d\udd17 operationId binding \u2014 handler functions are bound to routes purely by operationId; no routing decorators
      • \ud83e\uddf1 Fail-fast contracts \u2014 startup and client construction fail loudly when a handler, operation, or security scheme is missing
      • \ud83d\udee1\ufe0f Spec-driven security \u2014 securitySchemes + per-operation security auto-injected as FastAPI dependencies (Bearer JWT introspection included)
      • \ud83e\udde0 Contract-first codegen \u2014 models (Pydantic) and routes (resource stubs) generated from a spec
      • \ud83e\udde9 Bundled templates \u2014 health_app, crud_app, model_app, vet_app scaffolds for the whole lifecycle, from /health to SSE + multi-resource CRUD
      "},{"location":"#installation","title":"\ud83d\udce6 Installation","text":"

      From your internal PyPI:

      pip install --extra-index-url https://$PYPI_USERNAME:$PYPI_PASSWORD@pip.aetoskia.com/simple openapi-first\n

      From local source:

      pip install -e .\n
      "},{"location":"#documentation-structure","title":"\ud83d\udcc1 Documentation Structure","text":"Section Description Overview The mental model: spec as contract, operationId binding, fail-fast guarantees Components app, binder, loader, client, errors, security, codegen, cli Use cases Step-by-step recipes \u00b7 01 \u2013 Quickstart Scaffold your first OpenAPI-first service \u00b7 02 \u2013 Templates The bundled application templates and how to use them \u00b7 03 \u2013 Client setup Build an operationId-driven HTTP client \u00b7 04 \u2013 Codegen Generate Pydantic models and route stubs from a spec Design Architecture, responsibilities, and startup pipeline Security securitySchemes, env resolution, and JWT introspection Error Handling The error hierarchy and when each error is raised Testing Test strategy, quality gates, and coverage"},{"location":"#related-resources","title":"\ud83d\udd17 Related Resources","text":"
      • Source Code: Gitea Repository
      • Internal PyPI: pip.aetoskia.com/simple/openapi-first
      • Drone CI: Auto-builds and publishes tagged releases, gated on black / ruff / mypy / pytest.

      \u00a9 Aetoskia Internal \u2014 openapi-first 0.0.6

      "},{"location":"01_overview/","title":"Overview \u2014 The OpenAPI-First Mental Model","text":"

      openapi-first inverts the usual FastAPI workflow. Instead of decorating routes in code and letting FastAPI invent an OpenAPI document for you, you write one OpenAPI document first and let the library assemble both the application and the client from it. The spec is the contract; code is the implementation.

      "},{"location":"01_overview/#1-the-mental-model","title":"\ud83e\udde0 1. The Mental Model","text":""},{"location":"01_overview/#11-one-source-of-truth","title":"1.1 One source of truth","text":"

      Your OpenAPI document (openapi.yaml or openapi.json) is the single authoritative contract:

      paths:\n  /health:\n    get:\n      operationId: get_health\n      responses:\n        \"200\":\n          description: OK\n          content:\n            application/json:\n              schema:\n                type: object\n

      Every route, method, parameter, schema, and security requirement lives here \u2014 and only here. Code never declares routes.

      "},{"location":"01_overview/#12-operationid-is-the-binding-key","title":"1.2 operationId is the binding key","text":"

      The only bridge between the spec and your Python code is the operationId. Each operation maps, by name, to exactly one plain callable:

      # routes.py\ndef get_health():\n    \"\"\"Health check operation handler.\"\"\"\n    return {\"status\": \"ok\"}\n

      openapi_first resolves operationId: get_health \u2192 routes.get_health and registers the route. No decorators, no @app.get, no routing metadata in code.

      Guarantees this binding provides:

      • \ud83d\udeab No undocumented route can exist \u2014 every route must be in the spec
      • \ud83d\udeab No spec operation can go unhandled \u2014 every operationId must resolve at startup
      • \ud83d\udd12 No auth can be bypassed \u2014 security is injected from securitySchemes + per-operation security, spec-driven
      • \ud83e\uddf1 No drift possible \u2014 the server and client are built from the same document
      "},{"location":"01_overview/#13-fail-fast-by-design","title":"1.3 Fail-fast by design","text":"

      Contract violations are detected at application startup (or client construction), never silently at request time:

      What goes wrong When it fails Invalid / unloadable spec load_openapi at startup Spec fails OpenAPI 3.x validation load_openapi at startup operationId missing a handler bind_routes at startup Operation declared but no operationId bind_routes at startup Missing / duplicate operationId in client OpenAPIClient(...) construction"},{"location":"01_overview/#2-what-it-looks-like","title":"\u2699\ufe0f 2. What It Looks Like","text":""},{"location":"01_overview/#21-scaffold-an-application","title":"2.1 Scaffold an application","text":"
      openapi-first scaffold health_app my-health-service\nopenapi-first scaffold --list\n

      scaffold copies a bundled template (verbatim \u2014 no code generation, no mutation) into a directory of your choice.

      "},{"location":"01_overview/#22-bootstrap-the-server","title":"2.2 Bootstrap the server","text":"
      # main.py\nfrom openapi_first.app import OpenAPIFirstApp\nimport routes\n\napp = OpenAPIFirstApp(\n    openapi_path=\"openapi.yaml\",\n    routes_module=routes,\n    title=\"My Service\",\n)\n

      Run with your FastAPI-compatible server (ASGI):

      uvicorn main:app --reload\n

      FastAPI itself drives the server; every route, response model, and security dependency comes from the spec. /openapi.json and Swagger UI always reflect the spec, byte-for-byte.

      "},{"location":"01_overview/#23-talk-to-it-with-the-client","title":"2.3 Talk to it with the client","text":"
      # client-side\nfrom openapi_first.loader import load_openapi\nfrom openapi_first.client import OpenAPIClient\n\nspec = load_openapi(\"openapi.yaml\")\nclient = OpenAPIClient(spec)\n\nresponse = client.get_health()          # operationId-driven call\nprint(response.status_code)             # 200\nprint(response.json())                  # {\"status\": \"ok\"}\n

      OpenAPIClient builds one callable per operationId from the same spec, so the client can never drift from the server.

      "},{"location":"01_overview/#3-where-things-live","title":"\ud83e\udde9 3. Where Things Live","text":"Concern Module Load + validate spec openapi_first.loader Boot FastAPI app openapi_first.app Bind routes by opId openapi_first.binder HTTP client openapi_first.client Error hierarchy openapi_first.errors Security dependencies openapi_first.security Pydantic model codegen openapi_first.codegen (uses datamodel_code_generator) Route stub codegen openapi_first.codegen_routes Models / routes CLI openapi_first.cli Bundled templates openapi_first.templates

      Jurisdictions:

      • loader, app, binder, client, errors, security are the library API surface \u2014 stable, tested, documented.
      • templates are copyable scaffolds \u2014 not part of the library API; excluded from lint/format/type gates, never imported at runtime.
      "},{"location":"01_overview/#4-the-non-goals","title":"\ud83d\udcc4 4. The Non-Goals","text":"

      openapi-first deliberately does not:

      • Parse decorators to generate an OpenAPI schema (that's default FastAPI behavior \u2014 the inverse)
      • Generate models from code at runtime (only at build time via CLI, from spec \u2192 Pydantic)
      • Validate request/response bodies against the spec at runtime (contract is enforced at startup / client construction; FastAPI + Pydantic handle runtime coercion)
      • Invent routing from path conventions \u2014 operationId binding only
      • Ship a production feature set in the bundled templates (they're demos/scaffolds: in-memory stores, no concurrency, no auth configured)
      "},{"location":"01_overview/#5-path-forward","title":"\ud83e\udded 5. Path Forward","text":"

      New here? Start with 01 \u2013 Quickstart. Want to copy a runnable app? Jump to 02 \u2013 Templates. Digging into internals? See Design.

      "},{"location":"01_overview/#related","title":"Related","text":"
      • 01 \u2013 Quickstart \u00b7 02 \u2013 Components \u00b7 01 \u2013 Overview
      "},{"location":"02_components/","title":"Components \u2014 What Ships in the Box","text":"

      The library is deliberately small. Everything you need to run an OpenAPI-first service and talk to it with a strict client fits in a handful of modules.

      "},{"location":"02_components/#1-module-map","title":"\ud83d\uddc2\ufe0f 1. Module Map","text":"Module Responsibility Import openapi_first.loader Load/validate the spec, resolve {ENV_VAR} load_openapi openapi_first.app OpenAPI-first FastAPI application bootstrap OpenAPIFirstApp openapi_first.binder Spec \u2192 route binding via operationId bind_routes openapi_first.client operationId-driven HTTP client OpenAPIClient openapi_first.errors Explicit error hierarchy OpenAPIFirstError, OpenAPIClientError, MissingOperationHandler openapi_first.security Security-dependency construction from the spec parse_security_schemes, make_security_dependencies openapi_first.codegen Pydantic model generation (build-time) generate_models openapi_first.codegen_routes Route-handler stub generation (build-time) generate_routes openapi_first.cli scaffold / models / routes command surface CLI entry point openapi_first.templates Copyable application templates (NOT library API) openapi-first scaffold"},{"location":"02_components/#2-loader-load-validate","title":"\u2699\ufe0f 2. loader \u2014 Load & Validate","text":"

      load_openapi(path: str | Path) -> dict[str, Any] (in openapi_first/loader.py).

      loader.py ensures a spec is real, readable, parseable, and schema-valid before anything else runs \u2014 a golden rule of fail-fast.

      from openapi_first.loader import load_openapi\n\nspec = load_openapi(\"openapi.yaml\")\n

      Behavior:

      • Accepts .json, .yaml, .yml (parsed by extension).
      • Runs strict OpenAPI 3.x validation (openapi-spec-validator) at load time.
      • Raises OpenAPISpecLoadError on: missing file, unparseable content, or spec-validation failure.
      • Does not modify, coerce, or \"fix\" the spec \u2014 it's a read-only gate.

      Env-var resolution lives at the security layer rather than the loader (see Security).

      "},{"location":"02_components/#3-app-the-application-bootstrap","title":"\ud83e\uddec 3. app \u2014 The Application Bootstrap","text":"

      OpenAPIFirstApp (in openapi_first/app.py) is a FastAPI subclass that replaces manual route registration with OpenAPI-driven binding.

      from openapi_first.app import OpenAPIFirstApp\nimport routes\n\napp = OpenAPIFirstApp(\n    openapi_path=\"openapi.yaml\",\n    routes_module=routes,\n    title=\"My Service\",\n)\n

      Startup pipeline (fail-fast, in this order):

      1. Load the spec (.yaml/.json).
      2. Validate it against OpenAPI 3.x schema.
      3. Parse securitySchemes and per-operation security.
      4. Build per-route security dependencies.
      5. Bind every path/method \u2192 handler by operationId; a missing handler, a missing operationId on a declared operation, or an unbound operation raises at startup.

      Guarantees:

      • Every route has a spec declaration (no undocumented routes).
      • Every spec operation has a handler (no unhandled operations).
      • Auth enforcement is spec-driven, not hand-wired.
      • /openapi.json + Swagger UI always reflect the provided spec.

      Keyword arguments beyond openapi_path / routes_module pass straight through to fastapi.FastAPI (it's a subclass \u2014 title, version, middleware, lifespan, \u2026 all work).

      "},{"location":"02_components/#4-binder-spec-route-binding","title":"\ud83d\udd17 4. binder \u2014 Spec \u2192 Route Binding","text":"

      bind_routes(app, spec, routes_module, security_deps=None) -> None (in openapi_first/binder.py).

      This is the heart of the OpenAPI-first guarantee. For each path + HTTP method in the spec:

      1. Reads the operation's operationId.
      2. Looks up routes_module.<operationId> \u2014 a plain callable.
      3. Registers a FastAPI APIRoute bound to that handler, injecting Depends(...) for any matching security requirements.

      Failures are explicit and early:

      Condition Raised No operationId on an operation MissingOperationHandler operationId has no handler function MissingOperationHandler A path/method declared but unbound MissingOperationHandler

      Handlers stay framework-agnostic: they're plain functions (payload, id, response) named after operationIds \u2014 no decorators, no routing metadata.

      "},{"location":"02_components/#5-client-the-other-side-of-the-contract","title":"\ud83d\udce1 5. client \u2014 The Other Side of the Contract","text":"

      OpenAPIClient(spec, base_url=None, client=None) (in openapi_first/client.py).

      The same spec that builds the server builds its client \u2014 one callable per operationId, keyed by name:

      from openapi_first.loader import load_openapi\nfrom openapi_first.client import OpenAPIClient\n\nspec = load_openapi(\"openapi.yaml\")\nclient = OpenAPIClient(spec)\n\nresponse = client.get_health()\nresponse = client.get_user(path_params={\"user_id\": 1})\nresponse = client.create_user(body={\"name\": \"Ada\"})\n

      How operations become methods:

      • Each operationId \u2192 a dynamically-built callable on the client.
      • Path parameters \u2192 path_params={\"user_id\": 1}.
      • Request body \u2192 body={...} (JSON) or raw content for non-JSON media types.
      • Query/headers \u2192 query= / headers=.
      • Returns the raw httpx.Response (no implicit deserialization, no hidden schema inference).

      Client-construction guarantees (fail-fast, same philosophy as the app):

      • Spec must declare at least one servers entry (base_url falls back to it).
      • Spec must have paths.
      • Every operation must have a unique operationId.
      • Required params must match the spec at call time.

      Construction errors raise OpenAPIClientError; request-time errors surface as httpx exceptions with operationId context.

      "},{"location":"02_components/#6-errors-explicit-error-hierarchy","title":"\ud83d\udca5 6. errors \u2014 Explicit Error Hierarchy","text":"

      Everything raised by openapi-first derives from OpenAPIFirstError (in openapi_first/errors.py), so callers can handle first-party failures with a single except:

      • OpenAPIFirstError \u2014 base (comparable / aims to be picklable share).
      • OpenAPIClientError \u2014 client-side contract violations.
      • MissingOperationHandler \u2014 spec declares an operation whose handler is missing or unresolvable; carries path, method, and optional operationId.

      See Error Handling for the full table.

      "},{"location":"02_components/#7-security-auth-from-the-spec","title":"\ud83d\udee1\ufe0f 7. security \u2014 Auth from the Spec","text":"

      security.py turns an OpenAPI securitySchemes section into FastAPI Depends(...) objects \u2014 no manual middleware.

      • parse_security_schemes(spec) -> dict[str, dict] \u2014 collects schemes, resolving {ENV_VAR} placeholders.
      • make_security_dependencies(spec, security_schemes) -> dict[str, list[Depends]] \u2014 builds METHOD:/path \u2192 dependency list from per-operation security, falling back to top-level security.

      Supported scheme types (extensible):

      • type: http, scheme: bearer \u2014 OpenAPIFirstSecurityDependency(HTTPBearer). With x-introspect-path/x-server-url, it introspects the JWT against an auth service; optionally sets request.state.user. Without introspection, it validates token presence and stores it on request.state.token.
      • API-key style schemes via the same resolution path.

      Key properties: resolution is per-operation, env placeholders resolve once at startup, and dependencies are injected by binder \u2014 your handlers never mention security.

      "},{"location":"02_components/#8-codegen-cli-build-time-tooling","title":"\ud83c\udfed 8. codegen & cli \u2014 Build-Time Tooling","text":"

      Generation is strictly build-time \u2014 spec \u2192 code, run once by a developer, committed:

      "},{"location":"02_components/#openapi-first-models-spec-o-file","title":"openapi-first models <spec> -o <file>","text":"

      Generates Pydantic models from spec schemas (wraps datamodel_code_generator):

      # cli path\nopenapi-first models openapi.yaml -o app/models.py\n
      "},{"location":"02_components/#openapi-first-routes-spec-o-dir-use-models-models-module-models","title":"openapi-first routes <spec> -o <dir> [--use-models] [--models-module models]","text":"

      Generates one routes_<resource>.py stub file per resource, with NotImplementedError handlers bound to operationIds (optionally importing your generated models):

      openapi-first routes openapi.yaml -o app/routes --use-models\n
      "},{"location":"02_components/#openapi-first-scaffold-template-path","title":"openapi-first scaffold <template> [path]","text":"

      Copies a bundled application template verbatim (see Templates).

      "},{"location":"02_components/#9-templates-copyable-applications","title":"\ud83e\uddf1 9. templates \u2014 Copyable Applications","text":"

      Four bundled, runnable applications live under openapi_first/templates/: health_app, crud_app, model_app, and vet_app. They are not part of the library API \u2014 no lint/format/type gates, never imported at runtime. They exist to be copied via:

      openapi-first scaffold <template> [target-dir]\nopenapi-first scaffold --list\n
      "},{"location":"02_components/#related","title":"\ud83d\udd17 Related","text":"
      • 01 \u2013 Overview \u00b7 02 \u2013 Templates \u00b7 03 \u2013 Security \u00b7 04 \u2013 Design
      "},{"location":"04_design/","title":"Design \u2014 Guarantees, Startup Pipeline, and the Contract Model","text":"

      This page is the architecture reference: the fail-fast startup surface, the guarantees that hold by construction, and the trade-offs baked into every decision.

      "},{"location":"04_design/#1-the-startup-pipeline","title":"\ud83c\udfd7\ufe0f 1. The Startup Pipeline","text":"

      Everything happens once, eagerly, at construction time \u2014 for the app at OpenAPIFirstApp(...), for the client at OpenAPIClient(...). There is no lazy loading, no deferred binding, no \"it'll work on first request\".

      openapi.yaml \u2500\u2500\u25ba loader.load_openapi\n                     \u251c\u2500 parse (json/yaml by extension)\n                     \u251c\u2500 validate (strict OpenAPI 3.x validator)\n                     \u2514\u2500\u25ba dict                       \u2500\u2500\u25b6 OpenAPISpecLoadError\n                             \u2502\nOpenAPIFirstApp(openapi_path=..., routes_module=routes)\n    \u251c\u2500 1. load + validate spec (loader)\n    \u251c\u2500 2. parse securitySchemes (security)\n    \u251c\u2500 3. make_security_dependencies (security)\n    \u251c\u2500 4. bind_routes: every operationId \u2500\u2500\u25ba routes.<operationId>\n    \u2502        \u2514\u2500 missing op / missing handler \u2500\u2500\u25ba MissingOperationHandler\n    \u2514\u2500\u25ba FastAPI app: routes registry + /openapi.json + Swagger UI\n

      Client \u2014 same spec, same guarantees:

      OpenAPIClient(spec)\n    \u251c\u2500 require servers[]\n    \u251c\u2500 require paths\n    \u251c\u2500 one callable per operationId (client.<operationId>)\n    \u2502    \u2514\u2500 missing / duplicate opId \u2500\u2500\u25ba OpenAPIClientError\n    \u2514\u2500\u25ba ready\n
      "},{"location":"04_design/#2-guarantees-that-hold-by-construction","title":"\ud83d\udcdc 2. Guarantees That Hold by Construction","text":"

      These are not conventions \u2014 they are enforced at startup or client construction:

      1. Every route is spec-declared. Routes are registered only from paths; there is no decorator-driven or implicit routing.
      2. Every operation is handled. Each operationId resolves to an exact handler name in routes_module; a missing one aborts bootstrap.
      3. Every operation has an operationId. An operation without one cannot be bound and fails fast.
      4. Client and server use the same spec. One document drives both sides, so they cannot drift.
      5. Auth is spec-driven. security dependencies come from securitySchemes + per-operation security; no manual middleware.
      6. Spec is valid before use. Invalid, malformed, or unloadable specs are rejected at load, not at first request.
      "},{"location":"04_design/#the-startup-no-half-states-property","title":"The startup \"no half-states\" property","text":"

      Because binding, validation, and security resolution all run at construction, a booted app is a provably-complete app. If the contract is violated in any way, the process refuses to start \u2014 the failure is loud, immediate, and tells you exactly what to fix.

      "},{"location":"04_design/#3-component-responsibilities","title":"\ud83e\udde9 3. Component Responsibilities","text":"Module Owns Refuses to loader parse + validate the spec modify or \"fix\" the spec app assemble FastAPI from spec + handlers routing decisions, decorators binder operationId \u2192 handler mapping infer handlers from paths client build one callable per operationId guess URLs, deserialize implicitly security schemes \u2192 FastAPI Depends manual middleware errors the error hierarchy swallow failures codegen build-time model/routes generation runtime codegen templates copyable scaffolds production data stores

      Rule of thumb for contributors: keep modules coercive (they raise if the contract is wrong) and narrow (one responsibility each). Pydoclint + the test suite keep that contract honest.

      "},{"location":"04_design/#4-design-trade-offs-accepted","title":"\u2696\ufe0f 4. Design Trade-offs (Accepted)","text":"Decision Chosen because What you give up Handlers are plain callables Framework-agnostic, testable, grep-able No decorator sugar Fail-fast at startup Drift caught in CI, not at 3am Slightly heavier boot Raw httpx.Response from client No hidden deserialization/validation You read .json() yourself Build-time codegen One-way generation, no runtime generator dep Spec must already be spec-valid Templates copy verbatim Scaffold, not magic Templates never updated in place"},{"location":"04_design/#related","title":"\ud83d\udd17 Related","text":"
      • 01 \u2013 Overview \u00b7 02 \u2013 Components \u00b7 05 \u2013 Security \u00b7 06 \u2013 Error Handling \u00b7 07 \u2013 Testing
      "},{"location":"05_security/","title":"Security \u2014 Auth Driven by the Spec, Not by Hand","text":"

      The most distinctive thing about openapi-first security is that you never write middleware or Depends(authenticate) calls yourself. All authentication is declared in the OpenAPI document and enforced automatically.

      "},{"location":"05_security/#1-where-security-lives","title":"\ud83d\uddfa\ufe0f 1. Where Security Lives","text":"

      Two places in the spec, both respected:

      "},{"location":"05_security/#11-componentssecurityschemes-the-inventory","title":"1.1 components.securitySchemes \u2014 the inventory","text":"
      components:\n  securitySchemes:\n    internalBearer:\n      type: http\n      scheme: bearer\n      bearerFormat: JWT\n      x-server-url: \"{AUTH_SERVER_URL}\"\n      x-introspect-path: \"/introspect\"\n

      x-server-url and x-introspect-path are library extensions that point at a JWT introspection endpoint (see Introspection).

      "},{"location":"05_security/#12-security-per-operation-or-global-requirements","title":"1.2 security \u2014 per-operation (or global) requirements","text":"
      security:\n  - internalBearer: []          # applied to every operation by default\n\npaths:\n  /pets:\n    get:\n      operationId: list_pets\n      # inherits: security: [{internalBearer: []}]\n    post:\n      operationId: create_pet\n      security: []              # override \u2014 public endpoint\n

      OpenAPI dynamic-scoping rules apply: an operation-level security replaces the global list (it does not merge).

      "},{"location":"05_security/#2-reading-the-spec","title":"\ud83d\udcd6 2. Reading the Spec","text":"

      security.py exposes two functions:

      Function Returns Purpose parse_security_schemes(spec) dict[str, dict] Collect schemes, resolve {ENV_VAR} placeholders make_security_dependencies(spec, schemes) dict[str, list[Depends]] (keyed METHOD:/path) Effective per-operation security deps

      Env placeholders of the form {NAME} are resolved once at startup from os.environ. This is how you avoid embedding credentials or auth-service URLs in the committed spec.

      "},{"location":"05_security/#3-the-bearer-dependency","title":"\ud83d\udd10 3. The Bearer Dependency","text":"

      make_security_dependencies builds a FastAPI dependency for type: http, scheme: bearer.

      Two modes, decided by whether an introspection endpoint is configured:

      "},{"location":"05_security/#31-with-introspection-x-introspect-path","title":"3.1 With introspection (x-introspect-path)","text":"
      • Reads Authorization: Bearer <token>
      • POSTs {\"token\": \"<token>\"} to {x-server-url}{x-introspect-path} synchronously via the bundled httpx client
      • Expects a response with active: true
      • On valid: {\"user\": ...} from the introspection body \u2192 request.state.user
      • On failure: 401 (missing/invalid token) or 503 (auth service unreachable)
      "},{"location":"05_security/#32-without-introspection","title":"3.2 Without introspection","text":"
      • Validates only that a Bearer token is present
      • Stores it on request.state.token; no remote call
      "},{"location":"05_security/#4-wiring-it-together","title":"\ud83d\udd17 4. Wiring It Together","text":"
      # server-side\nfrom openapi_first.loader import load_openapi\nfrom openapi_first.security import (\n    parse_security_schemes,\n    make_security_dependencies,\n)\nfrom openapi_first.app import OpenAPIFirstApp\nimport routes\n\nspec = load_openapi(\"openapi.yaml\")\nschemes = parse_security_schemes(spec)\nsecurity_deps = make_security_dependencies(spec, schemes)\n\napp = OpenAPIFirstApp(\n    openapi_path=\"openapi.yaml\",\n    routes_module=routes,\n)\n

      OpenAPIFirstApp already does this internally \u2014 the snippet above shows what it encapsulates (and what you use directly if you assemble the pieces by hand).

      "},{"location":"05_security/#5-testing-security","title":"\ud83e\uddea 5. Testing Security","text":"

      Because handlers are plain callables, security is the one place FastAPI's TestClient earns its keep:

      from fastapi.testclient import TestClient\n\ndef test_unauthenticated_is_401(app, overrides):\n    with TestClient(app) as client:\n        r = client.get(\"/pets\")\n        assert r.status_code == 401\n\ndef test_invalid_token_using_fake_introspector(app):\n    # Point x-introspect-path at a stub uvicorn/TestServer returning active:false\n    with TestClient(app) as client:\n        r = client.get(\"/pets\", headers={\"Authorization\": \"Bearer nope\"})\n        assert r.status_code == 401\n

      See Testing for the full recipe, including how tests stub the introspection server.

      "},{"location":"05_security/#7-common-patterns","title":"\ud83d\udee1\ufe0f 7. Common Patterns","text":"Pattern How Public endpoint security: [] on the operation Whole-spec auth top-level security: (applies to all) Route-specific scheme replace security on that operation Env-driven auth URL x-server-url: \"{AUTH_SERVER_URL}\" Offline token carry scheme without x-introspect-path \u2192 request.state.token Auth on the client side pass the token via client.<operationId>(headers={\"Authorization\": ...})"},{"location":"05_security/#related","title":"Related","text":"
      • 02 \u2013 Components \u00b7 04 \u2013 Design \u00b7 06 \u2013 Error Handling \u00b7 07 \u2013 Testing
      "},{"location":"06_error_handling/","title":"Error Handling \u2014 Fail Loud, Fail Early","text":"

      openapi-first treats errors as first-class contract documents: every failure mode is a named exception with a stable import pathhare, and every one surfaces as early as possible.

      "},{"location":"06_error_handling/#1-the-hierarchy","title":"\ud83e\uddec 1. The Hierarchy","text":"

      All errors derive from OpenAPIFirstError (in openapi_first/errors.py), so a single except OpenAPIFirstError catches every first-party failure:

      OpenAPIFirstError\n\u251c\u2500\u2500 OpenAPISpecError                     # spec-level problems\n\u2502   \u2514\u2500\u2500 OpenAPISpecLoadError             # load / parse / validation  (loader)\n\u251c\u2500\u2500 OpenAPIClientError                   # client-side contract issues (client)\n\u2514\u2500\u2500 MissingOperationHandler              # spec op with no handler     (binder)\n\n# Security / loader layers raise through OpenAPISpecError subclasses too\n
      Exception Module Raised when OpenAPISpecLoadError loader Path missing, file unreadable, YAML/JSON invalid, or spec fails OpenAPI 3.x validation OpenAPIClientError client No servers, no paths, missing/duplicate operationId, missing required params at construction MissingOperationHandler errors An operation is declared whose operationId has no matching handler in routes_module"},{"location":"06_error_handling/#2-when-things-fail","title":"\u23f1\ufe0f 2. When Things Fail","text":"

      The single most important rule: violations are eager, not lazy.

      "},{"location":"06_error_handling/#21-at-application-startup","title":"2.1 At application startup","text":"
      # openapi.yaml missing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba OpenAPISpecLoadError\n# operationId without a handler \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba MissingOperationHandler\n# operation with no operationId declared \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba MissingOperationHandler\n

      Because these raise during OpenAPIFirstApp(...) construction, CI catches them the moment a spec and its routes drift \u2014 before a single request is served.

      "},{"location":"06_error_handling/#22-at-client-construction","title":"2.2 At client construction","text":"
      OpenAPIClient(spec)  # fails fast, same philosophy\n
      • Spec with no servers \u2192 OpenAPIClientError
      • Spec with no paths \u2192 OpenAPIClientError
      • Duplicate operationIds \u2192 OpenAPIClientError (client methods must be unambiguous)
      • Operation missing operationId \u2192 OpenAPIClientError
      "},{"location":"06_error_handling/#23-at-call-time-client","title":"2.3 At call time (client)","text":"

      Runtime transport errors surface as httpx exceptions (httpx.RequestError family), not swallowed or remapped. Missing required args fail before any HTTP request is made:

      client.get_user(path_params={\"user_id\": ...})     # OK\nclient.get_user()                                  # ValueError \u2014 user_id required\n
      "},{"location":"06_error_handling/#3-handling-in-your-app","title":"\ud83e\uddf0 3. Handling in Your App","text":""},{"location":"06_error_handling/#31-server-side","title":"3.1 Server-side","text":"

      Handlers raise FastAPI HTTPException for expected operation-level failures (404/422), and the OperationId-binding errors only exist at startup:

      from fastapi import HTTPException\n\ndef get_item(item_id: int):\n    \"\"\"Retrieve an item by ID.\n\n    Implements the OpenAPI operation ``get_item``.\n\n    Args:\n        item_id (int): Identifier of the item.\n\n    Raises:\n        HTTPException: If the item does not exist (404).\n    \"\"\"\n    try:\n        return _get_item(item_id)\n    except KeyError:\n        raise HTTPException(status_code=404, detail=\"Item not found\")\n
      "},{"location":"06_error_handling/#32-client-side","title":"3.2 Client-side","text":"
      import httpx\n\ntry:\n    response = client.get_item(path_params={\"item_id\": 1})\nexcept httpx.HTTPStatusError as exc:\n    ...  # 4xx/5xx from the server\n

      httpx.HTTPStatusError isn't raised by the library \u2014 it's the standard httpx.raise_for_status() you can opt into per call. The library never masks a response code.

      "},{"location":"06_error_handling/#4-fail-fast-guarantees-recap","title":"\ud83d\udee1\ufe0f 4. Fail-Fast Guarantees Recap","text":"Layer You write The library guarantees Loader a spec path unreadable/invalid specs never reach your app Binder handler functions every operation must resolve, or the app won't start Client a spec every operationId becomes a callable; missing ones fail at construction Runtime handler code FastAPI + Pydantic handle coercion; contract checks already happened at startup

      None of these can silently degrade: a violation is an exception at construction, not a 500 at request time.

      "},{"location":"06_error_handling/#related","title":"Related","text":"
      • 01 \u2013 Overview \u00b7 05 \u2013 Security \u00b7 07 \u2013 Testing
      "},{"location":"07_testing/","title":"Testing \u2014 Smoke-First, Contract-First","text":"

      Everything openapi-first ships is tested against real specs, real handlers, and real HTTP through FastAPI's TestClient and the bundled templates. There are no fakes of the library itself.

      "},{"location":"07_testing/#1-suite-overview","title":"\ud83e\uddea 1. Suite Overview","text":"
      tests/\n\u251c\u2500\u2500 conftest.py        # fixtures: spec_file, routes_module, app, client\n\u251c\u2500\u2500 test_app.py        # OpenAPIFirstApp: routes served, overrides, fail-fast\n\u251c\u2500\u2500 test_binder.py     # bind_routes: opId resolution + missing-handler failures\n\u251c\u2500\u2500 test_loader.py     # load_openapi: json/yaml, env resolution, validation\n\u2514\u2500\u2500 test_client.py     # OpenAPIClient: opId\u2192callable, params, error cases\n

      Run with:

      pytest            # 23 tests, zero mocks of the library\npytest -q\npytest tests/test_loader.py\n
      "},{"location":"07_testing/#2-the-fixture-pattern","title":"\ud83c\udfd7\ufe0f 2. The Fixture Pattern","text":"
      # conftest.py (abridged)\n@pytest.fixture\ndef spec_file(tmp_path):\n    path = tmp_path / \"openapi.json\"\n    path.write_text(json.dumps(SPEC), encoding=\"utf-8\")\n    return str(path)\n\n@pytest.fixture\ndef app(spec_file):\n    return OpenAPIFirstApp(\n        openapi_path=spec_file,\n        routes_module=routes_module(),\n    )\n\n@pytest.fixture\ndef client(spec_file):\n    return OpenAPIClient(json.loads(Path(spec_file).read_text()))\n
      "},{"location":"07_testing/#3-whats-actually-asserted","title":"\ud83e\uddea 3. What's Actually Asserted","text":""},{"location":"07_testing/#31-app-level-smoke","title":"3.1 App-level smoke","text":"
      def test_app_routes_served(spec_file):\n    app = OpenAPIFirstApp(openapi_path=spec_file, routes_module=routes)\n    client = TestClient(app)\n    assert client.get(\"/health\").json() == {\"status\": \"ok\"}\n
      "},{"location":"07_testing/#32-fail-fast-the-heart","title":"3.2 Fail-fast (the heart)","text":"
      def test_app_missing_handler_fails_at_startup(spec_file):\n    with pytest.raises(MissingOperationHandler):\n        OpenAPIFirstApp(openapi_path=spec_file, routes_module=empty_routes)\n
      "},{"location":"07_testing/#33-loader-validation","title":"3.3 Loader validation","text":"
      def test_loader_invalid_spec_fails():\n    with pytest.raises(OpenAPISpecLoadError):\n        load_openapi(\"broken.yaml\")\n
      "},{"location":"07_testing/#34-client-contract-drift","title":"3.4 Client contract drift","text":"
      def test_client_duplicate_operation_id_raises():\n    with pytest.raises(OpenAPIClientError):\n        OpenAPIClient(dup_spec)\n
      "},{"location":"07_testing/#4-testing-the-templates","title":"\ud83c\udfdb\ufe0f 4. Testing the Templates","text":"

      Each bundled template ships its own test + in-memory store, so you get a runnable contract test the moment you scaffold:

      openapi-first scaffold crud_app my-service\ncd my-service\npytest -q\n

      test_crud_app.py / test_model_app.py / test_vet_app.py exercise the full CRUD surface through TestClient \u2014 including 201/204 status codes, 404s, and (in vet_app) SSE streaming via StreamingResponse.

      "},{"location":"07_testing/#5-quality-gates-ci","title":"\ud83d\udd79\ufe0f 5. Quality Gates (CI)","text":"

      The same gates the library itself must pass are what keep the docs honest:

      Gate Purpose black --check formatting parity ruff check lint hygiene mypy type safety (strict, --disable-error-code where intentional) pytest 23 tests, green coverage tracked via pytest-cov (HTML + XML + term)

      Run the whole gate locally:

      black --check openapi_first tests\nruff check openapi_first tests\nmypy openapi_first\npytest\n
      "},{"location":"07_testing/#6-testing-tips","title":"\ud83d\udca1 6. Testing Tips","text":"
      • Start from startup: assert OpenAPIFirstApp(...) raises for the broken contracts \u2014 those are your most valuable tests
      • Client \u2194 server: smoke a client against the same spec the app was built from \u2014 one spec, two sides, zero drift
      • Templates are scaffolds: their tests are copyable starting points, not canonical suites
      • No mocking of the library: exercise loader \u2192 binder \u2192 app \u2192 client as a real pipeline
      "},{"location":"07_testing/#related","title":"Related","text":"
      • 01 \u2013 Overview \u00b7 02 \u2013 Components \u00b7 04 \u2013 Error Handling
      "},{"location":"03_use_cases/01_quickstart/","title":"Use Case 1: Quickstart \u2014 Build Your First OpenAPI-First Service","text":"

      This guide walks you from an empty directory to a running, contract-driven service in a few minutes, then talks to it with the generated client.

      "},{"location":"03_use_cases/01_quickstart/#1-prerequisites","title":"\ud83d\udee0\ufe0f 1. Prerequisites","text":"
      • Python 3.10+
      • openapi-first installed (see Overview)
      • pip install \"fastapi[standard]\" (or uvicorn) to run the app
      "},{"location":"03_use_cases/01_quickstart/#2-write-the-openapi-document","title":"\ud83d\udcc4 2. Write the OpenAPI document","text":"

      OpenAPI comes first. Create openapi.yaml:

      openapi: 3.0.3\ninfo:\n  title: Greeting Service\n  version: 1.0.0\nservers:\n  - url: http://localhost:8000\npaths:\n  /greet/{name}:\n    get:\n      operationId: get_greeting\n      parameters:\n        - name: name\n          in: path\n          required: true\n          schema:\n            type: string\n      responses:\n        \"200\":\n          description: A greeting\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  greeting:\n                    type: string\n

      Key points: every operation needs operationId, and every route must exist only here.

      "},{"location":"03_use_cases/01_quickstart/#3-write-the-handlers","title":"\ud83e\uddd1\u200d\ud83d\udcbb 3. Write the handlers","text":"

      Create routes.py \u2014 plain functions, no decorators, named exactly like the operationIds:

      # routes.py\ndef get_greeting(name: str) -> dict:\n    \"\"\"Return a greeting for the given name.\"\"\"\n    return {\"greeting\": f\"Hello, {name}!\"}\n

      If a handler is missing at startup, the app refuses to boot (MissingOperationHandler) \u2014 the fail-fast guarantee catches contract drift immediately.

      "},{"location":"03_use_cases/01_quickstart/#4-bootstrap-the-app","title":"\ud83d\ude80 4. Bootstrap the app","text":"

      Create main.py:

      # main.py\nfrom openapi_first.app import OpenAPIFirstApp\nimport routes\n\napp = OpenAPIFirstApp(\n    openapi_path=\"openapi.yaml\",\n    routes_module=routes,\n    title=\"Greeting Service\",\n)\n

      Run it:

      uvicorn main:app --reload\n

      Visit http://localhost:8000/docs (Swagger UI) and http://localhost:8000/openapi.json \u2014 both are generated from your spec.

      "},{"location":"03_use_cases/01_quickstart/#5-call-it-with-the-client","title":"\ud83d\udce1 5. Call it with the client","text":"

      The same spec builds a strict client:

      # client.py\nfrom openapi_first.loader import load_openapi\nfrom openapi_first.client import OpenAPIClient\n\nspec = load_openapi(\"openapi.yaml\")\nclient = OpenAPIClient(spec)\n\nresponse = client.get_greeting(path_params={\"name\": \"Ada\"})\nprint(response.status_code)   # 200\nprint(response.json())        # {\"greeting\": \"Hello, Ada!\"}\n
      "},{"location":"03_use_cases/01_quickstart/#6-next-steps","title":"\ud83d\udca1 6. Next Steps","text":"
      • Copy a fuller example: 02 \u2013 Templates
      • Generate models/routes from a bigger spec: 04 \u2013 Codegen
      • Drive everything from a client: 03 \u2013 Client
      "},{"location":"03_use_cases/01_quickstart/#related","title":"Related","text":"
      • 01 \u2013 Overview \u00b7 02 \u2013 Components \u00b7 01 \u2013 Quickstart
      "},{"location":"03_use_cases/02_templates/","title":"Use Case 2: Templates \u2014 Copyable Reference Applications","text":"

      openapi-first ships four runnable, copyable applications under openapi_first/templates/. They are not part of the library API \u2014 they are bundled scaffold examples you copy into your own project and build on.

      "},{"location":"03_use_cases/02_templates/#1-what-templates-are","title":"\ud83c\udfac 1. What Templates Are","text":"

      A template is a complete, self-contained OpenAPI-first service:

      • A bundled directory inside openapi_first/templates/<name>/
      • Copyable verbatim \u2014 no code generation, no mutation \u2014 via the CLI
      • Each one demonstrates a specific set of OpenAPI-first behaviors and FastAPI features

      All templates share the same skeleton:

      <name>_app/\n\u251c\u2500\u2500 __init__.py      # explains the template + how to scaffold it\n\u251c\u2500\u2500 openapi.yaml     # the contract (source of truth)\n\u251c\u2500\u2500 main.py          # assembles OpenAPIFirstApp from the spec\n\u251c\u2500\u2500 routes.py        # operationId-bound handler functions\n\u2514\u2500\u2500 data.py          # in-memory data store (demo only)\n
      "},{"location":"03_use_cases/02_templates/#2-the-four-templates","title":"\ud83d\udccb 2. The Four Templates","text":""},{"location":"03_use_cases/02_templates/#21-health_app-minimal-liveness-probe","title":"2.1 health_app \u2014 minimal liveness probe","text":"
      openapi-first scaffold health_app\n# or into a custom directory:\nopenapi-first scaffold health_app my-health-service\n
      File Purpose openapi.yaml GET /health \u2192 operationId: get_health routes.py get_health() returns {\"status\": \"ok\"} main.py OpenAPIFirstApp(openapi_path=\"openapi.yaml\", routes_module=routes)

      Why it exists: the absolute minimal OpenAPI-first round trip \u2014 one operation, one handler, zero moving parts. The best starting point to internalize the mental model.

      Smoke test:

      pip install -e .\nuvicorn main:app\ncurl http://localhost:8000/health\n# \u2192 {\"status\": \"ok\"}\n
      "},{"location":"03_use_cases/02_templates/#22-crud_app-dict-based-crud","title":"2.2 crud_app \u2014 dict-based CRUD","text":"
      openapi-first scaffold crud_app my-crud-service\n
      File Purpose openapi.yaml Full CRUD over /items (list/get/create/update/delete) routes.py Handlers bound via operationIds list_items, get_item, create_item, update_item, delete_item data.py In-memory dict store with auto-incrementing id

      Behaviors you learn:

      • Explicit status codes \u2014 create_item/delete_item take response: Response and set 201/204; get_item/update_item raise HTTPException(404) on KeyError
      • Handlers as plain callables \u2014 no FastAPI decorators, routing comes solely from the spec
      • Mock data store \u2014 data.py is a copyable in-memory store, explicitly not production-ready
      "},{"location":"03_use_cases/02_templates/#23-model_app-pydantic-model-crud","title":"2.3 model_app \u2014 Pydantic model CRUD","text":"
      openapi-first scaffold model_app my-model-service\n
      File Purpose openapi.yaml Same CRUD surface, schemas reference models models.py Pydantic Item, ItemCreate, ItemBase (request/response models) routes.py Handlers type-annotated with the models; create_item sets 201 data.py In-memory store returning real model instances

      Behaviors you learn:

      • Pydantic request/response models \u2014 payloads validated and serialized via FastAPI
      • Same handler contracts \u2014 identical operationId set as crud_app, so the two are interchangeable
      • Models in the client too \u2014 the same spec drives OpenAPIClient body handling
      "},{"location":"03_use_cases/02_templates/#24-vet_app-the-full-featured-demo","title":"2.4 vet_app \u2014 the full-featured demo","text":"
      openapi-first scaffold vet_app my-vet-clinic\n
      File Purpose openapi.yaml Five resources (parents, vets, treatments, pets, appointments) + SSE + upload + discriminated unions models.py Pydantic models incl. discriminated unions (noteType literal fields) routes.py ~20 handlers across all resources, incl. pagination, filtering, photo upload, SSE streaming sse.py Server-Sent Events helper (StreamingResponse, per-pet subscriber queues, background asyncio workers) data.py Larger in-memory store (parents \u2192 vets \u2192 treatments \u2192 pets \u2192 appointments) main.py App + CORS + lifespan example

      Behaviors you learn \u2014 the advanced tier:

      • Discriminated unions \u2014 ProcedureNotes uses oneOf + discriminator.noteType mapping; Pydantic models use Literal[...] discriminator fields
      • SSE streaming \u2014 a GET /pets/{id}/actions operation streaming text/event-stream via StreamingResponse with background task workers
      • File upload \u2014 UploadFile handler setting a multi-part body
      • CORS + middleware \u2014 add_middleware(CORSMiddleware, ...) alongside the spec-driven setup
      • Response injection \u2014 handlers set 201/204 explicitly via injected Response
      "},{"location":"03_use_cases/02_templates/#3-cli-reference","title":"\ud83d\ude80 3. CLI Reference","text":"
      # List available templates\nopenapi-first scaffold --list\n\n# Copy a template into its default directory (template name, dashes)\nopenapi-first scaffold health_app\n\n# Copy into a custom target directory\nopenapi-first scaffold crud_app my-project/crud\n

      Protip: DEFAULT_TEMPLATE is health_app, so openapi-first scaffold with no template name scaffolds the health app.

      "},{"location":"03_use_cases/02_templates/#4-anatomy-of-a-scaffolded-service","title":"\ud83e\udde9 4. Anatomy of a Scaffolded Service","text":"

      After openapi-first scaffold health_app my-health-service, your directory contains a drop-in FastAPI service:

      my-health-service/\n\u251c\u2500\u2500 openapi.yaml   # THE contract\n\u251c\u2500\u2500 main.py        # `app = OpenAPIFirstApp(openapi_path=..., routes_module=routes)`\n\u2514\u2500\u2500 routes.py      # `def get_health(): ...`\n

      Run it:

      cd my-health-service\npip install -e .\nuvicorn main:app --reload\n

      /docs, /openapi.json, and every declared route now exist \u2014 all derived from openapi.yaml.

      "},{"location":"03_use_cases/02_templates/#5-production-disclaimer","title":"\u26a0\ufe0f 5. Production Disclaimer","text":"

      Templates use in-memory, non-persistent, non-concurrency-safe data stores. They are learning scaffolds \u2014 not production references. Swap in a real data layer (SQL/REDIS/object store) the moment you go beyond a demo.

      See the __init__.py of each template for detailed client examples, CLI examples, and design notes.

      "},{"location":"03_use_cases/02_templates/#related","title":"Related","text":"
      • 01 \u2013 Quickstart \u00b7 04 \u2013 Codegen \u00b7 02 \u2013 Components
      "},{"location":"03_use_cases/03_client/","title":"Use Case 3: The OperationId-Driven Client","text":"

      OpenAPIClient is the other side of the contract. It reads the same OpenAPI document the server runs on and exposes one callable per operationId \u2014 so \"client\" and \"server\" are two views of one truth.

      "},{"location":"03_use_cases/03_client/#1-before-you-start","title":"\ud83d\udd0d 1. Before You Start","text":"

      The client is httpx-based and returns raw httpx.Response objects \u2014 no magic deserialization, no hidden schema inference:

      • No response Pydantic models
      • No implicit URL construction (path params are explicit)
      • No hand-written requests.get(...) scattered through your code
      "},{"location":"03_use_cases/03_client/#2-constructing-the-client","title":"\ud83e\uddec 2. Constructing the Client","text":"
      from openapi_first.loader import load_openapi\nfrom openapi_first.client import OpenAPIClient\n\nspec = load_openapi(\"openapi.yaml\")\nclient = OpenAPIClient(spec)\n

      The base URL comes from the spec's servers list (first entry) unless you pass base_url explicitly:

      client = OpenAPIClient(spec, base_url=\"https://api.internal.myco/v1\")\n

      You can also hand over a preconfigured httpx.Client (custom transport, TLS, retries):

      import httpx\n\ntransport = httpx.HTTPTransport(retries=3)\nclient = OpenAPIClient(\n    spec,\n    client=httpx.Client(transport=transport),\n)\n
      "},{"location":"03_use_cases/03_client/#3-fail-fast-at-construction","title":"\ud83d\udca5 3. Fail-Fast at Construction","text":"

      OpenAPIClient(...) raises immediately if the contract is broken \u2014 you find out at startup, not on the first request:

      Violation Error Spec has no servers entry OpenAPIClientError Spec has no paths OpenAPIClientError Operation missing operationId OpenAPIClientError Duplicate operationId OpenAPIClientError Operation references unknown parameters OpenAPIClientError

      Missing required parameters fail at call time \u2014 pydoclint-grade strictness on the wire.

      "},{"location":"03_use_cases/03_client/#4-calling-operations","title":"\ud83d\udcde 4. Calling Operations","text":"

      Every operationId becomes a method. The call signature is uniform across the whole client:

      response = client.<operationId>(\n    *,\n    path_params: dict | None = None,\n    query: dict | None = None,\n    headers: dict | None = None,\n    body: Any | None = None,\n    timeout: float | None = None,\n) -> httpx.Response\n

      Concrete examples (from the crud_app / health_app templates):

      # No parameters \u2014 simplest\nresponse = client.get_health()\nassert response.status_code == 200\n\n# Path parameter\nresponse = client.get_item(path_params={\"item_id\": 3})\n\n# Query parameters\nresponse = client.list_items(query={\"limit\": 10, \"offset\": 20})\n\n# JSON request body\nresponse = client.create_item(body={\"name\": \"Orange\", \"price\": 0.8})\n\n# Custom headers / timeout\nresponse = client.get_user(\n    path_params={\"user_id\": 1},\n    headers={\"X-Internal-Key\": \"...\"},\n    timeout=30,\n)\n

      Returns: the raw httpx.Response, so status_code, .json(), .headers are all yours to inspect.

      "},{"location":"03_use_cases/03_client/#5-how-parameters-are-bound","title":"\ud83e\udde0 5. How Parameters Are Bound","text":"

      For each operation the client knows exactly where each parameter belongs:

      OpenAPI location Client kwarg in: path path_params[name] in: query query[name] in: header headers[name] requestBody body

      JSON media types are sent as json=; other media types as raw content=.

      "},{"location":"03_use_cases/03_client/#6-server-client-one-spec","title":"\ud83d\udd04 6. Server \u2194 Client, One Spec","text":"
      # One directory, two processes\nuvicorn main:app --port 8000                                  # server\npython -c \"import asyncio; from client_script import run; asyncio.run(run())\"  # client\n

      Or the same client against a remote environment:

      client = OpenAPIClient(\n    spec,\n    base_url=\"https://staging.internal.myco\",\n)\n

      The URL is the only thing that changes between environments \u2014 the contract never does.

      "},{"location":"03_use_cases/03_client/#7-operationid-as-the-api","title":"\u270d\ufe0f 7. OperationId as the API","text":"

      Because the client is operationId-driven:

      • Adding an operation = adding a method (and vice versa)
      • No operation can be \"forgotten\" by the client \u2014 it's constructed from the spec
      • operationId is the one name you remember; the HTTP verb/path is an implementation detail

      If an operationId you call doesn't exist, you get an AttributeError at construction-scan time \u2014 never a silent 404.

      "},{"location":"03_use_cases/03_client/#related","title":"Related","text":"
      • 01 \u2013 Overview \u00b7 01 \u2013 Quickstart \u00b7 02 \u2013 Templates
      "},{"location":"03_use_cases/04_codegen/","title":"Codegen \u2014 Generate Models & Routes From Your Spec","text":"

      TL;DR \u2014 point openapi-first codegen at your spec and get models + routes scaffolds you can bind with one line. The codegen output is deterministic, and it's a starting point \u2014 not a maintained artifact.

      "},{"location":"03_use_cases/04_codegen/#1-why-celebrate-codegen","title":"\ud83e\udded 1. Why Celebrate Codegen?","text":"

      Two pain points kill OpenAPI projects:

      1. The \"write the spec, then write the same thing as Pydantic models\" step \u2014 exactly where server/client param types drift (your route says item_id: int, your client sends a string\u2026).
      2. Writing the docs/spec catalog by hand, once you have more than a handful of operations.

      Codegen collapses both. One command, one source file, same generated shapes everywhere.

      "},{"location":"03_use_cases/04_codegen/#2-model-generation","title":"\ud83d\ude80 2. Model Generation","text":"
      openapi-first codegen models --module my_project.models --input openapi.yaml\n

      The generated model module mirrors the spec's components.schemas by name:

      Spec Generated components.schemas.User class User(BaseModel) components.schemas.Item class Item(BaseModel) every $ref (schema) a type: ClassVar alias \u2713 required + type from schema Pydantic Field(...) / type hints \u2713

      Never hand-edit those files. If the spec changes, regenerate \u2014 just like you'd re-run cargo build after editing Cargo.toml.

      "},{"location":"03_use_cases/04_codegen/#why-constructor-time-validation-still-applies","title":"Why constructor-time validation still applies","text":"

      Codegen doesn't change the design: the generated models are plain Pydantic, and the client/server still validate against the spec at startup. Codegen is a convenience accelerator on top of the fail-fast guarantees in 04 \u2013 Design and 06 \u2013 Error Handling.

      "},{"location":"03_use_cases/04_codegen/#3-route-generation-verification","title":"\ud83d\udd01 3. Route Generation / Verification","text":"
      # dry-run verification against your routes module\nopenapi-first codegen routes --module my_routes --input openapi.yaml --check\n\n# scaffold an operation skeleton (generates the handler with a TODO)\nopenapi-first codegen routes --module my_routes --input openapi.yaml\n

      Why bother? Because bind_routes (in 02 \u2013 Components) needs an operationId \u2192 handler exactly matching the spec. Codegen guarantees you never type a handler name wrong \u2014 it wears the same operationId as the spec says.

      Note: codegen is build-time tooling. It does not run at runtime, and templates (the Bake-your-stuff section of 02 \u2013 Templates) make scaffolding a server out-of-the-box even simpler for greenfield projects.

      "},{"location":"03_use_cases/04_codegen/#4-idempotent-output","title":"\ud83e\uddea 4. Idempotent Output","text":"

      Generated output is deterministic w.r.t. the spec: - Same spec \u2192 byte-identical files (unless you hand-edit \u2014 you won't) - Ordering follows spec declaration order - No timestamps, no machine names, no hidden randomness

      That determinism is what lets you git diff after a spec change and see exactly what moved.

      "},{"location":"03_use_cases/04_codegen/#related","title":"\ud83e\udde9 Related","text":"
      • 01 \u2013 Overview \u00b7 02 \u2013 Templates \u00b7 03 \u2013 Client \u00b7 04 \u2013 Design \u00b7 07 \u2013 Testing
      "}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"\ud83e\udde9 openapi-first \u2014 OpenAPI as the Single Source of Truth","text":"

      openapi-first is a small, strict library for OpenAPI-first FastAPI bootstrapping: the OpenAPI document is the application contract \u2014 not an afterthought generated from decorators)Skip. Routes, schemas, security, and even the HTTP client are all derived from one specification.

      Doc model: this wiki is written for humans \u2014 how-to guides, examples, and usage recipes. The authoritative API contracts live in the code (docstrings) and the machine-readable bundle under docs/mcp/.

      "},{"location":"#key-features","title":"\ud83d\ude80 Key Features","text":"
      • \ud83d\udcdc One spec, two sides \u2014 the same OpenAPI document boots both a FastAPI server (OpenAPIFirstApp) and a strict HTTP client (OpenAPIClient)
      • \ud83d\udd17 operationId binding \u2014 handler functions are bound to routes purely by operationId; no routing decorators
      • \ud83e\uddf1 Fail-fast contracts \u2014 startup and client construction fail loudly when a handler, operation, or security scheme is missing
      • \ud83d\udee1\ufe0f Spec-driven security \u2014 securitySchemes + per-operation security auto-injected as FastAPI dependencies (Bearer JWT introspection included)
      • \ud83e\udde0 Contract-first codegen \u2014 models (Pydantic) and routes (resource stubs) generated from a spec
      • \ud83e\udde9 Bundled templates \u2014 health_app, crud_app, model_app, vet_app scaffolds for the whole lifecycle, from /health to SSE + multi-resource CRUD
      "},{"location":"#installation","title":"\ud83d\udce6 Installation","text":"

      From your internal PyPI:

      pip install --extra-index-url https://$PYPI_USERNAME:$PYPI_PASSWORD@pip.aetoskia.com/simple openapi-first\n

      From local source:

      pip install -e .\n
      "},{"location":"#documentation-structure","title":"\ud83d\udcc1 Documentation Structure","text":"Section Description Overview The mental model: spec as contract, operationId binding, fail-fast guarantees Components app, binder, loader, client, errors, security, codegen, cli Use cases Step-by-step recipes \u00b7 01 \u2013 Quickstart Scaffold your first OpenAPI-first service \u00b7 02 \u2013 Templates The bundled application templates and how to use them \u00b7 03 \u2013 Client setup Build an operationId-driven HTTP client \u00b7 04 \u2013 Codegen Generate Pydantic models and route stubs from a spec Design Architecture, responsibilities, and startup pipeline Security securitySchemes, env resolution, and JWT introspection Error Handling The error hierarchy and when each error is raised Testing Test strategy, quality gates, and coverage"},{"location":"#related-resources","title":"\ud83d\udd17 Related Resources","text":"
      • Source Code: Gitea Repository
      • Internal PyPI: pip.aetoskia.com/simple/openapi-first
      • Drone CI: Auto-builds and publishes tagged releases, gated on black / ruff / mypy / pytest.

      \u00a9 Aetoskia Internal \u2014 openapi-first 0.0.6

      "},{"location":"01_overview/","title":"Overview \u2014 The OpenAPI-First Mental Model","text":"

      openapi-first inverts the usual FastAPI workflow. Instead of decorating routes in code and letting FastAPI invent an OpenAPI document for you, you write one OpenAPI document first and let the library assemble both the application and the client from it. The spec is the contract; code is the implementation.

      "},{"location":"01_overview/#1-the-mental-model","title":"\ud83e\udde0 1. The Mental Model","text":""},{"location":"01_overview/#11-one-source-of-truth","title":"1.1 One source of truth","text":"

      Your OpenAPI document (openapi.yaml or openapi.json) is the single authoritative contract:

      paths:\n  /health:\n    get:\n      operationId: get_health\n      responses:\n        \"200\":\n          description: OK\n          content:\n            application/json:\n              schema:\n                type: object\n

      Every route, method, parameter, schema, and security requirement lives here \u2014 and only here. Code never declares routes.

      "},{"location":"01_overview/#12-operationid-is-the-binding-key","title":"1.2 operationId is the binding key","text":"

      The only bridge between the spec and your Python code is the operationId. Each operation maps, by name, to exactly one plain callable:

      # routes.py\ndef get_health():\n    \"\"\"Health check operation handler.\"\"\"\n    return {\"status\": \"ok\"}\n

      openapi_first resolves operationId: get_health \u2192 routes.get_health and registers the route. No decorators, no @app.get, no routing metadata in code.

      Guarantees this binding provides:

      • \ud83d\udeab No undocumented route can exist \u2014 every route must be in the spec
      • \ud83d\udeab No spec operation can go unhandled \u2014 every operationId must resolve at startup
      • \ud83d\udd12 No auth can be bypassed \u2014 security is injected from securitySchemes + per-operation security, spec-driven
      • \ud83e\uddf1 No drift possible \u2014 the server and client are built from the same document
      "},{"location":"01_overview/#13-fail-fast-by-design","title":"1.3 Fail-fast by design","text":"

      Contract violations are detected at application startup (or client construction), never silently at request time:

      What goes wrong When it fails Invalid / unloadable spec load_openapi at startup Spec fails OpenAPI 3.x validation load_openapi at startup operationId missing a handler bind_routes at startup Operation declared but no operationId bind_routes at startup Missing / duplicate operationId in client OpenAPIClient(...) construction"},{"location":"01_overview/#2-what-it-looks-like","title":"\u2699\ufe0f 2. What It Looks Like","text":""},{"location":"01_overview/#21-scaffold-an-application","title":"2.1 Scaffold an application","text":"
      openapi-first scaffold health_app my-health-service\nopenapi-first scaffold --list\n

      scaffold copies a bundled template (verbatim \u2014 no code generation, no mutation) into a directory of your choice.

      "},{"location":"01_overview/#22-bootstrap-the-server","title":"2.2 Bootstrap the server","text":"
      # main.py\nfrom openapi_first.app import OpenAPIFirstApp\nimport routes\n\napp = OpenAPIFirstApp(\n    openapi_path=\"openapi.yaml\",\n    routes_module=routes,\n    title=\"My Service\",\n)\n

      Run with your FastAPI-compatible server (ASGI):

      uvicorn main:app --reload\n

      FastAPI itself drives the server; every route, response model, and security dependency comes from the spec. /openapi.json and Swagger UI always reflect the spec, byte-for-byte.

      "},{"location":"01_overview/#23-talk-to-it-with-the-client","title":"2.3 Talk to it with the client","text":"
      # client-side\nfrom openapi_first.loader import load_openapi\nfrom openapi_first.client import OpenAPIClient\n\nspec = load_openapi(\"openapi.yaml\")\nclient = OpenAPIClient(spec)\n\nresponse = client.get_health()          # operationId-driven call\nprint(response.status_code)             # 200\nprint(response.json())                  # {\"status\": \"ok\"}\n

      OpenAPIClient builds one callable per operationId from the same spec, so the client can never drift from the server.

      "},{"location":"01_overview/#3-where-things-live","title":"\ud83e\udde9 3. Where Things Live","text":"Concern Module Load + validate spec openapi_first.loader Boot FastAPI app openapi_first.app Bind routes by opId openapi_first.binder HTTP client openapi_first.client Error hierarchy openapi_first.errors Security dependencies openapi_first.security Pydantic model codegen openapi_first.codegen (uses datamodel_code_generator) Route stub codegen openapi_first.codegen_routes Models / routes CLI openapi_first.cli Bundled templates openapi_first.templates

      Jurisdictions:

      • loader, app, binder, client, errors, security are the library API surface \u2014 stable, tested, documented.
      • templates are copyable scaffolds \u2014 not part of the library API; excluded from lint/format/type gates, never imported at runtime.
      "},{"location":"01_overview/#4-the-non-goals","title":"\ud83d\udcc4 4. The Non-Goals","text":"

      openapi-first deliberately does not:

      • Parse decorators to generate an OpenAPI schema (that's default FastAPI behavior \u2014 the inverse)
      • Generate models from code at runtime (only at build time via CLI, from spec \u2192 Pydantic)
      • Validate request/response bodies against the spec at runtime (contract is enforced at startup / client construction; FastAPI + Pydantic handle runtime coercion)
      • Invent routing from path conventions \u2014 operationId binding only
      • Ship a production feature set in the bundled templates (they're demos/scaffolds: in-memory stores, no concurrency, no auth configured)
      "},{"location":"01_overview/#5-path-forward","title":"\ud83e\udded 5. Path Forward","text":"

      New here? Start with 01 \u2013 Quickstart. Want to copy a runnable app? Jump to 02 \u2013 Templates. Digging into internals? See Design.

      "},{"location":"01_overview/#related","title":"Related","text":"
      • 01 \u2013 Quickstart \u00b7 02 \u2013 Components \u00b7 01 \u2013 Overview
      "},{"location":"02_components/","title":"Components \u2014 What Ships in the Box","text":"

      The library is deliberately small. Everything you need to run an OpenAPI-first service and talk to it with a strict client fits in a handful of modules.

      "},{"location":"02_components/#1-module-map","title":"\ud83d\uddc2\ufe0f 1. Module Map","text":"Module Responsibility Import openapi_first.loader Load/validate the spec, resolve {ENV_VAR} load_openapi openapi_first.app OpenAPI-first FastAPI application bootstrap OpenAPIFirstApp openapi_first.binder Spec \u2192 route binding via operationId bind_routes openapi_first.client operationId-driven HTTP client OpenAPIClient openapi_first.errors Explicit error hierarchy OpenAPIFirstError, OpenAPIClientError, MissingOperationHandler openapi_first.security Security-dependency construction from the spec parse_security_schemes, make_security_dependencies openapi_first.codegen Pydantic model generation (build-time) generate_models openapi_first.codegen_routes Route-handler stub generation (build-time) generate_routes openapi_first.cli scaffold / models / routes command surface CLI entry point openapi_first.templates Copyable application templates (NOT library API) openapi-first scaffold"},{"location":"02_components/#2-loader-load-validate","title":"\u2699\ufe0f 2. loader \u2014 Load & Validate","text":"

      load_openapi(path: str | Path) -> dict[str, Any] (in openapi_first/loader.py).

      loader.py ensures a spec is real, readable, parseable, and schema-valid before anything else runs \u2014 a golden rule of fail-fast.

      from openapi_first.loader import load_openapi\n\nspec = load_openapi(\"openapi.yaml\")\n

      Behavior:

      • Accepts .json, .yaml, .yml (parsed by extension).
      • Runs strict OpenAPI 3.x validation (openapi-spec-validator) at load time.
      • Raises OpenAPISpecLoadError on: missing file, unparseable content, or spec-validation failure.
      • Does not modify, coerce, or \"fix\" the spec \u2014 it's a read-only gate.

      Env-var resolution lives at the security layer rather than the loader (see Security).

      "},{"location":"02_components/#3-app-the-application-bootstrap","title":"\ud83e\uddec 3. app \u2014 The Application Bootstrap","text":"

      OpenAPIFirstApp (in openapi_first/app.py) is a FastAPI subclass that replaces manual route registration with OpenAPI-driven binding.

      from openapi_first.app import OpenAPIFirstApp\nimport routes\n\napp = OpenAPIFirstApp(\n    openapi_path=\"openapi.yaml\",\n    routes_module=routes,\n    title=\"My Service\",\n)\n

      Startup pipeline (fail-fast, in this order):

      1. Load the spec (.yaml/.json).
      2. Validate it against OpenAPI 3.x schema.
      3. Parse securitySchemes and per-operation security.
      4. Build per-route security dependencies.
      5. Bind every path/method \u2192 handler by operationId; a missing handler, a missing operationId on a declared operation, or an unbound operation raises at startup.

      Guarantees:

      • Every route has a spec declaration (no undocumented routes).
      • Every spec operation has a handler (no unhandled operations).
      • Auth enforcement is spec-driven, not hand-wired.
      • /openapi.json + Swagger UI always reflect the provided spec.

      Keyword arguments beyond openapi_path / routes_module pass straight through to fastapi.FastAPI (it's a subclass \u2014 title, version, middleware, lifespan, \u2026 all work).

      "},{"location":"02_components/#4-binder-spec-route-binding","title":"\ud83d\udd17 4. binder \u2014 Spec \u2192 Route Binding","text":"

      bind_routes(app, spec, routes_module, security_deps=None) -> None (in openapi_first/binder.py).

      This is the heart of the OpenAPI-first guarantee. For each path + HTTP method in the spec:

      1. Reads the operation's operationId.
      2. Looks up routes_module.<operationId> \u2014 a plain callable.
      3. Registers a FastAPI APIRoute bound to that handler, injecting Depends(...) for any matching security requirements.

      Failures are explicit and early:

      Condition Raised No operationId on an operation MissingOperationHandler operationId has no handler function MissingOperationHandler A path/method declared but unbound MissingOperationHandler

      Handlers stay framework-agnostic: they're plain functions (payload, id, response) named after operationIds \u2014 no decorators, no routing metadata.

      "},{"location":"02_components/#5-client-the-other-side-of-the-contract","title":"\ud83d\udce1 5. client \u2014 The Other Side of the Contract","text":"

      OpenAPIClient(spec, base_url=None, client=None) (in openapi_first/client.py).

      The same spec that builds the server builds its client \u2014 one callable per operationId, keyed by name:

      from openapi_first.loader import load_openapi\nfrom openapi_first.client import OpenAPIClient\n\nspec = load_openapi(\"openapi.yaml\")\nclient = OpenAPIClient(spec)\n\nresponse = client.get_health()\nresponse = client.get_user(path_params={\"user_id\": 1})\nresponse = client.create_user(body={\"name\": \"Ada\"})\n

      How operations become methods:

      • Each operationId \u2192 a dynamically-built callable on the client.
      • Path parameters \u2192 path_params={\"user_id\": 1}.
      • Request body \u2192 body={...} (JSON) or raw content for non-JSON media types.
      • Query/headers \u2192 query= / headers=.
      • Returns the raw httpx.Response (no implicit deserialization, no hidden schema inference).

      Client-construction guarantees (fail-fast, same philosophy as the app):

      • Spec must declare at least one servers entry (base_url falls back to it).
      • Spec must have paths.
      • Every operation must have a unique operationId.
      • Required params must match the spec at call time.

      Construction errors raise OpenAPIClientError; request-time errors surface as httpx exceptions with operationId context.

      "},{"location":"02_components/#6-errors-explicit-error-hierarchy","title":"\ud83d\udca5 6. errors \u2014 Explicit Error Hierarchy","text":"

      Everything raised by openapi-first derives from OpenAPIFirstError (in openapi_first/errors.py), so callers can handle first-party failures with a single except:

      • OpenAPIFirstError \u2014 base (comparable / aims to be picklable share).
      • OpenAPIClientError \u2014 client-side contract violations.
      • MissingOperationHandler \u2014 spec declares an operation whose handler is missing or unresolvable; carries path, method, and optional operationId.

      See Error Handling for the full table.

      "},{"location":"02_components/#7-security-auth-from-the-spec","title":"\ud83d\udee1\ufe0f 7. security \u2014 Auth from the Spec","text":"

      security.py turns an OpenAPI securitySchemes section into FastAPI Depends(...) objects \u2014 no manual middleware.

      • parse_security_schemes(spec) -> dict[str, dict] \u2014 collects schemes, resolving {ENV_VAR} placeholders.
      • make_security_dependencies(spec, security_schemes) -> dict[str, list[Depends]] \u2014 builds METHOD:/path \u2192 dependency list from per-operation security, falling back to top-level security.

      Supported scheme types (extensible):

      • type: http, scheme: bearer \u2014 OpenAPIFirstSecurityDependency(HTTPBearer). With x-introspect-path/x-server-url, it introspects the JWT against an auth service; optionally sets request.state.user. Without introspection, it validates token presence and stores it on request.state.token.
      • API-key style schemes via the same resolution path.

      Key properties: resolution is per-operation, env placeholders resolve once at startup, and dependencies are injected by binder \u2014 your handlers never mention security.

      "},{"location":"02_components/#8-codegen-cli-build-time-tooling","title":"\ud83c\udfed 8. codegen & cli \u2014 Build-Time Tooling","text":"

      Generation is strictly build-time \u2014 spec \u2192 code, run once by a developer, committed:

      "},{"location":"02_components/#openapi-first-models-spec-o-file","title":"openapi-first models <spec> -o <file>","text":"

      Generates Pydantic models from spec schemas (wraps datamodel_code_generator):

      # cli path\nopenapi-first models openapi.yaml -o app/models.py\n
      "},{"location":"02_components/#openapi-first-routes-spec-o-dir-use-models-models-module-models","title":"openapi-first routes <spec> -o <dir> [--use-models] [--models-module models]","text":"

      Generates one routes_<resource>.py stub file per resource, with NotImplementedError handlers bound to operationIds (optionally importing your generated models):

      openapi-first routes openapi.yaml -o app/routes --use-models\n
      "},{"location":"02_components/#openapi-first-scaffold-template-path","title":"openapi-first scaffold <template> [path]","text":"

      Copies a bundled application template verbatim (see Templates).

      "},{"location":"02_components/#9-templates-copyable-applications","title":"\ud83e\uddf1 9. templates \u2014 Copyable Applications","text":"

      Four bundled, runnable applications live under openapi_first/templates/: health_app, crud_app, model_app, and vet_app. They are not part of the library API \u2014 no lint/format/type gates, never imported at runtime. They exist to be copied via:

      openapi-first scaffold <template> [target-dir]\nopenapi-first scaffold --list\n
      "},{"location":"02_components/#related","title":"\ud83d\udd17 Related","text":"
      • 01 \u2013 Overview \u00b7 02 \u2013 Templates \u00b7 03 \u2013 Security \u00b7 04 \u2013 Design
      "},{"location":"04_design/","title":"Design \u2014 Guarantees, Startup Pipeline, and the Contract Model","text":"

      This page is the architecture reference: the fail-fast startup surface, the guarantees that hold by construction, and the trade-offs baked into every decision.

      "},{"location":"04_design/#1-the-startup-pipeline","title":"\ud83c\udfd7\ufe0f 1. The Startup Pipeline","text":"

      Everything happens once, eagerly, at construction time \u2014 for the app at OpenAPIFirstApp(...), for the client at OpenAPIClient(...). There is no lazy loading, no deferred binding, no \"it'll work on first request\".

      openapi.yaml \u2500\u2500\u25ba loader.load_openapi\n                     \u251c\u2500 parse (json/yaml by extension)\n                     \u251c\u2500 validate (strict OpenAPI 3.x validator)\n                     \u2514\u2500\u25ba dict                       \u2500\u2500\u25b6 OpenAPISpecLoadError\n                             \u2502\nOpenAPIFirstApp(openapi_path=..., routes_module=routes)\n    \u251c\u2500 1. load + validate spec (loader)\n    \u251c\u2500 2. parse securitySchemes (security)\n    \u251c\u2500 3. make_security_dependencies (security)\n    \u251c\u2500 4. bind_routes: every operationId \u2500\u2500\u25ba routes.<operationId>\n    \u2502        \u2514\u2500 missing op / missing handler \u2500\u2500\u25ba MissingOperationHandler\n    \u2514\u2500\u25ba FastAPI app: routes registry + /openapi.json + Swagger UI\n

      Client \u2014 same spec, same guarantees:

      OpenAPIClient(spec)\n    \u251c\u2500 require servers[]\n    \u251c\u2500 require paths\n    \u251c\u2500 one callable per operationId (client.<operationId>)\n    \u2502    \u2514\u2500 missing / duplicate opId \u2500\u2500\u25ba OpenAPIClientError\n    \u2514\u2500\u25ba ready\n
      "},{"location":"04_design/#2-guarantees-that-hold-by-construction","title":"\ud83d\udcdc 2. Guarantees That Hold by Construction","text":"

      These are not conventions \u2014 they are enforced at startup or client construction:

      1. Every route is spec-declared. Routes are registered only from paths; there is no decorator-driven or implicit routing.
      2. Every operation is handled. Each operationId resolves to an exact handler name in routes_module; a missing one aborts bootstrap.
      3. Every operation has an operationId. An operation without one cannot be bound and fails fast.
      4. Client and server use the same spec. One document drives both sides, so they cannot drift.
      5. Auth is spec-driven. security dependencies come from securitySchemes + per-operation security; no manual middleware.
      6. Spec is valid before use. Invalid, malformed, or unloadable specs are rejected at load, not at first request.
      "},{"location":"04_design/#the-startup-no-half-states-property","title":"The startup \"no half-states\" property","text":"

      Because binding, validation, and security resolution all run at construction, a booted app is a provably-complete app. If the contract is violated in any way, the process refuses to start \u2014 the failure is loud, immediate, and tells you exactly what to fix.

      "},{"location":"04_design/#3-component-responsibilities","title":"\ud83e\udde9 3. Component Responsibilities","text":"Module Owns Refuses to loader parse + validate the spec modify or \"fix\" the spec app assemble FastAPI from spec + handlers routing decisions, decorators binder operationId \u2192 handler mapping infer handlers from paths client build one callable per operationId guess URLs, deserialize implicitly security schemes \u2192 FastAPI Depends manual middleware errors the error hierarchy swallow failures codegen build-time model/routes generation runtime codegen templates copyable scaffolds production data stores

      Rule of thumb for contributors: keep modules coercive (they raise if the contract is wrong) and narrow (one responsibility each). Pydoclint + the test suite keep that contract honest.

      "},{"location":"04_design/#4-design-trade-offs-accepted","title":"\u2696\ufe0f 4. Design Trade-offs (Accepted)","text":"Decision Chosen because What you give up Handlers are plain callables Framework-agnostic, testable, grep-able No decorator sugar Fail-fast at startup Drift caught in CI, not at 3am Slightly heavier boot Raw httpx.Response from client No hidden deserialization/validation You read .json() yourself Build-time codegen One-way generation, no runtime generator dep Spec must already be spec-valid Templates copy verbatim Scaffold, not magic Templates never updated in place"},{"location":"04_design/#related","title":"\ud83d\udd17 Related","text":"
      • 01 \u2013 Overview \u00b7 02 \u2013 Components \u00b7 05 \u2013 Security \u00b7 06 \u2013 Error Handling \u00b7 07 \u2013 Testing
      "},{"location":"05_security/","title":"Security \u2014 Auth Driven by the Spec, Not by Hand","text":"

      The most distinctive thing about openapi-first security is that you never write middleware or Depends(authenticate) calls yourself. All authentication is declared in the OpenAPI document and enforced automatically.

      "},{"location":"05_security/#1-where-security-lives","title":"\ud83d\uddfa\ufe0f 1. Where Security Lives","text":"

      Two places in the spec, both respected:

      "},{"location":"05_security/#11-componentssecurityschemes-the-inventory","title":"1.1 components.securitySchemes \u2014 the inventory","text":"
      components:\n  securitySchemes:\n    internalBearer:\n      type: http\n      scheme: bearer\n      bearerFormat: JWT\n      x-server-url: \"{AUTH_SERVER_URL}\"\n      x-introspect-path: \"/introspect\"\n

      x-server-url and x-introspect-path are library extensions that point at a JWT introspection endpoint (see Introspection).

      "},{"location":"05_security/#12-security-per-operation-or-global-requirements","title":"1.2 security \u2014 per-operation (or global) requirements","text":"
      security:\n  - internalBearer: []          # applied to every operation by default\n\npaths:\n  /pets:\n    get:\n      operationId: list_pets\n      # inherits: security: [{internalBearer: []}]\n    post:\n      operationId: create_pet\n      security: []              # override \u2014 public endpoint\n

      OpenAPI dynamic-scoping rules apply: an operation-level security replaces the global list (it does not merge).

      "},{"location":"05_security/#2-reading-the-spec","title":"\ud83d\udcd6 2. Reading the Spec","text":"

      security.py exposes two functions:

      Function Returns Purpose parse_security_schemes(spec) dict[str, dict] Collect schemes, resolve {ENV_VAR} placeholders make_security_dependencies(spec, schemes) dict[str, list[Depends]] (keyed METHOD:/path) Effective per-operation security deps

      Env placeholders of the form {NAME} are resolved once at startup from os.environ. This is how you avoid embedding credentials or auth-service URLs in the committed spec.

      "},{"location":"05_security/#3-the-bearer-dependency","title":"\ud83d\udd10 3. The Bearer Dependency","text":"

      make_security_dependencies builds a FastAPI dependency for type: http, scheme: bearer.

      Two modes, decided by whether an introspection endpoint is configured:

      "},{"location":"05_security/#31-with-introspection-x-introspect-path","title":"3.1 With introspection (x-introspect-path)","text":"
      • Reads Authorization: Bearer <token>
      • POSTs {\"token\": \"<token>\"} to {x-server-url}{x-introspect-path} synchronously via the bundled httpx client
      • Expects a response with active: true
      • On valid: {\"user\": ...} from the introspection body \u2192 request.state.user
      • On failure: 401 (missing/invalid token) or 503 (auth service unreachable)
      "},{"location":"05_security/#32-without-introspection","title":"3.2 Without introspection","text":"
      • Validates only that a Bearer token is present
      • Stores it on request.state.token; no remote call
      "},{"location":"05_security/#4-wiring-it-together","title":"\ud83d\udd17 4. Wiring It Together","text":"
      # server-side\nfrom openapi_first.loader import load_openapi\nfrom openapi_first.security import (\n    parse_security_schemes,\n    make_security_dependencies,\n)\nfrom openapi_first.app import OpenAPIFirstApp\nimport routes\n\nspec = load_openapi(\"openapi.yaml\")\nschemes = parse_security_schemes(spec)\nsecurity_deps = make_security_dependencies(spec, schemes)\n\napp = OpenAPIFirstApp(\n    openapi_path=\"openapi.yaml\",\n    routes_module=routes,\n)\n

      OpenAPIFirstApp already does this internally \u2014 the snippet above shows what it encapsulates (and what you use directly if you assemble the pieces by hand).

      "},{"location":"05_security/#5-testing-security","title":"\ud83e\uddea 5. Testing Security","text":"

      Because handlers are plain callables, security is the one place FastAPI's TestClient earns its keep:

      from fastapi.testclient import TestClient\n\ndef test_unauthenticated_is_401(app, overrides):\n    with TestClient(app) as client:\n        r = client.get(\"/pets\")\n        assert r.status_code == 401\n\ndef test_invalid_token_using_fake_introspector(app):\n    # Point x-introspect-path at a stub uvicorn/TestServer returning active:false\n    with TestClient(app) as client:\n        r = client.get(\"/pets\", headers={\"Authorization\": \"Bearer nope\"})\n        assert r.status_code == 401\n

      See Testing for the full recipe, including how tests stub the introspection server.

      "},{"location":"05_security/#7-common-patterns","title":"\ud83d\udee1\ufe0f 7. Common Patterns","text":"Pattern How Public endpoint security: [] on the operation Whole-spec auth top-level security: (applies to all) Route-specific scheme replace security on that operation Env-driven auth URL x-server-url: \"{AUTH_SERVER_URL}\" Offline token carry scheme without x-introspect-path \u2192 request.state.token Auth on the client side pass the token via client.<operationId>(headers={\"Authorization\": ...})"},{"location":"05_security/#related","title":"Related","text":"
      • 02 \u2013 Components \u00b7 04 \u2013 Design \u00b7 06 \u2013 Error Handling \u00b7 07 \u2013 Testing
      "},{"location":"06_error_handling/","title":"Error Handling \u2014 Fail Loud, Fail Early","text":"

      openapi-first treats errors as first-class contract documents: every failure mode is a named exception with a stable import pathhare, and every one surfaces as early as possible.

      "},{"location":"06_error_handling/#1-the-hierarchy","title":"\ud83e\uddec 1. The Hierarchy","text":"

      All errors derive from OpenAPIFirstError (in openapi_first/errors.py), so a single except OpenAPIFirstError catches every first-party failure:

      OpenAPIFirstError\n\u251c\u2500\u2500 OpenAPISpecError                     # spec-level problems\n\u2502   \u2514\u2500\u2500 OpenAPISpecLoadError             # load / parse / validation  (loader)\n\u251c\u2500\u2500 OpenAPIClientError                   # client-side contract issues (client)\n\u2514\u2500\u2500 MissingOperationHandler              # spec op with no handler     (binder)\n\n# Security / loader layers raise through OpenAPISpecError subclasses too\n
      Exception Module Raised when OpenAPISpecLoadError loader Path missing, file unreadable, YAML/JSON invalid, or spec fails OpenAPI 3.x validation OpenAPIClientError client No servers, no paths, missing/duplicate operationId, missing required params at construction MissingOperationHandler errors An operation is declared whose operationId has no matching handler in routes_module"},{"location":"06_error_handling/#2-when-things-fail","title":"\u23f1\ufe0f 2. When Things Fail","text":"

      The single most important rule: violations are eager, not lazy.

      "},{"location":"06_error_handling/#21-at-application-startup","title":"2.1 At application startup","text":"
      # openapi.yaml missing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba OpenAPISpecLoadError\n# operationId without a handler \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba MissingOperationHandler\n# operation with no operationId declared \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba MissingOperationHandler\n

      Because these raise during OpenAPIFirstApp(...) construction, CI catches them the moment a spec and its routes drift \u2014 before a single request is served.

      "},{"location":"06_error_handling/#22-at-client-construction","title":"2.2 At client construction","text":"
      OpenAPIClient(spec)  # fails fast, same philosophy\n
      • Spec with no servers \u2192 OpenAPIClientError
      • Spec with no paths \u2192 OpenAPIClientError
      • Duplicate operationIds \u2192 OpenAPIClientError (client methods must be unambiguous)
      • Operation missing operationId \u2192 OpenAPIClientError
      "},{"location":"06_error_handling/#23-at-call-time-client","title":"2.3 At call time (client)","text":"

      Runtime transport errors surface as httpx exceptions (httpx.RequestError family), not swallowed or remapped. Missing required args fail before any HTTP request is made:

      client.get_user(path_params={\"user_id\": ...})     # OK\nclient.get_user()                                  # ValueError \u2014 user_id required\n
      "},{"location":"06_error_handling/#3-handling-in-your-app","title":"\ud83e\uddf0 3. Handling in Your App","text":""},{"location":"06_error_handling/#31-server-side","title":"3.1 Server-side","text":"

      Handlers raise FastAPI HTTPException for expected operation-level failures (404/422), and the OperationId-binding errors only exist at startup:

      from fastapi import HTTPException\n\ndef get_item(item_id: int):\n    \"\"\"Retrieve an item by ID.\n\n    Implements the OpenAPI operation ``get_item``.\n\n    Args:\n        item_id (int): Identifier of the item.\n\n    Raises:\n        HTTPException: If the item does not exist (404).\n    \"\"\"\n    try:\n        return _get_item(item_id)\n    except KeyError:\n        raise HTTPException(status_code=404, detail=\"Item not found\")\n
      "},{"location":"06_error_handling/#32-client-side","title":"3.2 Client-side","text":"
      import httpx\n\ntry:\n    response = client.get_item(path_params={\"item_id\": 1})\nexcept httpx.HTTPStatusError as exc:\n    ...  # 4xx/5xx from the server\n

      httpx.HTTPStatusError isn't raised by the library \u2014 it's the standard httpx.raise_for_status() you can opt into per call. The library never masks a response code.

      "},{"location":"06_error_handling/#4-fail-fast-guarantees-recap","title":"\ud83d\udee1\ufe0f 4. Fail-Fast Guarantees Recap","text":"Layer You write The library guarantees Loader a spec path unreadable/invalid specs never reach your app Binder handler functions every operation must resolve, or the app won't start Client a spec every operationId becomes a callable; missing ones fail at construction Runtime handler code FastAPI + Pydantic handle coercion; contract checks already happened at startup

      None of these can silently degrade: a violation is an exception at construction, not a 500 at request time.

      "},{"location":"06_error_handling/#related","title":"Related","text":"
      • 01 \u2013 Overview \u00b7 05 \u2013 Security \u00b7 07 \u2013 Testing
      "},{"location":"07_testing/","title":"Testing \u2014 Smoke-First, Contract-First","text":"

      Everything openapi-first ships is tested against real specs, real handlers, and real HTTP through FastAPI's TestClient and the bundled templates. There are no fakes of the library itself.

      "},{"location":"07_testing/#1-suite-overview","title":"\ud83e\uddea 1. Suite Overview","text":"
      tests/\n\u251c\u2500\u2500 conftest.py        # fixtures: spec_file, routes_module, app, client\n\u251c\u2500\u2500 test_app.py        # OpenAPIFirstApp: routes served, overrides, fail-fast\n\u251c\u2500\u2500 test_binder.py     # bind_routes: opId resolution + missing-handler failures\n\u251c\u2500\u2500 test_loader.py     # load_openapi: json/yaml, env resolution, validation\n\u2514\u2500\u2500 test_client.py     # OpenAPIClient: opId\u2192callable, params, error cases\n

      Run with:

      pytest            # 23 tests, zero mocks of the library\npytest -q\npytest tests/test_loader.py\n
      "},{"location":"07_testing/#2-the-fixture-pattern","title":"\ud83c\udfd7\ufe0f 2. The Fixture Pattern","text":"
      # conftest.py (abridged)\n@pytest.fixture\ndef spec_file(tmp_path):\n    path = tmp_path / \"openapi.json\"\n    path.write_text(json.dumps(SPEC), encoding=\"utf-8\")\n    return str(path)\n\n@pytest.fixture\ndef app(spec_file):\n    return OpenAPIFirstApp(\n        openapi_path=spec_file,\n        routes_module=routes_module(),\n    )\n\n@pytest.fixture\ndef client(spec_file):\n    return OpenAPIClient(json.loads(Path(spec_file).read_text()))\n
      "},{"location":"07_testing/#3-whats-actually-asserted","title":"\ud83e\uddea 3. What's Actually Asserted","text":""},{"location":"07_testing/#31-app-level-smoke","title":"3.1 App-level smoke","text":"
      def test_app_routes_served(spec_file):\n    app = OpenAPIFirstApp(openapi_path=spec_file, routes_module=routes)\n    client = TestClient(app)\n    assert client.get(\"/health\").json() == {\"status\": \"ok\"}\n
      "},{"location":"07_testing/#32-fail-fast-the-heart","title":"3.2 Fail-fast (the heart)","text":"
      def test_app_missing_handler_fails_at_startup(spec_file):\n    with pytest.raises(MissingOperationHandler):\n        OpenAPIFirstApp(openapi_path=spec_file, routes_module=empty_routes)\n
      "},{"location":"07_testing/#33-loader-validation","title":"3.3 Loader validation","text":"
      def test_loader_invalid_spec_fails():\n    with pytest.raises(OpenAPISpecLoadError):\n        load_openapi(\"broken.yaml\")\n
      "},{"location":"07_testing/#34-client-contract-drift","title":"3.4 Client contract drift","text":"
      def test_client_duplicate_operation_id_raises():\n    with pytest.raises(OpenAPIClientError):\n        OpenAPIClient(dup_spec)\n
      "},{"location":"07_testing/#4-testing-the-templates","title":"\ud83c\udfdb\ufe0f 4. Testing the Templates","text":"

      Each bundled template ships its own test + in-memory store, so you get a runnable contract test the moment you scaffold:

      openapi-first scaffold crud_app my-service\ncd my-service\npytest -q\n

      test_crud_app.py / test_model_app.py / test_vet_app.py exercise the full CRUD surface through TestClient \u2014 including 201/204 status codes, 404s, and (in vet_app) SSE streaming via StreamingResponse.

      "},{"location":"07_testing/#5-quality-gates-ci","title":"\ud83d\udd79\ufe0f 5. Quality Gates (CI)","text":"

      The same gates the library itself must pass are what keep the docs honest:

      Gate Purpose black --check formatting parity ruff check lint hygiene mypy type safety (strict, --disable-error-code where intentional) pytest 23 tests, green coverage tracked via pytest-cov (HTML + XML + term)

      Run the whole gate locally:

      black --check openapi_first tests\nruff check openapi_first tests\nmypy openapi_first\npytest\n
      "},{"location":"07_testing/#6-testing-tips","title":"\ud83d\udca1 6. Testing Tips","text":"
      • Start from startup: assert OpenAPIFirstApp(...) raises for the broken contracts \u2014 those are your most valuable tests
      • Client \u2194 server: smoke a client against the same spec the app was built from \u2014 one spec, two sides, zero drift
      • Templates are scaffolds: their tests are copyable starting points, not canonical suites
      • No mocking of the library: exercise loader \u2192 binder \u2192 app \u2192 client as a real pipeline
      "},{"location":"07_testing/#related","title":"Related","text":"
      • 01 \u2013 Overview \u00b7 02 \u2013 Components \u00b7 04 \u2013 Error Handling
      "},{"location":"03_use_cases/01_quickstart/","title":"Use Case 1: Quickstart \u2014 Build Your First OpenAPI-First Service","text":"

      This guide walks you from an empty directory to a running, contract-driven service in a few minutes, then talks to it with the generated client.

      "},{"location":"03_use_cases/01_quickstart/#1-prerequisites","title":"\ud83d\udee0\ufe0f 1. Prerequisites","text":"
      • Python 3.10+
      • openapi-first installed (see Overview)
      • pip install \"fastapi[standard]\" (or uvicorn) to run the app
      "},{"location":"03_use_cases/01_quickstart/#2-write-the-openapi-document","title":"\ud83d\udcc4 2. Write the OpenAPI document","text":"

      OpenAPI comes first. Create openapi.yaml:

      openapi: 3.0.3\ninfo:\n  title: Greeting Service\n  version: 1.0.0\nservers:\n  - url: http://localhost:8000\npaths:\n  /greet/{name}:\n    get:\n      operationId: get_greeting\n      parameters:\n        - name: name\n          in: path\n          required: true\n          schema:\n            type: string\n      responses:\n        \"200\":\n          description: A greeting\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  greeting:\n                    type: string\n

      Key points: every operation needs operationId, and every route must exist only here.

      "},{"location":"03_use_cases/01_quickstart/#3-write-the-handlers","title":"\ud83e\uddd1\u200d\ud83d\udcbb 3. Write the handlers","text":"

      Create routes.py \u2014 plain functions, no decorators, named exactly like the operationIds:

      # routes.py\ndef get_greeting(name: str) -> dict:\n    \"\"\"Return a greeting for the given name.\"\"\"\n    return {\"greeting\": f\"Hello, {name}!\"}\n

      If a handler is missing at startup, the app refuses to boot (MissingOperationHandler) \u2014 the fail-fast guarantee catches contract drift immediately.

      "},{"location":"03_use_cases/01_quickstart/#4-bootstrap-the-app","title":"\ud83d\ude80 4. Bootstrap the app","text":"

      Create main.py:

      # main.py\nfrom openapi_first.app import OpenAPIFirstApp\nimport routes\n\napp = OpenAPIFirstApp(\n    openapi_path=\"openapi.yaml\",\n    routes_module=routes,\n    title=\"Greeting Service\",\n)\n

      Run it:

      uvicorn main:app --reload\n

      Visit http://localhost:8000/docs (Swagger UI) and http://localhost:8000/openapi.json \u2014 both are generated from your spec.

      "},{"location":"03_use_cases/01_quickstart/#5-call-it-with-the-client","title":"\ud83d\udce1 5. Call it with the client","text":"

      The same spec builds a strict client:

      # client.py\nfrom openapi_first.loader import load_openapi\nfrom openapi_first.client import OpenAPIClient\n\nspec = load_openapi(\"openapi.yaml\")\nclient = OpenAPIClient(spec)\n\nresponse = client.get_greeting(path_params={\"name\": \"Ada\"})\nprint(response.status_code)   # 200\nprint(response.json())        # {\"greeting\": \"Hello, Ada!\"}\n
      "},{"location":"03_use_cases/01_quickstart/#6-next-steps","title":"\ud83d\udca1 6. Next Steps","text":"
      • Copy a fuller example: 02 \u2013 Templates
      • Generate models/routes from a bigger spec: 04 \u2013 Codegen
      • Drive everything from a client: 03 \u2013 Client
      "},{"location":"03_use_cases/01_quickstart/#related","title":"Related","text":"
      • 01 \u2013 Overview \u00b7 02 \u2013 Components \u00b7 01 \u2013 Quickstart
      "},{"location":"03_use_cases/02_templates/","title":"Use Case 2: Templates \u2014 Copyable Reference Applications","text":"

      openapi-first ships four runnable, copyable applications under openapi_first/templates/. They are not part of the library API \u2014 they are bundled scaffold examples you copy into your own project and build on.

      "},{"location":"03_use_cases/02_templates/#1-what-templates-are","title":"\ud83c\udfac 1. What Templates Are","text":"

      A template is a complete, self-contained OpenAPI-first service:

      • A bundled directory inside openapi_first/templates/<name>/
      • Copyable verbatim \u2014 no code generation, no mutation \u2014 via the CLI
      • Each one demonstrates a specific set of OpenAPI-first behaviors and FastAPI features

      All templates share the same skeleton:

      <name>_app/\n\u251c\u2500\u2500 __init__.py      # explains the template + how to scaffold it\n\u251c\u2500\u2500 openapi.yaml     # the contract (source of truth)\n\u251c\u2500\u2500 main.py          # assembles OpenAPIFirstApp from the spec\n\u251c\u2500\u2500 routes.py        # operationId-bound handler functions\n\u2514\u2500\u2500 data.py          # in-memory data store (demo only)\n
      "},{"location":"03_use_cases/02_templates/#2-the-four-templates","title":"\ud83d\udccb 2. The Four Templates","text":""},{"location":"03_use_cases/02_templates/#21-health_app-minimal-liveness-probe","title":"2.1 health_app \u2014 minimal liveness probe","text":"
      openapi-first scaffold health_app\n# or into a custom directory:\nopenapi-first scaffold health_app my-health-service\n
      File Purpose openapi.yaml GET /health \u2192 operationId: get_health routes.py get_health() returns {\"status\": \"ok\"} main.py OpenAPIFirstApp(openapi_path=\"openapi.yaml\", routes_module=routes)

      Why it exists: the absolute minimal OpenAPI-first round trip \u2014 one operation, one handler, zero moving parts. The best starting point to internalize the mental model.

      Smoke test:

      pip install -e .\nuvicorn main:app\ncurl http://localhost:8000/health\n# \u2192 {\"status\": \"ok\"}\n
      "},{"location":"03_use_cases/02_templates/#22-crud_app-dict-based-crud","title":"2.2 crud_app \u2014 dict-based CRUD","text":"
      openapi-first scaffold crud_app my-crud-service\n
      File Purpose openapi.yaml Full CRUD over /items (list/get/create/update/delete) routes.py Handlers bound via operationIds list_items, get_item, create_item, update_item, delete_item data.py In-memory dict store with auto-incrementing id

      Behaviors you learn:

      • Explicit status codes \u2014 create_item/delete_item take response: Response and set 201/204; get_item/update_item raise HTTPException(404) on KeyError
      • Handlers as plain callables \u2014 no FastAPI decorators, routing comes solely from the spec
      • Mock data store \u2014 data.py is a copyable in-memory store, explicitly not production-ready
      "},{"location":"03_use_cases/02_templates/#23-model_app-pydantic-model-crud","title":"2.3 model_app \u2014 Pydantic model CRUD","text":"
      openapi-first scaffold model_app my-model-service\n
      File Purpose openapi.yaml Same CRUD surface, schemas reference models models.py Pydantic Item, ItemCreate, ItemBase (request/response models) routes.py Handlers type-annotated with the models; create_item sets 201 data.py In-memory store returning real model instances

      Behaviors you learn:

      • Pydantic request/response models \u2014 payloads validated and serialized via FastAPI
      • Same handler contracts \u2014 identical operationId set as crud_app, so the two are interchangeable
      • Models in the client too \u2014 the same spec drives OpenAPIClient body handling
      "},{"location":"03_use_cases/02_templates/#24-vet_app-the-full-featured-demo","title":"2.4 vet_app \u2014 the full-featured demo","text":"
      openapi-first scaffold vet_app my-vet-clinic\n
      File Purpose openapi.yaml Five resources (parents, vets, treatments, pets, appointments) + SSE + upload + discriminated unions models.py Pydantic models incl. discriminated unions (noteType literal fields) routes.py ~20 handlers across all resources, incl. pagination, filtering, photo upload, SSE streaming sse.py Server-Sent Events helper (StreamingResponse, per-pet subscriber queues, background asyncio workers) data.py Larger in-memory store (parents \u2192 vets \u2192 treatments \u2192 pets \u2192 appointments) main.py App + CORS + lifespan example

      Behaviors you learn \u2014 the advanced tier:

      • Discriminated unions \u2014 ProcedureNotes uses oneOf + discriminator.noteType mapping; Pydantic models use Literal[...] discriminator fields
      • SSE streaming \u2014 a GET /pets/{id}/actions operation streaming text/event-stream via StreamingResponse with background task workers
      • File upload \u2014 UploadFile handler setting a multi-part body
      • CORS + middleware \u2014 add_middleware(CORSMiddleware, ...) alongside the spec-driven setup
      • Response injection \u2014 handlers set 201/204 explicitly via injected Response
      "},{"location":"03_use_cases/02_templates/#3-cli-reference","title":"\ud83d\ude80 3. CLI Reference","text":"
      # List available templates\nopenapi-first scaffold --list\n\n# Copy a template into its default directory (template name, dashes)\nopenapi-first scaffold health_app\n\n# Copy into a custom target directory\nopenapi-first scaffold crud_app my-project/crud\n

      Protip: DEFAULT_TEMPLATE is health_app, so openapi-first scaffold with no template name scaffolds the health app.

      "},{"location":"03_use_cases/02_templates/#4-anatomy-of-a-scaffolded-service","title":"\ud83e\udde9 4. Anatomy of a Scaffolded Service","text":"

      After openapi-first scaffold health_app my-health-service, your directory contains a drop-in FastAPI service:

      my-health-service/\n\u251c\u2500\u2500 openapi.yaml   # THE contract\n\u251c\u2500\u2500 main.py        # `app = OpenAPIFirstApp(openapi_path=..., routes_module=routes)`\n\u2514\u2500\u2500 routes.py      # `def get_health(): ...`\n

      Run it:

      cd my-health-service\npip install -e .\nuvicorn main:app --reload\n

      /docs, /openapi.json, and every declared route now exist \u2014 all derived from openapi.yaml.

      "},{"location":"03_use_cases/02_templates/#5-production-disclaimer","title":"\u26a0\ufe0f 5. Production Disclaimer","text":"

      Templates use in-memory, non-persistent, non-concurrency-safe data stores. They are learning scaffolds \u2014 not production references. Swap in a real data layer (SQL/REDIS/object store) the moment you go beyond a demo.

      See the __init__.py of each template for detailed client examples, CLI examples, and design notes.

      "},{"location":"03_use_cases/02_templates/#related","title":"Related","text":"
      • 01 \u2013 Quickstart \u00b7 04 \u2013 Codegen \u00b7 02 \u2013 Components
      "},{"location":"03_use_cases/03_client/","title":"Use Case 3: The OperationId-Driven Client","text":"

      OpenAPIClient is the other side of the contract. It reads the same OpenAPI document the server runs on and exposes one callable per operationId \u2014 so \"client\" and \"server\" are two views of one truth.

      "},{"location":"03_use_cases/03_client/#1-before-you-start","title":"\ud83d\udd0d 1. Before You Start","text":"

      The client is httpx-based and returns raw httpx.Response objects \u2014 no magic deserialization, no hidden schema inference:

      • No response Pydantic models
      • No implicit URL construction (path params are explicit)
      • No hand-written requests.get(...) scattered through your code
      "},{"location":"03_use_cases/03_client/#2-constructing-the-client","title":"\ud83e\uddec 2. Constructing the Client","text":"
      from openapi_first.loader import load_openapi\nfrom openapi_first.client import OpenAPIClient\n\nspec = load_openapi(\"openapi.yaml\")\nclient = OpenAPIClient(spec)\n

      The base URL comes from the spec's servers list (first entry) unless you pass base_url explicitly:

      client = OpenAPIClient(spec, base_url=\"https://api.internal.myco/v1\")\n

      You can also hand over a preconfigured httpx.Client (custom transport, TLS, retries):

      import httpx\n\ntransport = httpx.HTTPTransport(retries=3)\nclient = OpenAPIClient(\n    spec,\n    client=httpx.Client(transport=transport),\n)\n
      "},{"location":"03_use_cases/03_client/#3-fail-fast-at-construction","title":"\ud83d\udca5 3. Fail-Fast at Construction","text":"

      OpenAPIClient(...) raises immediately if the contract is broken \u2014 you find out at startup, not on the first request:

      Violation Error Spec has no servers entry OpenAPIClientError Spec has no paths OpenAPIClientError Operation missing operationId OpenAPIClientError Duplicate operationId OpenAPIClientError Operation references unknown parameters OpenAPIClientError

      Missing required parameters fail at call time \u2014 pydoclint-grade strictness on the wire.

      "},{"location":"03_use_cases/03_client/#4-calling-operations","title":"\ud83d\udcde 4. Calling Operations","text":"

      Every operationId becomes a method. The call signature is uniform across the whole client:

      response = client.<operationId>(\n    *,\n    path_params: dict | None = None,\n    query: dict | None = None,\n    headers: dict | None = None,\n    body: Any | None = None,\n    timeout: float | None = None,\n) -> httpx.Response\n

      Concrete examples (from the crud_app / health_app templates):

      # No parameters \u2014 simplest\nresponse = client.get_health()\nassert response.status_code == 200\n\n# Path parameter\nresponse = client.get_item(path_params={\"item_id\": 3})\n\n# Query parameters\nresponse = client.list_items(query={\"limit\": 10, \"offset\": 20})\n\n# JSON request body\nresponse = client.create_item(body={\"name\": \"Orange\", \"price\": 0.8})\n\n# Custom headers / timeout\nresponse = client.get_user(\n    path_params={\"user_id\": 1},\n    headers={\"X-Internal-Key\": \"...\"},\n    timeout=30,\n)\n

      Returns: the raw httpx.Response, so status_code, .json(), .headers are all yours to inspect.

      "},{"location":"03_use_cases/03_client/#5-how-parameters-are-bound","title":"\ud83e\udde0 5. How Parameters Are Bound","text":"

      For each operation the client knows exactly where each parameter belongs:

      OpenAPI location Client kwarg in: path path_params[name] in: query query[name] in: header headers[name] requestBody body

      JSON media types are sent as json=; other media types as raw content=.

      "},{"location":"03_use_cases/03_client/#6-server-client-one-spec","title":"\ud83d\udd04 6. Server \u2194 Client, One Spec","text":"
      # One directory, two processes\nuvicorn main:app --port 8000                                  # server\npython -c \"import asyncio; from client_script import run; asyncio.run(run())\"  # client\n

      Or the same client against a remote environment:

      client = OpenAPIClient(\n    spec,\n    base_url=\"https://staging.internal.myco\",\n)\n

      The URL is the only thing that changes between environments \u2014 the contract never does.

      "},{"location":"03_use_cases/03_client/#7-operationid-as-the-api","title":"\u270d\ufe0f 7. OperationId as the API","text":"

      Because the client is operationId-driven:

      • Adding an operation = adding a method (and vice versa)
      • No operation can be \"forgotten\" by the client \u2014 it's constructed from the spec
      • operationId is the one name you remember; the HTTP verb/path is an implementation detail

      If an operationId you call doesn't exist, you get an AttributeError at construction-scan time \u2014 never a silent 404.

      "},{"location":"03_use_cases/03_client/#related","title":"Related","text":"
      • 01 \u2013 Overview \u00b7 01 \u2013 Quickstart \u00b7 02 \u2013 Templates
      "},{"location":"03_use_cases/04_codegen/","title":"Codegen \u2014 Generate Models & Routes From Your Spec","text":"

      TL;DR \u2014 point openapi-first models / openapi-first routes at your spec and get Pydantic models plus operation-matching route stubs. The output is deterministic and is a starting point \u2014 not a maintained artifact.

      "},{"location":"03_use_cases/04_codegen/#1-why-celebrate-codegen","title":"\ud83e\udded 1. Why Celebrate Codegen?","text":"

      Two pain points kill OpenAPI projects:

      1. The \"write the spec, then write the same thing as Pydantic models\" step \u2014 exactly where server/client param types drift (your route says item_id: int, your client sends a string\u2026).
      2. Typing handler names by hand when bind_routes requires each operationId to resolve to a function of the exact same name.

      Codegen collapses both. One command, one source file, the same shapes everywhere.

      "},{"location":"03_use_cases/04_codegen/#2-model-generation","title":"\ud83d\ude80 2. Model Generation","text":"
      openapi-first models openapi.yaml -o app/_gen/models.py\n

      The generated module mirrors the spec's components.schemas by name:

      Spec Generated components.schemas.User class User(BaseModel) components.schemas.Item class Item(BaseModel) required + type from schema Pydantic Field(...) / type hints \u2713

      Never hand-edit those files. If the spec changes, regenerate \u2014 just like re-running a compiler after editing source.

      "},{"location":"03_use_cases/04_codegen/#why-constructor-time-validation-still-applies","title":"Why constructor-time validation still applies","text":"

      Codegen doesn't change the design: the generated models are plain Pydantic, and the client/server still validate against the spec at startup. Codegen is a convenience accelerator on top of the fail-fast guarantees in 04 \u2013 Design and 06 \u2013 Error Handling.

      Wire model vs. domain model. Generated models are the wire contract. Keep them separate from your persisted/domain model (see the standards CRUD Template): give the wire schema an id: str, but model the Mongo document so the id is optional.

      "},{"location":"03_use_cases/04_codegen/#3-route-generation","title":"\ud83d\udd01 3. Route Generation","text":"
      openapi-first routes openapi.yaml -o app/interfaces \\\n  --use-models --models-module app._gen.models \\\n  --inject-request --package\n

      One file per resource (derived from the first path segment). Flags:

      Flag Effect --use-models Import request-body $ref models from --models-module --models-module MOD Dotted module for those imports (default models) --inject-request Prepend request: Request to every handler (DI convention) --package Emit <resource>/routes.py packages (with __init__.py) instead of flat <resource>.py

      Why bother? Because bind_routes (in 02 \u2013 Components) needs an operationId \u2192 handler exactly matching the spec. Codegen guarantees you never type a handler name wrong \u2014 it wears the same operationId the spec declares.

      "},{"location":"03_use_cases/04_codegen/#resource-names-and-extra-media-types","title":"Resource names and extra media types","text":"
      • A path such as /fetch-requests yields a sanitized module name fetch_requests.py (a raw hyphen could never be imported).
      • multipart/form-data and application/x-www-form-urlencoded bodies expand to one parameter per schema property: UploadFile = File(...) for format: binary, Form(...) otherwise \u2014 not an opaque payload: dict. Fields are required only when listed in the schema's required.

      The stubs raise NotImplementedError; fill each body in (typically by resolving a service from request.app.state).

      Note: models / routes are build-time tooling. They do not run at runtime, and templates (the Bake-your-stuff section of 02 \u2013 Templates) scaffold a working server out-of-the-box for greenfield projects.

      "},{"location":"03_use_cases/04_codegen/#4-idempotent-output","title":"\ud83e\uddea 4. Idempotent Output","text":"

      Generated output is deterministic w.r.t. the spec: - Same spec \u2192 byte-identical files (unless you hand-edit \u2014 you won't) - Ordering follows spec declaration order - No timestamps, no machine names, no hidden randomness

      That determinism is what lets you git diff after a spec change and see exactly what moved.

      "},{"location":"03_use_cases/04_codegen/#related","title":"\ud83e\udde9 Related","text":"
      • 01 \u2013 Overview \u00b7 02 \u2013 Templates \u00b7 03 \u2013 Client \u00b7 04 \u2013 Design \u00b7 07 \u2013 Testing
      "}]} \ No newline at end of file diff --git a/openapi-first/wiki/sitemap.xml.gz b/openapi-first/wiki/sitemap.xml.gz index 01465253604ef76b3b2b517f1166919160e58317..36fff951d658b8fa1c2624e6fcea07a1ebb3772b 100644 GIT binary patch delta 13 Ucmb=gXP58h;9yWbGm*Ul02yinzW@LL delta 13 Ucmb=gXP58h;ArStF_FCj03F=~f&c&j diff --git a/registry.py b/registry.py index c95f374..9b3a6d5 100644 --- a/registry.py +++ b/registry.py @@ -6,7 +6,7 @@ helpers so an agent can find the right internal library — and pull exact template code — before writing any infrastructure. Resources: - doc://index, doc://nav, doc://modules/{module} (from the base server) + docs://index, docs://nav, docs://modules/{module} (from the base server) registry://libraries (the structured catalog) registry://components (the template component index) @@ -15,9 +15,13 @@ Tools: find_library (capability -> matching libraries + MCP endpoints) list_app_components (the template component kinds) get_app_component (kind -> exact template file, seed tokens renamed) + get_app_template (the whole app skeleton as a path -> content map) Prompts: - start_app (collect the two axes, then emit the build prompt) + intake (the questions to gather before building) + build (the two axes -> deterministic build prompt) + start_app (combined entry: intake, then build) + trial (self-contained trial build into a scratch dir) """ from __future__ import annotations @@ -35,6 +39,50 @@ def _pascal(name: str) -> str: return "".join(part.capitalize() for part in re.split(r"[_\-\s]+", name) if part) +_INTAKE_TEXT = ( + "# Start a new app — intake\n\n" + "Gather the following, then call `build` (or `start_app`) with the values:\n\n" + "1. App name + one-line purpose.\n" + "2. Axis 1 — resources: each resource, its fields, and relationships\n" + " (or paste `openapi.yaml`).\n" + "3. Axis 2 — pipelines: multi-step inputs? families + formats (or `none`).\n" + "4. Auth needed now? (yes/no; auth is deferred — `py-jwt` only).\n" + "5. Integrations: Mongo / ingestion formats / email / LLM provider.\n\n" + "Then: `build(app_name=..., resources=..., pipelines=..., auth=...)`.\n" +) + + +def _build_text(app_name: str, resources: str, pipelines: str, auth: str) -> str: + """Render the deterministic build prompt from the two axes. + + Args: + app_name (str): App name + one-line purpose. + resources (str): Axis 1 — resources, fields, relationships. + pipelines (str): Axis 2 — pipeline families + input formats. + auth (str): Whether auth is needed now ("yes"/"no"). + + Returns: + str: The build prompt. + """ + return ( + f"# Build: {app_name}\n\n" + "## Spec\n" + f"- resources: {resources}\n" + f"- pipelines: {pipelines or 'none'}\n" + f"- auth: {auth}\n\n" + "## Method (deterministic)\n" + "1. Read `docs://modules/build_recipe` and follow it step by step.\n" + "2. Read `docs://modules/genericity_matrix` (Tier A/B/C) and " + "`docs://modules/layering_rules`.\n" + "3. Call `find_library(capability)` for db, di, routing, ingestion, " + "and pipeline; never reimplement a Tier A library.\n" + "4. Scaffold with `get_app_template()` (whole tree) or pull single " + "files with `list_app_components()` and " + "`get_app_component(kind, resource=..., family=...)`.\n" + "5. Author only Tier C; keep Tier B as shipped.\n" + ) + + class StandardsMCPServer(MCPServer): """MCP server for the `standards` bundle with discovery/scaffolding tools. @@ -72,6 +120,32 @@ class StandardsMCPServer(MCPServer): return entry return None + def _template_files(self, prefix: str | None = None) -> dict[str, str]: + """Read the vendored template tree as a path -> content map. + + Args: + prefix (str | None): Optional path prefix to filter by (POSIX + separators, for example ``"infra/"``). + + Returns: + dict[str, str]: Relative POSIX path -> UTF-8 file content, excluding + caches and bytecode. + """ + root = Path(self.mcp_root) / "templates" / "app" + files: dict[str, str] = {} + if not root.is_dir(): + return files + for path in sorted(root.rglob("*")): + if not path.is_file(): + continue + if "__pycache__" in path.parts or path.suffix == ".pyc": + continue + rel = path.relative_to(root).as_posix() + if prefix and not rel.startswith(prefix): + continue + files[rel] = path.read_text(encoding="utf-8") + return files + def _read_component( self, entry: dict[str, Any], @@ -186,6 +260,66 @@ class StandardsMCPServer(MCPServer): } return self._read_component(entry, resource, family) + @self.app.tool() + def get_app_template(prefix: str = "") -> dict[str, Any]: + """Return the whole app skeleton as a path -> content map. + + Use this to scaffold in one shot instead of pulling components one + by one; combine with :func:`list_app_components` for the kind and + placeholder metadata. Seed tokens (``item`` / ``example``) are left + as-is here — fetch a renamed file with :func:`get_app_component`. + + Args: + prefix (str): Optional path prefix filter with POSIX + separators, for example ``"infra/"`` or ``"pipelines/"``. + Empty returns every file. + + Returns: + dict[str, Any]: ``{root, file_count, files: {relpath: content}}``. + """ + files = self._template_files(prefix or None) + return { + "root": "templates/app", + "file_count": len(files), + "files": files, + } + + @self.app.prompt() + def intake() -> str: + """Return the questions to gather before building an app. + + Use this (or `start_app` with no arguments) to collect the two axes + and the integration list. Answer them, then call `build`. + + Returns: + str: The intake questions. + """ + return _INTAKE_TEXT + + @self.app.prompt() + def build( + app_name: str = "", + resources: str = "", + pipelines: str = "", + auth: str = "no", + ) -> str: + """Emit the deterministic build prompt from the two axes. + + Args: + app_name (str): App name + one-line purpose. + resources (str): Axis 1 — resources, fields, relationships + (or a pasted openapi.yaml). + pipelines (str): Axis 2 — pipeline families + input formats. + auth (str): Whether auth is needed now ("yes"/"no"). + + Returns: + str: The build prompt, or the intake questions when the axes + are missing. + """ + if not app_name or not resources: + return _INTAKE_TEXT + return _build_text(app_name, resources, pipelines, auth) + @self.app.prompt() def start_app( app_name: str = "", @@ -209,30 +343,36 @@ class StandardsMCPServer(MCPServer): str: The intake questions, or the build prompt when complete. """ if not app_name or not resources: - return ( - "# Start a new app — intake\n\n" - "Gather the following, then call `start_app` again with the values:\n\n" - "1. App name + one-line purpose.\n" - "2. Axis 1 — resources: each resource, its fields, and relationships\n" - " (or paste `openapi.yaml`).\n" - "3. Axis 2 — pipelines: multi-step inputs? families + formats (or `none`).\n" - "4. Auth needed now? (yes/no; auth is deferred — `py-jwt` only).\n" - "5. Integrations: Mongo / ingestion formats / email / LLM provider.\n\n" - "Then: `start_app(app_name=..., resources=..., pipelines=..., auth=...)`.\n" - ) + return _INTAKE_TEXT + return _build_text(app_name, resources, pipelines, auth) + + @self.app.prompt() + def trial(app_name: str = "", build_path: str = "") -> str: + """Prompt a self-contained trial build into a scratch directory. + + Use this to validate the standard end-to-end without touching a real + app: generate into a throwaway path, prove it builds, and record + findings. + + Args: + app_name (str): App to build for the trial. + build_path (str): Where to write the trial app. + + Returns: + str: The trial prompt. + """ + target = build_path or f"apps/{app_name or ''}-trial" return ( - f"# Build: {app_name}\n\n" - "## Spec\n" - f"- resources: {resources}\n" - f"- pipelines: {pipelines or 'none'}\n" - f"- auth: {auth}\n\n" - "## Method (deterministic)\n" - "1. Read `doc://modules/build_recipe` and follow it step by step.\n" - "2. Read `doc://modules/genericity_matrix` (Tier A/B/C) and " - "`doc://modules/layering_rules`.\n" - "3. Call `find_library(capability)` for db, di, routing, ingestion, " - "and pipeline; never reimplement a Tier A library.\n" - "4. Pull exact files with `list_app_components()` and " - "`get_app_component(kind, resource=..., family=...)`.\n" - "5. Author only Tier C; keep Tier B as shipped.\n" + f"# Trial: {app_name or ''}\n\n" + f"Build into `{target}` (a throwaway path) and validate the " + "standard end-to-end.\n\n" + "## Steps\n" + "1. Run the `build` prompt to get the deterministic build prompt.\n" + f"2. Scaffold into `{target}` only — never an existing app.\n" + "3. Prove it: `python -c \"import main\"` and `pytest`.\n" + "4. Record every friction point as a finding: missing component, " + "wrong codegen shape, ambiguous doc, or missing prompt.\n" + "5. Do not fix the standard from inside the trial; report findings " + "back so the standard can be amended.\n\n" + f"Delete `{target}` once you are satisfied.\n" ) diff --git a/standards/lib/01_build_recipe/index.html b/standards/lib/01_build_recipe/index.html index b86b6d7..8bfff72 100644 --- a/standards/lib/01_build_recipe/index.html +++ b/standards/lib/01_build_recipe/index.html @@ -330,6 +330,15 @@ + + +
    • + + + 🔧 The codegen fast path, in detail + + +
    • @@ -638,6 +647,15 @@ +
    • + +
    • + + + 🔧 The codegen fast path, in detail + + +
    • @@ -677,9 +695,9 @@

      Build Recipe

      Summary

      The ordered, deterministic checklist for generating a new application of the -standard shape. Every step is fixed; only the resource model (step 2) and the -pipeline family (step 7) need app-specific authoring.

      -

      This page is served by the standards MCP as doc://modules/build_recipe. The +standard shape. Every step is fixed; only the resource model (step 2), the +codegen pass (step 3), and the pipeline family (step 8) need app-specific work.

      +

      This page is served by the standards MCP as docs://modules/build_recipe. The start_app prompt on that server collects the two axes and hands off here; pull exact files with get_app_component.


      @@ -777,18 +795,34 @@ environment-only; never commit them.

      69 70 71 -72
    • # Build a new application from the Aetos Standards blueprint
      +72
      +73
      +74
      +75
      +76
      +77
      +78
      +79
      +80
      +81
      +82
      +83
      +84
      +85
      +86
      +87
      +88
      # Build a new application from the Aetos Standards blueprint
       
       You are coding a complete new backend application conforming to the Aetos
      -standard app blueprint. Execute every step in order. Only steps 2 and 3 use the
      +standard app blueprint. Execute every step in order. Only steps 2–4 use the
       app-specific details provided by the user; everything else is deterministic.
       
       ## Before writing any code
       
       Connect to the `standards` MCP server and read:
      -- `doc://nav` (the module list)
      -- `doc://modules/genericity_matrix` (Tier A library / Tier B template / Tier C authored)
      -- `doc://modules/layering_rules` (the dependency rules)
      +- `docs://nav` (the module list)
      +- `docs://modules/genericity_matrix` (Tier A library / Tier B template / Tier C authored)
      +- `docs://modules/layering_rules` (the dependency rules)
       
       Then:
       - call `find_library` with `db`, `di`, `routing`, `ingestion`, `pipeline`, `docs`
      @@ -799,56 +833,72 @@ environment-only; never commit them.

      ## Steps -1. **Scaffold** — copy `standards/src/templates/app/` into `apps/<your-app>/`. - Keep the tree: `domain/`, `infra/`, `interfaces/`, `pipelines/`, `tests/`, - `scripts/`, plus `pyproject.toml`, `openapi.yaml`, `config.py`, `init.py`, - `main.py`, `worker.py`, `Dockerfile`, `docker-compose.yaml`, `.env.example`. - -2. **Author the spec (app-specific)** — write `openapi.yaml`: resources, - schemas, `operationId`s, security, and extension metadata. This is the - contract and drives all routing. - -3. **Write each CRUD slice (app-specific)** — per resource create the four-file - slice: `models.py`, `ports/db.py`, `ports/service.py`, and optional - `converters.py`. Follow the CRUD template. - -4. **Wire the composition root** — in `init.py`, build leaf repositories, then - dependent repositories, then services; add infra adapters (db, cache, stores, - progress bus, fetchers). Expose `get_svc` overloads. - -5. **Add routes** — create `interfaces/<resource>/routes.py` thin handlers bound - to `operationId`s and re-export them in `interfaces/openapi.py`. Add the cache - decorator to read endpoints. - -6. **Bootstrap app and worker** — confirm `main.py` runs the OpenAPI-first app - with its lifespan. If the app has long-running jobs, adapt `worker.py` to poll - the job collection, run the pipeline, and publish progress. - -7. **Add pipeline families (only if data-driven)** — copy `pipelines/example/` - to `pipelines/<family>/` with `abc.py`, `impl.py`, `pipeline.yaml`, - `resources/`, and reuse `pipelines/shared/` (loader + registry). Register the - root. Add fixtures and a parity test. - -8. **Add infrastructure only where needed** — Tier A capability: use the - library. Tier B capability: copy the template. Never hand-write a Tier A - library. - -9. **Tests** — integration tests with fakes at the HTTP edge; unit tests for - domain and pipeline logic; a config-consistency test for rule-driven - pipelines. - -10. **Scripts and seeds** — operator scripts under `scripts/` using - `init()` / `get_svc()` / `shutdown()`; run as `python -m scripts.<name>`. - -11. **Docs** — author GSDFC docstrings, generate `.pyi` stubs, mark the package - `py.typed`, and build the docs. - -**Definition of done:** -- `python -c "import main"` succeeds. -- `pytest` passes. -- The app serves every operation in the spec; the worker drains its queue. -- No domain module imports a web framework. -- No Tier A library was reimplemented. +1. **Scaffold** — copy `standards/src/templates/app/` into `apps/<your-app>/` + (or pull the same tree via `get_app_template`). Keep the tree: `domain/`, + `infra/`, `interfaces/`, `pipelines/`, `tests/`, `scripts/`, plus + `pyproject.toml`, `openapi.yaml`, `config.py`, `init.py`, `main.py`, + `worker.py`, `Dockerfile`, `docker-compose.yaml`, `.env.example`. + +2. **Author the spec (app-specific)** — write `openapi.yaml`: resources, + schemas, `operationId`s, security, and extension metadata. This is the + contract and drives all routing. Every operation MUST have an `operationId`. + +3. **Generate the scaffold (codegen)** — from the spec, generate the wire + models and the exact handler signatures: + ```bash + openapi-first models openapi.yaml -o _gen/models.py + openapi-first routes openapi.yaml -o _gen/routes \ + --use-models --models-module _gen.models --inject-request --package + ``` + `_gen/models.py` is the **wire contract** (Pydantic, one class per + `components.schemas` entry). `_gen/routes/<resource>/routes.py` lists every + `operationId` with its exact parameter signature. Treat `_gen/` as generated + (never hand-edit it) — the canonical handlers live in `interfaces/`. + +4. **Write each CRUD slice (app-specific)** — per resource create the four-file + slice: `models.py` (domain model, **no `id`**), `ports/db.py` + (`class XDocument(BaseDocument, X)` — id comes from `BaseDocument`), + `ports/service.py`, and optional `converters.py`. Follow the CRUD template. + +5. **Wire the composition root** — in `init.py`, build leaf repositories, then + dependent repositories, then services; add infra adapters (db, cache, stores, + progress bus, fetchers). Expose `get_svc` overloads. + +6. **Add routes** — create `interfaces/<resource>/routes.py` thin handlers whose + names match the `operationId`s (use the signatures from step 3 as your + checklist) and re-export them in `interfaces/openapi.py`. Add the cache + decorator to read endpoints. + +7. **Bootstrap app and worker** — confirm `main.py` runs the OpenAPI-first app + with its lifespan. If the app has long-running jobs, adapt `worker.py` to poll + the job collection, run the pipeline, and publish progress. + +8. **Add pipeline families (only if data-driven)** — copy `pipelines/example/` + to `pipelines/<family>/` with `abc.py`, `impl.py`, `pipeline.yaml`, + `resources/`, and reuse `pipelines/shared/` (loader + registry). Register the + root. Add fixtures and a parity test. + +9. **Add infrastructure only where needed** — Tier A capability: use the + library. Tier B capability: copy the template. Never hand-write a Tier A + library. + +10. **Tests** — integration tests with fakes at the HTTP edge; unit tests for + domain and pipeline logic; a config-consistency test for rule-driven + pipelines. Include `tests/integration/test_openapi_app_builds.py` (imports + the app, asserts `import main` binds every operation). + +11. **Scripts and seeds** — operator scripts under `scripts/` using + `init()` / `get_svc()` / `shutdown()`; run as `python -m scripts.<name>`. + +12. **Docs** — author GSDFC docstrings, generate `.pyi` stubs, mark the package + `py.typed`, and build the docs. + +**Definition of done:** +- `python -c "import main"` succeeds. +- `pytest` passes. +- The app serves every operation in the spec; the worker drains its queue. +- No domain module imports a web framework. +- No Tier A library was reimplemented.

      🧭 Where the steps land

      @@ -873,53 +923,94 @@ environment-only; never commit them.

      — -3 · CRUD slices +3 · Codegen +_gen/ (generated) +spec, openapi-first + + +4 · CRUD slices domain/<resource>/ spec, CRUD template -4 · Wiring +5 · Wiring init.py every slice -5 · Routes +6 · Routes interfaces/<resource>/ services, openapi route registry -6 · Bootstrap +7 · Bootstrap main.py, worker.py composition root -7 · Pipelines +8 · Pipelines pipelines/<family>/, shared/ library pipeline engine -8 · Infra +9 · Infra infra/ domain ports -9 · Tests +10 · Tests tests/ app + fixtures -10 · Scripts +11 · Scripts scripts/ composition root -11 · Docs +12 · Docs docstrings + docs build code, Doc Forge
      +

      🔧 The codegen fast path, in detail

      +

      openapi-first is spec → code only (it never generates a spec). Use it to erase +the two mechanical parts of the build:

      + + + + + + + + + + + + + + + + + + + + +
      Generated artifactReplacesNotes
      _gen/models.pyhand-writing Pydantic wire modelsone class per components.schemas entry
      _gen/routes/<resource>/routes.pyhand-typing handler signaturesnames == operationIds; request: Request injected
      +

      Rules of the road:

      +
        +
      • Wire models are not domain models. _gen/models.py is the HTTP contract; + domain/<resource>/models.py is your business shape. If the spec's schema has + a required id: str, keep it off the domain model and let the document get its + id from BaseDocument.
      • +
      • Resource names are sanitized. /fetch-requests → fetch_requests.py.
      • +
      • Multipart bodies expand to UploadFile = File(...) / Form(...) params.
      • +
      • _gen/ is disposable. Regenerate on every spec change; git diff shows + exactly what moved.
      • +
      +

      ⚠️ Definition of done

      • python -c "import main" succeeds.
      • diff --git a/standards/lib/04_spec_first_routing/index.html b/standards/lib/04_spec_first_routing/index.html index 6fb32ec..5c63cf1 100644 --- a/standards/lib/04_spec_first_routing/index.html +++ b/standards/lib/04_spec_first_routing/index.html @@ -414,6 +414,15 @@ + + +
      • + + + Authless apps + + +
      • @@ -665,6 +674,15 @@ +
      • + +
      • + + + Authless apps + + +
      • @@ -763,10 +781,33 @@ can resolve operationId across the whole app:

      • Security is declared once in the spec and enforced by the framework; handlers contain no auth code.
      +

      Authless apps

      +

      Auth is deferred in the standard: the same spec-first app runs with or +without it, because security lives entirely in the spec, never in handlers. +For an app that needs no auth yet, do not invent per-handler auth checks — +just omit the security keys:

      +
        +
      • Remove the top-level security: block.
      • +
      • Remove components.securitySchemes.
      • +
      • Do not add security: [] to operations. An explicit empty array is a + per-operation override that means "public", and it only matters when a + global security block exists. Leaving every operation without a security + key is the cleanest authless spec.
      • +
      +

      The handlers, services, and composition root are unchanged. When auth is needed +later, add the global security block and an introspecting dependency; only +handlers that must opt out get security: [].

      Codegen

      -

      The openapi_first library can scaffold models and route stubs from the spec, -and ships app templates (CRUD app, model app, service). Prefer codegen over -hand-writing boilerplate.

      +

      openapi_first scaffolds models and route stubs from the spec (see the +Build Recipe, step 3):

      +
      1
      +2
      +3
      openapi-first models openapi.yaml -o _gen/models.py
      +openapi-first routes openapi.yaml -o _gen/routes \
      +  --use-models --models-module _gen.models --inject-request --package
      +
      +

      Prefer codegen over hand-writing boilerplate. The result is the wire contract +and a set of operationId-named signatures — not the canonical handlers.

      diff --git a/standards/lib/05_crud_template/index.html b/standards/lib/05_crud_template/index.html index 49c09c2..e9473db 100644 --- a/standards/lib/05_crud_template/index.html +++ b/standards/lib/05_crud_template/index.html @@ -400,6 +400,15 @@ +
    • + +
    • + + + The id rule (model vs. document) + + +
    • @@ -629,6 +638,15 @@ +
    • + +
    • + + + The id rule (model vs. document) + + +
    • @@ -710,8 +728,53 @@ makes the codebase predictable and generatable.

      service.py # service class + factory converters.py # (optional) transforms
      +

      The id rule (model vs. document)

      +

      Two different objects, two different jobs:

      + + + + + + + + + + + + + + + + + + + + + + + +
      ObjectLives inHas id?Purpose
      Domain modeldomain/<resource>/models.pyNobusiness shape, framework-free
      Documentdomain/<resource>/ports/db.pyYes (from BaseDocument)Mongo persistence shape
      +

      BaseDocument (mongo-ops) supplies id: Optional[PyObjectId] (aliased to +_id) plus created_at / updated_at. The domain model must not declare +id; Mongo owns it.

      +
      +

      Trap — generated wire models. openapi-first models produces the HTTP +contract (see the Build Recipe, step 3). Response +schemas frequently declare a required id: str, so a generated class is +not a domain model. If you write +class ItemDocument(Item, BaseDocument) and Item re-exports a required +id: str, that field shadows BaseDocument.id and no Mongo _id can ever be +set. Fix it one of three ways:

      +
        +
      1. Preferred: keep the domain model id-free (hand-author it from the CRUD + template), and keep the required id only on the wire model.
      2. +
      3. Order the bases so the document wins: + class ItemDocument(BaseDocument, Item).
      4. +
      5. Keep a separate document type and translate with ports/converters.py.
      6. +
      +

      models.py

      -

      A framework-free Pydantic model describing the business shape.

      +

      A framework-free Pydantic model describing the business shape (no id).

      1
       2
       3
      class Item(BaseModel):
      @@ -730,16 +793,18 @@ The repository factory selects the right repository flavour.

      7 8 9 -10
      class ItemDocument(Item, BaseDocument):   # BaseDocument adds id/created_at/...
      -    pass
      -
      -def create_item_repo(*, owner_repo=None) -> ItemRepository:
      -    return repo_factory(
      -        collection="items",
      -        document=ItemDocument,
      -        populate_rules=[PopulateRule(field="owner", collection="users")],
      -        cache=None,               # or a CachedBaseRepository config
      -    )
      +10
      +11
      # Item is id-free; BaseDocument adds id/created_at/updated_at.
      +class ItemDocument(Item, BaseDocument):
      +    pass
      +
      +def create_item_repo(*, owner_repo=None) -> ItemRepository:
      +    return repo_factory(
      +        collection="items",
      +        document=ItemDocument,
      +        populate_rules=[PopulateRule(field="owner", collection="users")],
      +        cache=None,               # or a CachedBaseRepository config
      +    )
       

      ports/service.py

      The service is built through the DI library, injecting the repository. The diff --git a/standards/lib/12_genericity_matrix/index.html b/standards/lib/12_genericity_matrix/index.html index b443fe0..929fc64 100644 --- a/standards/lib/12_genericity_matrix/index.html +++ b/standards/lib/12_genericity_matrix/index.html @@ -519,6 +519,15 @@ +

    • + +
    • + + + Tier B (patterns) — port from the reference app + + +
    • @@ -620,6 +629,15 @@ +
    • + +
    • + + + Tier B (patterns) — port from the reference app + + +
    • @@ -721,13 +739,16 @@ for its API. Writing a private copy of any of these is a defect.

      Tier B — Template-provided (copy from templates/app, then adapt)

      These are reusable internal components shipped inside the template — they are not independent libraries. Copy them as-is, then rename/populate only what is -app-specific. They give every app the same wiring shape as the reference app.

      +app-specific. They give every app the same wiring shape as the reference app. +Fetch them by kind with get_app_component, or the whole tree with +get_app_template.

      + @@ -735,81 +756,114 @@ app-specific. They give every app the same wiring shape as the reference app.

      Layering & dependency rule + + + + + + + - - - - - - - + + + - + + - - - - - - + + + + + + + + +
      Component Template location ActionShipped
      the package layout + import rules keep✅
      Composition root shape init.py (get_svc/get_db, idempotency) keep✅
      Spec-first web edge interfaces/openapi.py, thin handlers bound to operationId keep✅
      CRUD slice pattern domain/<resource>/{models.py, ports/db.py, ports/service.py, converters.py} rename + fill✅
      Generic CRUD service infra/adapters/service.py (CrudsServiceAdaptor) keep✅
      Repository factory infra/db/repos/mongo.py (_repo_factory) keep✅
      Worker poll + status loop worker.py keep✅
      Progress bus + streamingbus port + in-memory adapter + SSE endpointkeep
      Human-in-the-loopdurable pause + question document + resolve endpoint + resume mapProgress businfra/adapters/sse.py (keyed in-memory bus) + an app SSE endpoint keep✅
      HTTP response cachecache_response decorator + generation invalidationinfra/http_cache.py (cache_response + generation invalidation) keep✅
      Key-value storeKV store port + Redis adapter + in-memory fallbackkeep
      LLM client poolprovider-select + retry pool scaffoldinfra/adapters/kv.py (port + in-memory + Redis adaptor) keep✅
      Pipeline scaffold pipelines/shared/{ports,loader,registry}.py + pipelines/<family>/ rename + fill✅
      Operator scripts scripts/ (init() / get_svc() / shutdown()) rename✅
      Testing harness integration fakes + fixture-parity pattern keep✅
      Project scaffolding pyproject.toml, Dockerfile, docker-compose.yaml, .env.example, openapi.yaml rename + fill✅
      +

      Tier B (patterns) — port from the reference app

      +

      A few Tier B capabilities are real and reusable, but are shaped by the app or +the provider, so the template does not ship them verbatim — copying them +blindly would carry app-specific names. Treat them as patterns: lift the shape +from the reference app and rename per app.

      + + + + + + + + + + + + + + + + + + + + +
      ComponentReference locationActionShipped
      Human-in-the-loopdurable pause + question document + resolve endpoint + resume mapport + reshape✗
      LLM client poolprovider-select + retry/rotation pool scaffoldport + reshape✗
      diff --git a/standards/lib/13_library_registry/index.html b/standards/lib/13_library_registry/index.html index 08316ee..589a524 100644 --- a/standards/lib/13_library_registry/index.html +++ b/standards/lib/13_library_registry/index.html @@ -661,7 +661,7 @@ every application. Use them. Writing a private copy of any capa is a defect.

      Each library also exposes its own documentation as an MCP server. An AI agent can read a library's full API by connecting to its MCP and reading -doc://index, doc://nav, and doc://modules/{module}.

      +docs://index, docs://nav, and docs://modules/{module}.


      Catalog

      @@ -790,8 +790,8 @@ endpoint. Query it before writing any infrastructure.

      Notes

      • Hexa is a full library with its own MCP server on :8008: read - doc://index, doc://nav, and doc://modules/{module} (e.g. - doc://modules/hexa.container) to discover the DI surface — build(cls, *, + docs://index, docs://nav, and docs://modules/{module} (e.g. + docs://modules/hexa.container) to discover the DI surface — build(cls, *, instances, config) and the PortNode model.
      • Auth Server is deferred: it has no pyproject.toml/importable package and no MCP bundle. Do not reach for it yet; py-jwt covers token primitives.
      • diff --git a/standards/lib/search/search_index.json b/standards/lib/search/search_index.json index f5ed8a8..de0fab5 100644 --- a/standards/lib/search/search_index.json +++ b/standards/lib/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Standards Reference","text":"

        The reference half of the Standards description: the enforceable rules, patterns, and the internal library catalog that agents consult while coding.

        • Build Recipe \u2014 the ordered, agentic checklist for a new app.
        • Layering Rules \u2014 the strict request \u2192 domain \u2192 repository layering and the rules that enforce it.
        • Composition Root \u2014 a single composition point wiring the whole app.
        • Spec-First Routing \u2014 the OpenAPI contract as the routing source of truth.
        • CRUD Template \u2014 the standardized generic resource endpoint scaffold.
        • Pipeline Scaffold \u2014 the deterministic pipeline skeleton for multi-stage jobs.
        • Worker & Progress \u2014 queued work, progress reporting, and visibility.
        • Human-in-the-Loop \u2014 guardrails and approvals inside automations.
        • Infra Adapters \u2014 how infrastructure connects without leaking into domain.
        • Genericity Matrix \u2014 what is library-backed, template-provided, or authored.
        • Library Registry \u2014 the catalog of internal libraries behind all of it.

        Read the Wiki for the philosophy and rationale; these pages are the reference that mirrors the standards MCP bundle.

        "},{"location":"01_build_recipe/","title":"Build Recipe","text":""},{"location":"01_build_recipe/#summary","title":"Summary","text":"

        The ordered, deterministic checklist for generating a new application of the standard shape. Every step is fixed; only the resource model (step 2) and the pipeline family (step 7) need app-specific authoring.

        This page is served by the standards MCP as doc://modules/build_recipe. The start_app prompt on that server collects the two axes and hands off here; pull exact files with get_app_component.

        "},{"location":"01_build_recipe/#bootstrap","title":"\ud83d\ude80 Bootstrap","text":"

        From the standards docs checkout:

        cp -r <docs>/standards/src/templates/app apps/<your-app>\ncd apps/<your-app>\n

        Install the internal libraries \u2014 editable local checkouts (preferred in development) or the private index (CI / fresh machines). Credentials are environment-only; never commit them.

        # local checkouts\npip install -e ../aetos/openapi-first -e ../aetos/mongo-ops -e ../aetos/hexa \\\n            -e ../aetos/dagpipe -e ../aetos/doc-forge\n# private index\npip install --extra-index-url \\\n  \"https://$PIP_REGISTRY_USERNAME:$PIP_REGISTRY_PASSWORD@pip.aetoskia.com/simple\" .\n
        "},{"location":"01_build_recipe/#agent-prompt-copyable","title":"\ud83d\udccb Agent prompt (copyable)","text":"
        # Build a new application from the Aetos Standards blueprint\n\nYou are coding a complete new backend application conforming to the Aetos\nstandard app blueprint. Execute every step in order. Only steps 2 and 3 use the\napp-specific details provided by the user; everything else is deterministic.\n\n## Before writing any code\n\nConnect to the `standards` MCP server and read:\n- `doc://nav` (the module list)\n- `doc://modules/genericity_matrix` (Tier A library / Tier B template / Tier C authored)\n- `doc://modules/layering_rules` (the dependency rules)\n\nThen:\n- call `find_library` with `db`, `di`, `routing`, `ingestion`, `pipeline`, `docs`\n  and note the Tier A libraries that already exist \u2014 never reimplement one;\n- call `list_app_components()` and pull each file you need with\n  `get_app_component(kind, resource=..., family=...)` instead of writing it from\n  scratch.\n\n## Steps\n\n1. **Scaffold** \u2014 copy `standards/src/templates/app/` into `apps/<your-app>/`.\n   Keep the tree: `domain/`, `infra/`, `interfaces/`, `pipelines/`, `tests/`,\n   `scripts/`, plus `pyproject.toml`, `openapi.yaml`, `config.py`, `init.py`,\n   `main.py`, `worker.py`, `Dockerfile`, `docker-compose.yaml`, `.env.example`.\n\n2. **Author the spec (app-specific)** \u2014 write `openapi.yaml`: resources,\n   schemas, `operationId`s, security, and extension metadata. This is the\n   contract and drives all routing.\n\n3. **Write each CRUD slice (app-specific)** \u2014 per resource create the four-file\n   slice: `models.py`, `ports/db.py`, `ports/service.py`, and optional\n   `converters.py`. Follow the CRUD template.\n\n4. **Wire the composition root** \u2014 in `init.py`, build leaf repositories, then\n   dependent repositories, then services; add infra adapters (db, cache, stores,\n   progress bus, fetchers). Expose `get_svc` overloads.\n\n5. **Add routes** \u2014 create `interfaces/<resource>/routes.py` thin handlers bound\n   to `operationId`s and re-export them in `interfaces/openapi.py`. Add the cache\n   decorator to read endpoints.\n\n6. **Bootstrap app and worker** \u2014 confirm `main.py` runs the OpenAPI-first app\n   with its lifespan. If the app has long-running jobs, adapt `worker.py` to poll\n   the job collection, run the pipeline, and publish progress.\n\n7. **Add pipeline families (only if data-driven)** \u2014 copy `pipelines/example/`\n   to `pipelines/<family>/` with `abc.py`, `impl.py`, `pipeline.yaml`,\n   `resources/`, and reuse `pipelines/shared/` (loader + registry). Register the\n   root. Add fixtures and a parity test.\n\n8. **Add infrastructure only where needed** \u2014 Tier A capability: use the\n   library. Tier B capability: copy the template. Never hand-write a Tier A\n   library.\n\n9. **Tests** \u2014 integration tests with fakes at the HTTP edge; unit tests for\n   domain and pipeline logic; a config-consistency test for rule-driven\n   pipelines.\n\n10. **Scripts and seeds** \u2014 operator scripts under `scripts/` using\n    `init()` / `get_svc()` / `shutdown()`; run as `python -m scripts.<name>`.\n\n11. **Docs** \u2014 author GSDFC docstrings, generate `.pyi` stubs, mark the package\n    `py.typed`, and build the docs.\n\n**Definition of done:**\n- `python -c \"import main\"` succeeds.\n- `pytest` passes.\n- The app serves every operation in the spec; the worker drains its queue.\n- No domain module imports a web framework.\n- No Tier A library was reimplemented.\n
        "},{"location":"01_build_recipe/#where-the-steps-land","title":"\ud83e\udded Where the steps land","text":"

        The steps map onto the layers as follows:

        Step Package / file Depends on 1 \u00b7 Scaffold whole templates/app/ \u2014 2 \u00b7 Spec openapi.yaml \u2014 3 \u00b7 CRUD slices domain/<resource>/ spec, CRUD template 4 \u00b7 Wiring init.py every slice 5 \u00b7 Routes interfaces/<resource>/ services, openapi route registry 6 \u00b7 Bootstrap main.py, worker.py composition root 7 \u00b7 Pipelines pipelines/<family>/, shared/ library pipeline engine 8 \u00b7 Infra infra/ domain ports 9 \u00b7 Tests tests/ app + fixtures 10 \u00b7 Scripts scripts/ composition root 11 \u00b7 Docs docstrings + docs build code, Doc Forge"},{"location":"01_build_recipe/#definition-of-done","title":"\u26a0\ufe0f Definition of done","text":"
        • python -c \"import main\" succeeds.
        • pytest passes.
        • The app serves the spec's operations; the worker drains its queue.
        • No domain module imports a web framework.
        • No Tier A library was reimplemented.
        "},{"location":"01_build_recipe/#related","title":"Related","text":"
        • 02 \u2013 Layering Rules \u00b7 05 \u2013 CRUD Template \u00b7 06 \u2013 Pipeline Scaffold \u00b7 12 \u2013 Genericity Matrix
        "},{"location":"02_layering_rules/","title":"Layering Rules","text":""},{"location":"02_layering_rules/#summary","title":"Summary","text":"

        The dependency direction is fixed. Violating it is a design defect.

        "},{"location":"02_layering_rules/#allowed-imports","title":"Allowed imports","text":"
        interfaces/*      ->  domain/*            (services, ports, models)\ninterfaces/*      ->  infra/adapters/*    (only read-only value objects)\ndomain/*          ->  domain/*            (same layer)\ndomain/*          ->  pipelines/*         (call a pipeline via a port)\ndomain/*/ports/*  ->  infra/*             (bind adapters + libraries at the seam)\ninfra/adapters/*  ->  domain/ports/*      (implement the interface)\nmain.py/init.py   ->  everything          (composition root only)\n
        "},{"location":"02_layering_rules/#forbidden-imports","title":"Forbidden imports","text":"
        domain/*          ->  interfaces/*        (never)\ndomain/*          ->  fastapi/starlette   (never)\ninfra/adapters/*  ->  interfaces/*        (never)\ninterfaces/*      ->  infra/db/*          (state lives in resources, not imports)\n
        "},{"location":"02_layering_rules/#why","title":"Why","text":"
        • The business logic is testable without a server or an event loop.
        • Adapters can be swapped (in-memory fake, real database) without touching business logic.
        • The HTTP edge stays thin and replaceable.
        "},{"location":"02_layering_rules/#the-portadapter-contract","title":"The port/adapter contract","text":"

        The ports/ package is the seam where the domain meets infrastructure. Each domain/<resource>/ports/ module declares the resource's models/document shape and exposes factory functions that bind a concrete adapter \u2014 so a port module is allowed to import from infra/ and from the persistence libraries. Everything outside ports/ in domain/ stays framework- and driver-free.

        domain/<resource>/ports/db.py       -> document model + create_<resource>_repo()\ndomain/<resource>/ports/service.py  -> service + get_<resource>_service()\ninfra/db/repos/<vendor>.py          -> repository adapter factory\ninfra/adapters/service.py           -> generic CRUD service base\n

        The composition root calls the port factories; nothing else constructs an adapter.

        "},{"location":"02_layering_rules/#enforcement","title":"Enforcement","text":"
        • domain/ must never import the web framework (fastapi / starlette) or interfaces/. Only the ports/ seam, infra/, and the composition root touch drivers.
        • Reviewers (and CI) reject import fastapi inside domain/ or infra/.
        "},{"location":"03_composition_root/","title":"Composition Root","text":""},{"location":"03_composition_root/#summary","title":"Summary","text":"

        All wiring happens in exactly one module. It builds a flat resource registry and exposes it to the HTTP layer and to scripts.

        "},{"location":"03_composition_root/#the-resource-registry","title":"The resource registry","text":"

        init.py returns a plain dict[str, Any]. Keys follow two conventions:

        • <resource>_repo \u2014 a repository adapter for a resource.
        • <resource>_svc \u2014 a service for a resource.

        Plus named infrastructure adaptors:

        • http_cache_store \u2014 response cache backend.
        • progress_bus \u2014 progress/event bus.
        • <vendor>_<kind> \u2014 fetchers, clients, stores.

        The API stores the dict on app.state.db_resources during lifespan startup. Handlers read from it; nothing imports concrete adapters.

        "},{"location":"03_composition_root/#shape","title":"Shape","text":"
        _resources: dict = {}\n_initialized = False\n\nasync def init(settings=None) -> dict:\n    global _initialized\n    if _initialized:\n        return _resources\n\n    await setup_database(uri=settings.mongo_uri, db_name=settings.mongo_db)\n\n    # 1. leaf repositories\n    user_repo = create_user_repo()\n\n    # 2. dependent repositories (dependencies passed by keyword)\n    wallet_repo = create_wallet_repo(user_repo=user_repo)\n\n    # 3. services (build via the DI library, injecting the repo)\n    _resources[\"user_repo\"]   = user_repo\n    _resources[\"wallet_repo\"] = wallet_repo\n    _resources[\"user_svc\"]    = get_user_service(user_repo)\n    _resources[\"wallet_svc\"]  = get_wallet_service(wallet_repo)\n\n    # 4. infrastructure adaptors (optional)\n    _resources[\"http_cache_store\"] = HttpCacheStore(...)\n    _resources[\"progress_bus\"]     = ProgressEventBus()\n\n    _initialized = True\n    return _resources\n\nasync def shutdown() -> None:\n    global _initialized\n    await close_stores(_resources)\n    await shutdown_database()\n    _resources.clear()\n    _initialized = False\n
        "},{"location":"03_composition_root/#typed-accessors","title":"Typed accessors","text":"

        Expose a get_svc(name) helper with one @overload per key so scripts get typing without importing concrete classes:

        @overload\ndef get_svc(name: Literal[\"user_svc\"]) -> UserService: ...\n@overload\ndef get_svc(name: Literal[\"wallet_svc\"]) -> WalletService: ...\n\ndef get_svc(name: str):\n    return _resources[name]\n

        Also expose get_db() (raw database handle) and pure helpers such as compute_fingerprint(payload) (canonical hash used for deduplication).

        "},{"location":"03_composition_root/#rules","title":"Rules","text":"
        • init() is idempotent.
        • The composition root is the only place that imports concrete adapters.
        • Handlers never call init(); they read request.app.state.db_resources.
        • Scripts call init() / shutdown() around their work.
        "},{"location":"04_spec_first_routing/","title":"Spec-First Routing","text":""},{"location":"04_spec_first_routing/#summary","title":"Summary","text":"

        The OpenAPI document is the single source of truth for the HTTP contract. Handlers are bound to it by name; there is no decorator router.

        "},{"location":"04_spec_first_routing/#the-contract","title":"The contract","text":"
        • openapi.yaml (or .json) declares paths, schemas, parameters, responses, and security.
        • A custom operationId names the handler function.
        • The framework (openapi_first) validates requests/responses against the spec and dispatches to the handler whose name equals operationId.
        "},{"location":"04_spec_first_routing/#app-bootstrap","title":"App bootstrap","text":"
        # main.py\nfrom openapi_first.app import OpenAPIFirstApp\n\napp = OpenAPIFirstApp(\n    openapi_path=\"openapi.yaml\",\n    routes_module=interfaces.openapi,   # module whose namespace holds handlers\n    title=\"<App> API\",\n    version=\"0.1.0\",\n    lifespan=lifespan,\n)\n

        lifespan calls the composition root:

        @asynccontextmanager\nasync def lifespan(app):\n    app.state.db_resources = await init()\n    yield\n    await shutdown()\n
        "},{"location":"04_spec_first_routing/#handler-registry","title":"Handler registry","text":"

        interfaces/openapi.py is a flat re-export of every handler, so the framework can resolve operationId across the whole app:

        from interfaces.item.routes import *\nfrom interfaces.wallet.routes import *\nfrom interfaces.progress.sse import *\n
        "},{"location":"04_spec_first_routing/#handler-shape","title":"Handler shape","text":"

        Handlers are thin and synchronous with respect to business logic:

        async def create_item(request: Request, body: dict):\n    svc = request.app.state.db_resources[\"item_svc\"]\n    return await svc.create(Item.model_validate(body))\n
        "},{"location":"04_spec_first_routing/#conventions","title":"Conventions","text":"
        • operationId naming: list_<resource>, create_<resource>, get_<resource>, update_<resource>, delete_<resource>.
        • Read-only endpoints may use a cache decorator keyed on path + query + user.
        • Validation errors -> 400; not found -> 404; delete -> 204.
        • Security is declared once in the spec and enforced by the framework; handlers contain no auth code.
        "},{"location":"04_spec_first_routing/#codegen","title":"Codegen","text":"

        The openapi_first library can scaffold models and route stubs from the spec, and ships app templates (CRUD app, model app, service). Prefer codegen over hand-writing boilerplate.

        "},{"location":"05_crud_template/","title":"CRUD Template","text":""},{"location":"05_crud_template/#summary","title":"Summary","text":"

        Every resource follows the same four-file slice. Repetition is intentional: it makes the codebase predictable and generatable.

        "},{"location":"05_crud_template/#the-slice","title":"The slice","text":"
        domain/<resource>/\n    models.py               # pure Pydantic model (no DB fields)\n    ports/\n        db.py               # document model + repository factory\n        service.py          # service class + factory\n        converters.py       # (optional) transforms\n
        "},{"location":"05_crud_template/#modelspy","title":"models.py","text":"

        A framework-free Pydantic model describing the business shape.

        class Item(BaseModel):\n    name: str\n    quantity: int = 0\n
        "},{"location":"05_crud_template/#portsdbpy","title":"ports/db.py","text":"

        The persistence model extends the business model and adds database concerns. The repository factory selects the right repository flavour.

        class ItemDocument(Item, BaseDocument):   # BaseDocument adds id/created_at/...\n    pass\n\ndef create_item_repo(*, owner_repo=None) -> ItemRepository:\n    return repo_factory(\n        collection=\"items\",\n        document=ItemDocument,\n        populate_rules=[PopulateRule(field=\"owner\", collection=\"users\")],\n        cache=None,               # or a CachedBaseRepository config\n    )\n
        "},{"location":"05_crud_template/#portsservicepy","title":"ports/service.py","text":"

        The service is built through the DI library, injecting the repository. The generic CRUD base provides create/read/update/patch/delete/search/....

        class ItemService(CrudsServiceAdaptor[Item, ItemDocument]):\n    model = Item\n    schema = ItemDocument\n\ndef get_item_service(repo) -> ItemService:\n    return build(ItemService, instances={\"repo\": repo})\n
        "},{"location":"05_crud_template/#naming-conventions","title":"Naming conventions","text":"Concept Convention collection plural snake case (items) document <Resource>Document repository factory create_<resource>_repo service <Resource>Service service factory get_<resource>_service resource key <resource>_svc operationId list_items, create_item, ..."},{"location":"05_crud_template/#optional-files","title":"Optional files","text":"
        • converters.py \u2014 pure functions mapping between domain types.
        • ports/enrichment.py \u2014 adapts a pipeline into an async callable the service can invoke (used when a resource is populated by a pipeline).
        "},{"location":"06_pipeline_scaffold/","title":"Pipeline Scaffold","text":""},{"location":"06_pipeline_scaffold/#summary","title":"Summary","text":"

        Data-driven processing is expressed as a composition graph. The graph is built by a DI library; execution is driven by a DAG engine. Stages are pure and individually testable.

        "},{"location":"06_pipeline_scaffold/#when-to-use-a-pipeline","title":"When to use a pipeline","text":"

        Use a pipeline when a resource is produced by a multi-step transformation of an input (a document, a file, a message) rather than by a simple CRUD write.

        "},{"location":"06_pipeline_scaffold/#structure-of-a-family","title":"Structure of a family","text":"

        A family is one input shape (e.g. a file format or a message kind). Each family has the same skeleton:

        pipelines/<family>/\n    abc.py            # port contracts (stage interfaces + root interface)\n    impl.py           # concrete stages + root implementation\n    pipeline.yaml     # graph spec: nodes, edges, roots, schema\n    resources/\n        pipeline.yaml # runtime spec consumed by the loader\n        patterns/*.yaml  # rule fragments (pattern registry)\n    banks/<variant>.py   # optional variants that re-pin a few slots\n
        "},{"location":"06_pipeline_scaffold/#shared-scaffolding","title":"Shared scaffolding","text":"
        pipelines/shared/\n    ports.py     # RegistryPort, FinalizePort, etc.\n    loader.py    # build(root_cls) -> wires the graph + engine\n    registry.py  # resolve_root(...) -> root class for a (kind, variant)\n
        "},{"location":"06_pipeline_scaffold/#the-loader","title":"The loader","text":"
        def build(root_cls, *, instances=None, config=None):\n    # merge resources/patterns/*.yaml into a pattern registry\n    # bind registry/config into each graph node by id\n    # build the schema (state) class and the engine\n    root = root_cls()\n    root._engine = Engine(graph, state_cls, payload_cls)\n    return root\n
        "},{"location":"06_pipeline_scaffold/#the-registry","title":"The registry","text":"

        A single lookup replaces hand-rolled if/else selection:

        _FAMILIES = {\n    \"csv\":  {\"acme\": {\"heuristic\": AcmeCsvPipeline}},\n    \"json\": {\"globex\": {\"heuristic\": GlobexJsonPipeline}},\n}\n\ndef resolve_root(kind, variant, mode=\"heuristic\"):\n    return _FAMILIES[kind][variant][mode]\n

        Adding a new variant means adding one class and one registry entry.

        "},{"location":"06_pipeline_scaffold/#driving-a-pipeline","title":"Driving a pipeline","text":"

        Stages are driven explicitly so intermediate state can be persisted:

        pipeline = build(resolve_root(kind, variant))\ncontent = pipeline.ingest.materialize(raw_bytes)\nrecords = pipeline.reader.read(content)\nitems   = pipeline.normalize.build(records)\n
        "},{"location":"06_pipeline_scaffold/#testing","title":"Testing","text":"

        Because stages are pure, test each stage in isolation and test the assembled root against fixture inputs whose expected outputs are stored as golden files. A parity test replays fixtures headlessly.

        "},{"location":"07_worker_and_progress/","title":"Worker & Progress","text":""},{"location":"07_worker_and_progress/#summary","title":"Summary","text":"

        Long-running work runs in a background worker. Jobs are durable documents that move through an explicit status machine. Progress is streamed to clients.

        "},{"location":"07_worker_and_progress/#the-status-machine","title":"The status machine","text":"
        pending -> processing -> completed\n                     \\-> failed    (retried up to N times)\n                     \\-> paused    (waiting for human input)\n
        • pending \u2014 queued, eligible for pickup.
        • processing \u2014 claimed by a worker.
        • paused \u2014 blocked on a human-in-the-loop question.
        • failed \u2014 error; retry_count increments until a max, then skipped.
        • completed \u2014 terminal success.
        "},{"location":"07_worker_and_progress/#the-poll-loop","title":"The poll loop","text":"
        async def poll(resources):\n    backoff = MIN_POLL\n    while True:\n        jobs = await job_svc.search(filter={\"status\": \"pending\"}, limit=100)\n        backoff = MIN_POLL if jobs else min(backoff * 2, MAX_POLL)\n        for job in jobs:\n            if (job.retry_count or 0) >= MAX_RETRIES:\n                continue\n            await job_svc.update_status(job.id, \"processing\")\n            try:\n                await process(job, resources)\n            except PauseForResolution:\n                await job_svc.update_status(job.id, \"paused\")\n            except Exception as e:\n                await job_svc.update_status(\n                    job.id, \"failed\", error=str(e),\n                    retry_count=(job.retry_count or 0) + 1,\n                )\n        await asyncio.sleep(backoff)\n

        Tunables live in environment variables: WORKER_POLL_MIN, WORKER_POLL_MAX, WORKER_MAX_RETRIES.

        "},{"location":"07_worker_and_progress/#progress-events","title":"Progress events","text":"

        A worker publishes structured progress events to a bus (a port). The HTTP edge exposes a streaming endpoint that replays events for a given job id.

        class ProgressEvent(BaseModel):\n    job_id: str\n    step: str\n    status: str            # started | progress | completed | skipped | paused\n    message: dict | None = None\n

        The bus is an in-memory adapter by default; because it is a port, it can be swapped for a durable broker. Emit from the orchestrator at each stage:

        await self.bus.publish(ProgressEvent(job_id=jid, step=\"normalize\",\n                                     status=\"completed\", message={\"count\": n}))\n
        "},{"location":"07_worker_and_progress/#streaming-endpoint","title":"Streaming endpoint","text":"
        async def stream_job_events(request, id):\n    bus = request.app.state.db_resources.get(\"progress_bus\")\n    if bus is None:\n        return {\"events\": []}\n\n    async def gen():\n        sent = 0\n        while True:\n            events = bus.get_events(id)\n            for e in events[sent:]:\n                yield f\"data: {e.model_dump_json(exclude_none=True)}\\n\\n\"\n                sent += 1\n            await asyncio.sleep(0.5)\n\n    return StreamingResponse(gen(), media_type=\"text/event-stream\")\n

        Known limitation. The default in-memory bus is per-process. If the worker and the API run as separate processes, events published by the worker are not visible to the API. For production, back the bus with a shared broker. The port boundary makes this a one-adapter change.

        "},{"location":"08_human_in_loop/","title":"Human-in-the-Loop","text":""},{"location":"08_human_in_loop/#summary","title":"Summary","text":"

        Some inputs are ambiguous and must be resolved by a human before processing can continue. The blueprint models this as a durable pause: an exception is raised, a question is recorded, the job pauses, and later resumes.

        "},{"location":"08_human_in_loop/#the-mechanism","title":"The mechanism","text":"
        1. During processing, a stage raises a domain exception carrying the context needed to ask the question:
        raise PauseForResolution(\n    job_id=jid,\n    reference=\"<stable key for this decision>\",\n    options=[...],\n    context={...},\n)\n
        1. The orchestrator catches it, records a question document, and re-raises so the worker marks the job paused.
        2. A client lists pending questions and answers one:
        GET  /jobs/{id}/questions            -> list pending questions\nPOST /questions/{qid}/resolve  {choice}  -> record the choice\n
        1. When the last pending question is resolved, the resolve endpoint flips the parent job back to pending.
        2. The worker picks it up again, rebuilds a resolution map from previously answered questions, and seeds it into the pipeline so the same decision is not asked twice.
        "},{"location":"08_human_in_loop/#the-question-document","title":"The question document","text":"
        class Question(BaseDocument):\n    job_id: str\n    reference: str            # stable key: stage|field|value\n    status: str               # pending | resolved\n    options: list[dict]\n    context: dict\n    chosen: dict | None = None\n    resolved_at: str | None = None\n
        "},{"location":"08_human_in_loop/#resume-map","title":"Resume map","text":"

        The worker turns answered questions into a lookup keyed by reference, then injects it into the pipeline so that stage can skip re-raising:

        resolved = {q.reference: q.chosen for q in await svc.search(\n    filter={\"job_id\": jid, \"status\": \"resolved\"})}\n\npipeline = build(root, instances={\"pause\": handler}, config={\"resolved\": resolved})\n
        "},{"location":"08_human_in_loop/#resolve-endpoint","title":"Resolve endpoint","text":"
        async def resolve_question(request, id, body):\n    svc = request.app.state.db_resources[\"question_svc\"]\n    q = await svc.read(id)\n    await svc.update(id, {\"chosen\": body[\"chosen\"], \"status\": \"resolved\",\n                          \"resolved_at\": now()})\n\n    pending = await svc.search(filter={\"job_id\": q.job_id, \"status\": \"pending\"},\n                               limit=1)\n    if not pending:\n        jobs = request.app.state.db_resources[\"job_svc\"]\n        job = await jobs.read(q.job_id)\n        if job and job.status == \"paused\":\n            await jobs.update_status(q.job_id, \"pending\")\n    return q\n
        "},{"location":"08_human_in_loop/#cli-parity","title":"CLI parity","text":"

        The same flow is available through scripts/CLI: list, get, create, questions, resolve, retry.

        "},{"location":"09_infra_adaptors/","title":"Infra Adapters","text":""},{"location":"09_infra_adaptors/#summary","title":"Summary","text":"

        All external systems are reached through adapters in infra/. Prefer an existing internal library over writing a new adapter; where no library exists, copy the Tier B scaffold from templates/app. See the Genericity Matrix for the Tier A / B / C split.

        "},{"location":"09_infra_adaptors/#database-mongodb","title":"Database (MongoDB)","text":"
        • A single connection manager owns the client lifecycle. setup_database(uri, db_name) / shutdown_database().
        • A repository factory selects the repository flavour:
        repo_factory(collection, document, engine=None,\n             populate_rules=None, cache=None)\n
        • base repository \u2014 plain CRUD;
        • populating repository \u2014 resolves referenced documents (foreign keys);
        • cached repository \u2014 an LRU/TTL cache in front of reads.

        • Referenced documents are described declaratively:

        PopulateRule(field=\"owner\", collection=\"users\",\n             nested_rules=[], max_depth=2, projection=None)\n
        • Transactions use a session context manager provided by the library.

        Tier A \u2014 Library (Mongo Ops). Do not write raw Motor calls in the domain; consult the registry and the library's MCP before touching the driver.

        "},{"location":"09_infra_adaptors/#http-response-cache","title":"HTTP response cache","text":"

        Tier B \u2014 template. Copy from templates/app; not a standalone library.

        A generic GET cache sits behind a decorator.

        @cache_response(ttl=60)\nasync def list_items(request): ...\n
        • Keyed on path + query + user.
        • No-op when no store is wired.
        • invalidate(*resources) bumps a generation counter for write-triggered invalidation.
        "},{"location":"09_infra_adaptors/#event-progress-bus","title":"Event / progress bus","text":"

        Tier B \u2014 template. Copy from templates/app.

        A port with an in-memory default adapter (publish, get_events). Swap for a durable broker in production (see Worker & Progress).

        "},{"location":"09_infra_adaptors/#external-clients-ingestion","title":"External clients (ingestion)","text":"

        External fetchers (files, mail, HTTP) are adapters that wrap ingestion libraries. Construct them in the composition root and inject into orchestrators.

        Tier A \u2014 Libraries: Omniread (formats) and Mail Intake (email).

        "},{"location":"09_infra_adaptors/#llm-clients","title":"LLM clients","text":"

        Tier B \u2014 template. Copy the pool scaffold from templates/app.

        When a pipeline calls a model provider, route through a pool adaptor that handles provider selection, retries, and client lifecycle:

        pool = setup_async_pool(provider=settings.llm_provider)\nresult = await pool.complete(model=\"...\", messages=[...])\n

        Providers are selected by LLM_PROVIDER and configured by environment.

        "},{"location":"09_infra_adaptors/#key-value-store","title":"Key-value store","text":"

        Tier B \u2014 template. Copy from templates/app.

        For report/snapshot storage, provide a pluggable store port with a Redis adapter and an in-memory fallback.

        "},{"location":"09_infra_adaptors/#rule","title":"Rule","text":"
        • Tier A (libraries: DB, DI, ingestion, mail, auth, docs, pipelines) \u2014 use the library; writing a private copy is a defect. Query find_library first.
        • Tier B (cache, progress bus, LLM pool, KV store, composition shape, worker, tests) \u2014 these are template code, not libraries: copy them from templates/app rather than searching the registry.
        • Tier C (models, business logic, pipeline families, rules) \u2014 author them yourself.
        "},{"location":"12_genericity_matrix/","title":"Genericity Matrix","text":""},{"location":"12_genericity_matrix/#summary","title":"Summary","text":"

        This page states precisely what is library-backed, what is template-provided, and what is app-specific and must be authored. Use it to scope a new build and to know, for any given capability, whether to call a library, copy template code, or write it yourself.

        "},{"location":"12_genericity_matrix/#tier-a-library-backed-use-never-reimplement","title":"Tier A \u2014 Library-backed (use; never reimplement)","text":"

        These capabilities already exist as internal libraries. Reach them through the library registry MCP (find_library(capability)) and read the library's own MCP for its API. Writing a private copy of any of these is a defect.

        Capability Library MCP Persistence: repositories, cache, populate, transactions Mongo Ops (mongo_ops) :8007 Dependency injection / composition Hexa (hexa) :8008 Deterministic multi-step transformation engine DAG Pipe (dagpipe) :8006 Spec-first HTTP edge, client, codegen OpenAPI First (openapi_first) :8004 Format-agnostic fetch + parse (HTML, PDF, CSV, XLSX) Omniread (omniread) :8003 Read-only email ingestion Mail Intake (mail_intake) :8002 JWT auth primitives (register, login, introspect) Py JWT (jwtlib) :8005 Documentation build (MkDocs + MCP) Doc Forge (docforge) :8001"},{"location":"12_genericity_matrix/#tier-b-template-provided-copy-from-templatesapp-then-adapt","title":"Tier B \u2014 Template-provided (copy from templates/app, then adapt)","text":"

        These are reusable internal components shipped inside the template \u2014 they are not independent libraries. Copy them as-is, then rename/populate only what is app-specific. They give every app the same wiring shape as the reference app.

        Component Template location Action Layering & dependency rule the package layout + import rules keep Composition root shape init.py (get_svc/get_db, idempotency) keep Spec-first web edge interfaces/openapi.py, thin handlers bound to operationId keep CRUD slice pattern domain/<resource>/{models.py, ports/db.py, ports/service.py, converters.py} rename + fill Generic CRUD service infra/adapters/service.py (CrudsServiceAdaptor) keep Repository factory infra/db/repos/mongo.py (_repo_factory) keep Worker poll + status loop worker.py keep Progress bus + streaming bus port + in-memory adapter + SSE endpoint keep Human-in-the-loop durable pause + question document + resolve endpoint + resume map keep HTTP response cache cache_response decorator + generation invalidation keep Key-value store KV store port + Redis adapter + in-memory fallback keep LLM client pool provider-select + retry pool scaffold keep Pipeline scaffold pipelines/shared/{ports,loader,registry}.py + pipelines/<family>/ rename + fill Operator scripts scripts/ (init() / get_svc() / shutdown()) rename Testing harness integration fakes + fixture-parity pattern keep Project scaffolding pyproject.toml, Dockerfile, docker-compose.yaml, .env.example, openapi.yaml rename + fill"},{"location":"12_genericity_matrix/#tier-c-app-specific-must-be-authored","title":"Tier C \u2014 App-specific (must be authored)","text":"Artifact Why Resource models the business shape Repository populate/cache rules the relationships Service business logic domain behaviour Pipeline families the transformation graph Pattern/rule resources data-driven policy Fixtures & golden outputs test data"},{"location":"12_genericity_matrix/#the-two-axes","title":"The two axes","text":"

        Everything app-specific reduces to two axes:

        1. Resource model \u2014 the set of resources, their fields, relationships, and the OpenAPI contract.
        2. Pipeline family \u2014 only if inputs require multi-step transformation.

        If you can specify those two axes, the rest of the app follows mechanically: Tier A is imported, Tier B is copied, and Tier C is the only thing you author.

        "},{"location":"13_library_registry/","title":"Library Registry","text":""},{"location":"13_library_registry/#summary","title":"Summary","text":"

        The Aetos ecosystem provides internal libraries for the shared concerns of every application. Use them. Writing a private copy of any capability below is a defect.

        Each library also exposes its own documentation as an MCP server. An AI agent can read a library's full API by connecting to its MCP and reading doc://index, doc://nav, and doc://modules/{module}.

        "},{"location":"13_library_registry/#catalog","title":"Catalog","text":"Library Import Purpose Use instead of MCP DAG Pipe dagpipe Deterministic state-transformation DAG engine hand-rolled orchestration/topological ordering :8006 Hexa hexa Declarative DI / composition-root tooling manual wiring, hand-written factories :8008 Mongo Ops mongo_ops Async MongoDB repositories, cache, populate, transactions raw Motor CRUD and connection lifecycle :8007 Omniread omniread Format-agnostic fetch + parse (HTML, PDF, CSV, XLSX) custom scrapers/parsers :8003 OpenAPI First openapi_first Spec-first FastAPI bootstrap, client, codegen decorator routers and hand-written clients :8004 Mail Intake mail_intake Provider-agnostic read-only email ingestion Gmail OAuth + MIME parsing :8002 Py JWT jwtlib JWT auth primitives (register/login/introspect) password hashing + token code :8005 Doc Forge docforge Docstring compiler \u2192 MkDocs + MCP docs custom doc generators :8001 Auth Server (service) Central identity service (register/login/me/introspect) building auth endpoints yourself (deferred)"},{"location":"13_library_registry/#how-to-choose","title":"How to choose","text":"You need to\u2026 Use persist and query documents Mongo Ops compose dependencies Hexa run a multi-step transformation DAG Pipe read/parse an arbitrary input format Omniread expose a contract-first HTTP API OpenAPI First ingest email Mail Intake authenticate users / verify tokens Py JWT (+ Auth Server) generate documentation Doc Forge"},{"location":"13_library_registry/#discovery","title":"Discovery","text":"

        A single standards MCP exposes find_library(capability), which returns the matching library, its import name, its \"use instead of\" guidance, and its MCP endpoint. Query it before writing any infrastructure.

        "},{"location":"13_library_registry/#notes","title":"Notes","text":"
        • Hexa is a full library with its own MCP server on :8008: read doc://index, doc://nav, and doc://modules/{module} (e.g. doc://modules/hexa.container) to discover the DI surface \u2014 build(cls, *, instances, config) and the PortNode model.
        • Auth Server is deferred: it has no pyproject.toml/importable package and no MCP bundle. Do not reach for it yet; py-jwt covers token primitives.
        • Library MCP servers expose documentation resources (not executable tools): they tell an agent the available API surface so it does not invent code.
        "}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Standards Reference","text":"

        The reference half of the Standards description: the enforceable rules, patterns, and the internal library catalog that agents consult while coding.

        • Build Recipe \u2014 the ordered, agentic checklist for a new app.
        • Layering Rules \u2014 the strict request \u2192 domain \u2192 repository layering and the rules that enforce it.
        • Composition Root \u2014 a single composition point wiring the whole app.
        • Spec-First Routing \u2014 the OpenAPI contract as the routing source of truth.
        • CRUD Template \u2014 the standardized generic resource endpoint scaffold.
        • Pipeline Scaffold \u2014 the deterministic pipeline skeleton for multi-stage jobs.
        • Worker & Progress \u2014 queued work, progress reporting, and visibility.
        • Human-in-the-Loop \u2014 guardrails and approvals inside automations.
        • Infra Adapters \u2014 how infrastructure connects without leaking into domain.
        • Genericity Matrix \u2014 what is library-backed, template-provided, or authored.
        • Library Registry \u2014 the catalog of internal libraries behind all of it.

        Read the Wiki for the philosophy and rationale; these pages are the reference that mirrors the standards MCP bundle.

        "},{"location":"01_build_recipe/","title":"Build Recipe","text":""},{"location":"01_build_recipe/#summary","title":"Summary","text":"

        The ordered, deterministic checklist for generating a new application of the standard shape. Every step is fixed; only the resource model (step 2), the codegen pass (step 3), and the pipeline family (step 8) need app-specific work.

        This page is served by the standards MCP as docs://modules/build_recipe. The start_app prompt on that server collects the two axes and hands off here; pull exact files with get_app_component.

        "},{"location":"01_build_recipe/#bootstrap","title":"\ud83d\ude80 Bootstrap","text":"

        From the standards docs checkout:

        cp -r <docs>/standards/src/templates/app apps/<your-app>\ncd apps/<your-app>\n

        Install the internal libraries \u2014 editable local checkouts (preferred in development) or the private index (CI / fresh machines). Credentials are environment-only; never commit them.

        # local checkouts\npip install -e ../aetos/openapi-first -e ../aetos/mongo-ops -e ../aetos/hexa \\\n            -e ../aetos/dagpipe -e ../aetos/doc-forge\n# private index\npip install --extra-index-url \\\n  \"https://$PIP_REGISTRY_USERNAME:$PIP_REGISTRY_PASSWORD@pip.aetoskia.com/simple\" .\n
        "},{"location":"01_build_recipe/#agent-prompt-copyable","title":"\ud83d\udccb Agent prompt (copyable)","text":"
        # Build a new application from the Aetos Standards blueprint\n\nYou are coding a complete new backend application conforming to the Aetos\nstandard app blueprint. Execute every step in order. Only steps 2\u20134 use the\napp-specific details provided by the user; everything else is deterministic.\n\n## Before writing any code\n\nConnect to the `standards` MCP server and read:\n- `docs://nav` (the module list)\n- `docs://modules/genericity_matrix` (Tier A library / Tier B template / Tier C authored)\n- `docs://modules/layering_rules` (the dependency rules)\n\nThen:\n- call `find_library` with `db`, `di`, `routing`, `ingestion`, `pipeline`, `docs`\n  and note the Tier A libraries that already exist \u2014 never reimplement one;\n- call `list_app_components()` and pull each file you need with\n  `get_app_component(kind, resource=..., family=...)` instead of writing it from\n  scratch.\n\n## Steps\n\n1. **Scaffold** \u2014 copy `standards/src/templates/app/` into `apps/<your-app>/`\n   (or pull the same tree via `get_app_template`). Keep the tree: `domain/`,\n   `infra/`, `interfaces/`, `pipelines/`, `tests/`, `scripts/`, plus\n   `pyproject.toml`, `openapi.yaml`, `config.py`, `init.py`, `main.py`,\n   `worker.py`, `Dockerfile`, `docker-compose.yaml`, `.env.example`.\n\n2. **Author the spec (app-specific)** \u2014 write `openapi.yaml`: resources,\n   schemas, `operationId`s, security, and extension metadata. This is the\n   contract and drives all routing. Every operation MUST have an `operationId`.\n\n3. **Generate the scaffold (codegen)** \u2014 from the spec, generate the wire\n   models and the exact handler signatures:\n   ```bash\n   openapi-first models openapi.yaml -o _gen/models.py\n   openapi-first routes openapi.yaml -o _gen/routes \\\n     --use-models --models-module _gen.models --inject-request --package\n   ```\n   `_gen/models.py` is the **wire contract** (Pydantic, one class per\n   `components.schemas` entry). `_gen/routes/<resource>/routes.py` lists every\n   `operationId` with its exact parameter signature. Treat `_gen/` as generated\n   (never hand-edit it) \u2014 the canonical handlers live in `interfaces/`.\n\n4. **Write each CRUD slice (app-specific)** \u2014 per resource create the four-file\n   slice: `models.py` (domain model, **no `id`**), `ports/db.py`\n   (`class XDocument(BaseDocument, X)` \u2014 id comes from `BaseDocument`),\n   `ports/service.py`, and optional `converters.py`. Follow the CRUD template.\n\n5. **Wire the composition root** \u2014 in `init.py`, build leaf repositories, then\n   dependent repositories, then services; add infra adapters (db, cache, stores,\n   progress bus, fetchers). Expose `get_svc` overloads.\n\n6. **Add routes** \u2014 create `interfaces/<resource>/routes.py` thin handlers whose\n   names match the `operationId`s (use the signatures from step 3 as your\n   checklist) and re-export them in `interfaces/openapi.py`. Add the cache\n   decorator to read endpoints.\n\n7. **Bootstrap app and worker** \u2014 confirm `main.py` runs the OpenAPI-first app\n   with its lifespan. If the app has long-running jobs, adapt `worker.py` to poll\n   the job collection, run the pipeline, and publish progress.\n\n8. **Add pipeline families (only if data-driven)** \u2014 copy `pipelines/example/`\n   to `pipelines/<family>/` with `abc.py`, `impl.py`, `pipeline.yaml`,\n   `resources/`, and reuse `pipelines/shared/` (loader + registry). Register the\n   root. Add fixtures and a parity test.\n\n9. **Add infrastructure only where needed** \u2014 Tier A capability: use the\n   library. Tier B capability: copy the template. Never hand-write a Tier A\n   library.\n\n10. **Tests** \u2014 integration tests with fakes at the HTTP edge; unit tests for\n    domain and pipeline logic; a config-consistency test for rule-driven\n    pipelines. Include `tests/integration/test_openapi_app_builds.py` (imports\n    the app, asserts `import main` binds every operation).\n\n11. **Scripts and seeds** \u2014 operator scripts under `scripts/` using\n    `init()` / `get_svc()` / `shutdown()`; run as `python -m scripts.<name>`.\n\n12. **Docs** \u2014 author GSDFC docstrings, generate `.pyi` stubs, mark the package\n    `py.typed`, and build the docs.\n\n**Definition of done:**\n- `python -c \"import main\"` succeeds.\n- `pytest` passes.\n- The app serves every operation in the spec; the worker drains its queue.\n- No domain module imports a web framework.\n- No Tier A library was reimplemented.\n
        "},{"location":"01_build_recipe/#where-the-steps-land","title":"\ud83e\udded Where the steps land","text":"

        The steps map onto the layers as follows:

        Step Package / file Depends on 1 \u00b7 Scaffold whole templates/app/ \u2014 2 \u00b7 Spec openapi.yaml \u2014 3 \u00b7 Codegen _gen/ (generated) spec, openapi-first 4 \u00b7 CRUD slices domain/<resource>/ spec, CRUD template 5 \u00b7 Wiring init.py every slice 6 \u00b7 Routes interfaces/<resource>/ services, openapi route registry 7 \u00b7 Bootstrap main.py, worker.py composition root 8 \u00b7 Pipelines pipelines/<family>/, shared/ library pipeline engine 9 \u00b7 Infra infra/ domain ports 10 \u00b7 Tests tests/ app + fixtures 11 \u00b7 Scripts scripts/ composition root 12 \u00b7 Docs docstrings + docs build code, Doc Forge"},{"location":"01_build_recipe/#the-codegen-fast-path-in-detail","title":"\ud83d\udd27 The codegen fast path, in detail","text":"

        openapi-first is spec \u2192 code only (it never generates a spec). Use it to erase the two mechanical parts of the build:

        Generated artifact Replaces Notes _gen/models.py hand-writing Pydantic wire models one class per components.schemas entry _gen/routes/<resource>/routes.py hand-typing handler signatures names == operationIds; request: Request injected

        Rules of the road:

        • Wire models are not domain models. _gen/models.py is the HTTP contract; domain/<resource>/models.py is your business shape. If the spec's schema has a required id: str, keep it off the domain model and let the document get its id from BaseDocument.
        • Resource names are sanitized. /fetch-requests \u2192 fetch_requests.py.
        • Multipart bodies expand to UploadFile = File(...) / Form(...) params.
        • _gen/ is disposable. Regenerate on every spec change; git diff shows exactly what moved.
        "},{"location":"01_build_recipe/#definition-of-done","title":"\u26a0\ufe0f Definition of done","text":"
        • python -c \"import main\" succeeds.
        • pytest passes.
        • The app serves the spec's operations; the worker drains its queue.
        • No domain module imports a web framework.
        • No Tier A library was reimplemented.
        "},{"location":"01_build_recipe/#related","title":"Related","text":"
        • 02 \u2013 Layering Rules \u00b7 05 \u2013 CRUD Template \u00b7 06 \u2013 Pipeline Scaffold \u00b7 12 \u2013 Genericity Matrix
        "},{"location":"02_layering_rules/","title":"Layering Rules","text":""},{"location":"02_layering_rules/#summary","title":"Summary","text":"

        The dependency direction is fixed. Violating it is a design defect.

        "},{"location":"02_layering_rules/#allowed-imports","title":"Allowed imports","text":"
        interfaces/*      ->  domain/*            (services, ports, models)\ninterfaces/*      ->  infra/adapters/*    (only read-only value objects)\ndomain/*          ->  domain/*            (same layer)\ndomain/*          ->  pipelines/*         (call a pipeline via a port)\ndomain/*/ports/*  ->  infra/*             (bind adapters + libraries at the seam)\ninfra/adapters/*  ->  domain/ports/*      (implement the interface)\nmain.py/init.py   ->  everything          (composition root only)\n
        "},{"location":"02_layering_rules/#forbidden-imports","title":"Forbidden imports","text":"
        domain/*          ->  interfaces/*        (never)\ndomain/*          ->  fastapi/starlette   (never)\ninfra/adapters/*  ->  interfaces/*        (never)\ninterfaces/*      ->  infra/db/*          (state lives in resources, not imports)\n
        "},{"location":"02_layering_rules/#why","title":"Why","text":"
        • The business logic is testable without a server or an event loop.
        • Adapters can be swapped (in-memory fake, real database) without touching business logic.
        • The HTTP edge stays thin and replaceable.
        "},{"location":"02_layering_rules/#the-portadapter-contract","title":"The port/adapter contract","text":"

        The ports/ package is the seam where the domain meets infrastructure. Each domain/<resource>/ports/ module declares the resource's models/document shape and exposes factory functions that bind a concrete adapter \u2014 so a port module is allowed to import from infra/ and from the persistence libraries. Everything outside ports/ in domain/ stays framework- and driver-free.

        domain/<resource>/ports/db.py       -> document model + create_<resource>_repo()\ndomain/<resource>/ports/service.py  -> service + get_<resource>_service()\ninfra/db/repos/<vendor>.py          -> repository adapter factory\ninfra/adapters/service.py           -> generic CRUD service base\n

        The composition root calls the port factories; nothing else constructs an adapter.

        "},{"location":"02_layering_rules/#enforcement","title":"Enforcement","text":"
        • domain/ must never import the web framework (fastapi / starlette) or interfaces/. Only the ports/ seam, infra/, and the composition root touch drivers.
        • Reviewers (and CI) reject import fastapi inside domain/ or infra/.
        "},{"location":"03_composition_root/","title":"Composition Root","text":""},{"location":"03_composition_root/#summary","title":"Summary","text":"

        All wiring happens in exactly one module. It builds a flat resource registry and exposes it to the HTTP layer and to scripts.

        "},{"location":"03_composition_root/#the-resource-registry","title":"The resource registry","text":"

        init.py returns a plain dict[str, Any]. Keys follow two conventions:

        • <resource>_repo \u2014 a repository adapter for a resource.
        • <resource>_svc \u2014 a service for a resource.

        Plus named infrastructure adaptors:

        • http_cache_store \u2014 response cache backend.
        • progress_bus \u2014 progress/event bus.
        • <vendor>_<kind> \u2014 fetchers, clients, stores.

        The API stores the dict on app.state.db_resources during lifespan startup. Handlers read from it; nothing imports concrete adapters.

        "},{"location":"03_composition_root/#shape","title":"Shape","text":"
        _resources: dict = {}\n_initialized = False\n\nasync def init(settings=None) -> dict:\n    global _initialized\n    if _initialized:\n        return _resources\n\n    await setup_database(uri=settings.mongo_uri, db_name=settings.mongo_db)\n\n    # 1. leaf repositories\n    user_repo = create_user_repo()\n\n    # 2. dependent repositories (dependencies passed by keyword)\n    wallet_repo = create_wallet_repo(user_repo=user_repo)\n\n    # 3. services (build via the DI library, injecting the repo)\n    _resources[\"user_repo\"]   = user_repo\n    _resources[\"wallet_repo\"] = wallet_repo\n    _resources[\"user_svc\"]    = get_user_service(user_repo)\n    _resources[\"wallet_svc\"]  = get_wallet_service(wallet_repo)\n\n    # 4. infrastructure adaptors (optional)\n    _resources[\"http_cache_store\"] = HttpCacheStore(...)\n    _resources[\"progress_bus\"]     = ProgressEventBus()\n\n    _initialized = True\n    return _resources\n\nasync def shutdown() -> None:\n    global _initialized\n    await close_stores(_resources)\n    await shutdown_database()\n    _resources.clear()\n    _initialized = False\n
        "},{"location":"03_composition_root/#typed-accessors","title":"Typed accessors","text":"

        Expose a get_svc(name) helper with one @overload per key so scripts get typing without importing concrete classes:

        @overload\ndef get_svc(name: Literal[\"user_svc\"]) -> UserService: ...\n@overload\ndef get_svc(name: Literal[\"wallet_svc\"]) -> WalletService: ...\n\ndef get_svc(name: str):\n    return _resources[name]\n

        Also expose get_db() (raw database handle) and pure helpers such as compute_fingerprint(payload) (canonical hash used for deduplication).

        "},{"location":"03_composition_root/#rules","title":"Rules","text":"
        • init() is idempotent.
        • The composition root is the only place that imports concrete adapters.
        • Handlers never call init(); they read request.app.state.db_resources.
        • Scripts call init() / shutdown() around their work.
        "},{"location":"04_spec_first_routing/","title":"Spec-First Routing","text":""},{"location":"04_spec_first_routing/#summary","title":"Summary","text":"

        The OpenAPI document is the single source of truth for the HTTP contract. Handlers are bound to it by name; there is no decorator router.

        "},{"location":"04_spec_first_routing/#the-contract","title":"The contract","text":"
        • openapi.yaml (or .json) declares paths, schemas, parameters, responses, and security.
        • A custom operationId names the handler function.
        • The framework (openapi_first) validates requests/responses against the spec and dispatches to the handler whose name equals operationId.
        "},{"location":"04_spec_first_routing/#app-bootstrap","title":"App bootstrap","text":"
        # main.py\nfrom openapi_first.app import OpenAPIFirstApp\n\napp = OpenAPIFirstApp(\n    openapi_path=\"openapi.yaml\",\n    routes_module=interfaces.openapi,   # module whose namespace holds handlers\n    title=\"<App> API\",\n    version=\"0.1.0\",\n    lifespan=lifespan,\n)\n

        lifespan calls the composition root:

        @asynccontextmanager\nasync def lifespan(app):\n    app.state.db_resources = await init()\n    yield\n    await shutdown()\n
        "},{"location":"04_spec_first_routing/#handler-registry","title":"Handler registry","text":"

        interfaces/openapi.py is a flat re-export of every handler, so the framework can resolve operationId across the whole app:

        from interfaces.item.routes import *\nfrom interfaces.wallet.routes import *\nfrom interfaces.progress.sse import *\n
        "},{"location":"04_spec_first_routing/#handler-shape","title":"Handler shape","text":"

        Handlers are thin and synchronous with respect to business logic:

        async def create_item(request: Request, body: dict):\n    svc = request.app.state.db_resources[\"item_svc\"]\n    return await svc.create(Item.model_validate(body))\n
        "},{"location":"04_spec_first_routing/#conventions","title":"Conventions","text":"
        • operationId naming: list_<resource>, create_<resource>, get_<resource>, update_<resource>, delete_<resource>.
        • Read-only endpoints may use a cache decorator keyed on path + query + user.
        • Validation errors -> 400; not found -> 404; delete -> 204.
        • Security is declared once in the spec and enforced by the framework; handlers contain no auth code.
        "},{"location":"04_spec_first_routing/#authless-apps","title":"Authless apps","text":"

        Auth is deferred in the standard: the same spec-first app runs with or without it, because security lives entirely in the spec, never in handlers. For an app that needs no auth yet, do not invent per-handler auth checks \u2014 just omit the security keys:

        • Remove the top-level security: block.
        • Remove components.securitySchemes.
        • Do not add security: [] to operations. An explicit empty array is a per-operation override that means \"public\", and it only matters when a global security block exists. Leaving every operation without a security key is the cleanest authless spec.

        The handlers, services, and composition root are unchanged. When auth is needed later, add the global security block and an introspecting dependency; only handlers that must opt out get security: [].

        "},{"location":"04_spec_first_routing/#codegen","title":"Codegen","text":"

        openapi_first scaffolds models and route stubs from the spec (see the Build Recipe, step 3):

        openapi-first models openapi.yaml -o _gen/models.py\nopenapi-first routes openapi.yaml -o _gen/routes \\\n  --use-models --models-module _gen.models --inject-request --package\n

        Prefer codegen over hand-writing boilerplate. The result is the wire contract and a set of operationId-named signatures \u2014 not the canonical handlers.

        "},{"location":"05_crud_template/","title":"CRUD Template","text":""},{"location":"05_crud_template/#summary","title":"Summary","text":"

        Every resource follows the same four-file slice. Repetition is intentional: it makes the codebase predictable and generatable.

        "},{"location":"05_crud_template/#the-slice","title":"The slice","text":"
        domain/<resource>/\n    models.py               # pure Pydantic model (no DB fields)\n    ports/\n        db.py               # document model + repository factory\n        service.py          # service class + factory\n        converters.py       # (optional) transforms\n
        "},{"location":"05_crud_template/#the-id-rule-model-vs-document","title":"The id rule (model vs. document)","text":"

        Two different objects, two different jobs:

        Object Lives in Has id? Purpose Domain model domain/<resource>/models.py No business shape, framework-free Document domain/<resource>/ports/db.py Yes (from BaseDocument) Mongo persistence shape

        BaseDocument (mongo-ops) supplies id: Optional[PyObjectId] (aliased to _id) plus created_at / updated_at. The domain model must not declare id; Mongo owns it.

        Trap \u2014 generated wire models. openapi-first models produces the HTTP contract (see the Build Recipe, step 3). Response schemas frequently declare a required id: str, so a generated class is not a domain model. If you write class ItemDocument(Item, BaseDocument) and Item re-exports a required id: str, that field shadows BaseDocument.id and no Mongo _id can ever be set. Fix it one of three ways:

        1. Preferred: keep the domain model id-free (hand-author it from the CRUD template), and keep the required id only on the wire model.
        2. Order the bases so the document wins: class ItemDocument(BaseDocument, Item).
        3. Keep a separate document type and translate with ports/converters.py.
        "},{"location":"05_crud_template/#modelspy","title":"models.py","text":"

        A framework-free Pydantic model describing the business shape (no id).

        class Item(BaseModel):\n    name: str\n    quantity: int = 0\n
        "},{"location":"05_crud_template/#portsdbpy","title":"ports/db.py","text":"

        The persistence model extends the business model and adds database concerns. The repository factory selects the right repository flavour.

        # Item is id-free; BaseDocument adds id/created_at/updated_at.\nclass ItemDocument(Item, BaseDocument):\n    pass\n\ndef create_item_repo(*, owner_repo=None) -> ItemRepository:\n    return repo_factory(\n        collection=\"items\",\n        document=ItemDocument,\n        populate_rules=[PopulateRule(field=\"owner\", collection=\"users\")],\n        cache=None,               # or a CachedBaseRepository config\n    )\n
        "},{"location":"05_crud_template/#portsservicepy","title":"ports/service.py","text":"

        The service is built through the DI library, injecting the repository. The generic CRUD base provides create/read/update/patch/delete/search/....

        class ItemService(CrudsServiceAdaptor[Item, ItemDocument]):\n    model = Item\n    schema = ItemDocument\n\ndef get_item_service(repo) -> ItemService:\n    return build(ItemService, instances={\"repo\": repo})\n
        "},{"location":"05_crud_template/#naming-conventions","title":"Naming conventions","text":"Concept Convention collection plural snake case (items) document <Resource>Document repository factory create_<resource>_repo service <Resource>Service service factory get_<resource>_service resource key <resource>_svc operationId list_items, create_item, ..."},{"location":"05_crud_template/#optional-files","title":"Optional files","text":"
        • converters.py \u2014 pure functions mapping between domain types.
        • ports/enrichment.py \u2014 adapts a pipeline into an async callable the service can invoke (used when a resource is populated by a pipeline).
        "},{"location":"06_pipeline_scaffold/","title":"Pipeline Scaffold","text":""},{"location":"06_pipeline_scaffold/#summary","title":"Summary","text":"

        Data-driven processing is expressed as a composition graph. The graph is built by a DI library; execution is driven by a DAG engine. Stages are pure and individually testable.

        "},{"location":"06_pipeline_scaffold/#when-to-use-a-pipeline","title":"When to use a pipeline","text":"

        Use a pipeline when a resource is produced by a multi-step transformation of an input (a document, a file, a message) rather than by a simple CRUD write.

        "},{"location":"06_pipeline_scaffold/#structure-of-a-family","title":"Structure of a family","text":"

        A family is one input shape (e.g. a file format or a message kind). Each family has the same skeleton:

        pipelines/<family>/\n    abc.py            # port contracts (stage interfaces + root interface)\n    impl.py           # concrete stages + root implementation\n    pipeline.yaml     # graph spec: nodes, edges, roots, schema\n    resources/\n        pipeline.yaml # runtime spec consumed by the loader\n        patterns/*.yaml  # rule fragments (pattern registry)\n    banks/<variant>.py   # optional variants that re-pin a few slots\n
        "},{"location":"06_pipeline_scaffold/#shared-scaffolding","title":"Shared scaffolding","text":"
        pipelines/shared/\n    ports.py     # RegistryPort, FinalizePort, etc.\n    loader.py    # build(root_cls) -> wires the graph + engine\n    registry.py  # resolve_root(...) -> root class for a (kind, variant)\n
        "},{"location":"06_pipeline_scaffold/#the-loader","title":"The loader","text":"
        def build(root_cls, *, instances=None, config=None):\n    # merge resources/patterns/*.yaml into a pattern registry\n    # bind registry/config into each graph node by id\n    # build the schema (state) class and the engine\n    root = root_cls()\n    root._engine = Engine(graph, state_cls, payload_cls)\n    return root\n
        "},{"location":"06_pipeline_scaffold/#the-registry","title":"The registry","text":"

        A single lookup replaces hand-rolled if/else selection:

        _FAMILIES = {\n    \"csv\":  {\"acme\": {\"heuristic\": AcmeCsvPipeline}},\n    \"json\": {\"globex\": {\"heuristic\": GlobexJsonPipeline}},\n}\n\ndef resolve_root(kind, variant, mode=\"heuristic\"):\n    return _FAMILIES[kind][variant][mode]\n

        Adding a new variant means adding one class and one registry entry.

        "},{"location":"06_pipeline_scaffold/#driving-a-pipeline","title":"Driving a pipeline","text":"

        Stages are driven explicitly so intermediate state can be persisted:

        pipeline = build(resolve_root(kind, variant))\ncontent = pipeline.ingest.materialize(raw_bytes)\nrecords = pipeline.reader.read(content)\nitems   = pipeline.normalize.build(records)\n
        "},{"location":"06_pipeline_scaffold/#testing","title":"Testing","text":"

        Because stages are pure, test each stage in isolation and test the assembled root against fixture inputs whose expected outputs are stored as golden files. A parity test replays fixtures headlessly.

        "},{"location":"07_worker_and_progress/","title":"Worker & Progress","text":""},{"location":"07_worker_and_progress/#summary","title":"Summary","text":"

        Long-running work runs in a background worker. Jobs are durable documents that move through an explicit status machine. Progress is streamed to clients.

        "},{"location":"07_worker_and_progress/#the-status-machine","title":"The status machine","text":"
        pending -> processing -> completed\n                     \\-> failed    (retried up to N times)\n                     \\-> paused    (waiting for human input)\n
        • pending \u2014 queued, eligible for pickup.
        • processing \u2014 claimed by a worker.
        • paused \u2014 blocked on a human-in-the-loop question.
        • failed \u2014 error; retry_count increments until a max, then skipped.
        • completed \u2014 terminal success.
        "},{"location":"07_worker_and_progress/#the-poll-loop","title":"The poll loop","text":"
        async def poll(resources):\n    backoff = MIN_POLL\n    while True:\n        jobs = await job_svc.search(filter={\"status\": \"pending\"}, limit=100)\n        backoff = MIN_POLL if jobs else min(backoff * 2, MAX_POLL)\n        for job in jobs:\n            if (job.retry_count or 0) >= MAX_RETRIES:\n                continue\n            await job_svc.update_status(job.id, \"processing\")\n            try:\n                await process(job, resources)\n            except PauseForResolution:\n                await job_svc.update_status(job.id, \"paused\")\n            except Exception as e:\n                await job_svc.update_status(\n                    job.id, \"failed\", error=str(e),\n                    retry_count=(job.retry_count or 0) + 1,\n                )\n        await asyncio.sleep(backoff)\n

        Tunables live in environment variables: WORKER_POLL_MIN, WORKER_POLL_MAX, WORKER_MAX_RETRIES.

        "},{"location":"07_worker_and_progress/#progress-events","title":"Progress events","text":"

        A worker publishes structured progress events to a bus (a port). The HTTP edge exposes a streaming endpoint that replays events for a given job id.

        class ProgressEvent(BaseModel):\n    job_id: str\n    step: str\n    status: str            # started | progress | completed | skipped | paused\n    message: dict | None = None\n

        The bus is an in-memory adapter by default; because it is a port, it can be swapped for a durable broker. Emit from the orchestrator at each stage:

        await self.bus.publish(ProgressEvent(job_id=jid, step=\"normalize\",\n                                     status=\"completed\", message={\"count\": n}))\n
        "},{"location":"07_worker_and_progress/#streaming-endpoint","title":"Streaming endpoint","text":"
        async def stream_job_events(request, id):\n    bus = request.app.state.db_resources.get(\"progress_bus\")\n    if bus is None:\n        return {\"events\": []}\n\n    async def gen():\n        sent = 0\n        while True:\n            events = bus.get_events(id)\n            for e in events[sent:]:\n                yield f\"data: {e.model_dump_json(exclude_none=True)}\\n\\n\"\n                sent += 1\n            await asyncio.sleep(0.5)\n\n    return StreamingResponse(gen(), media_type=\"text/event-stream\")\n

        Known limitation. The default in-memory bus is per-process. If the worker and the API run as separate processes, events published by the worker are not visible to the API. For production, back the bus with a shared broker. The port boundary makes this a one-adapter change.

        "},{"location":"08_human_in_loop/","title":"Human-in-the-Loop","text":""},{"location":"08_human_in_loop/#summary","title":"Summary","text":"

        Some inputs are ambiguous and must be resolved by a human before processing can continue. The blueprint models this as a durable pause: an exception is raised, a question is recorded, the job pauses, and later resumes.

        "},{"location":"08_human_in_loop/#the-mechanism","title":"The mechanism","text":"
        1. During processing, a stage raises a domain exception carrying the context needed to ask the question:
        raise PauseForResolution(\n    job_id=jid,\n    reference=\"<stable key for this decision>\",\n    options=[...],\n    context={...},\n)\n
        1. The orchestrator catches it, records a question document, and re-raises so the worker marks the job paused.
        2. A client lists pending questions and answers one:
        GET  /jobs/{id}/questions            -> list pending questions\nPOST /questions/{qid}/resolve  {choice}  -> record the choice\n
        1. When the last pending question is resolved, the resolve endpoint flips the parent job back to pending.
        2. The worker picks it up again, rebuilds a resolution map from previously answered questions, and seeds it into the pipeline so the same decision is not asked twice.
        "},{"location":"08_human_in_loop/#the-question-document","title":"The question document","text":"
        class Question(BaseDocument):\n    job_id: str\n    reference: str            # stable key: stage|field|value\n    status: str               # pending | resolved\n    options: list[dict]\n    context: dict\n    chosen: dict | None = None\n    resolved_at: str | None = None\n
        "},{"location":"08_human_in_loop/#resume-map","title":"Resume map","text":"

        The worker turns answered questions into a lookup keyed by reference, then injects it into the pipeline so that stage can skip re-raising:

        resolved = {q.reference: q.chosen for q in await svc.search(\n    filter={\"job_id\": jid, \"status\": \"resolved\"})}\n\npipeline = build(root, instances={\"pause\": handler}, config={\"resolved\": resolved})\n
        "},{"location":"08_human_in_loop/#resolve-endpoint","title":"Resolve endpoint","text":"
        async def resolve_question(request, id, body):\n    svc = request.app.state.db_resources[\"question_svc\"]\n    q = await svc.read(id)\n    await svc.update(id, {\"chosen\": body[\"chosen\"], \"status\": \"resolved\",\n                          \"resolved_at\": now()})\n\n    pending = await svc.search(filter={\"job_id\": q.job_id, \"status\": \"pending\"},\n                               limit=1)\n    if not pending:\n        jobs = request.app.state.db_resources[\"job_svc\"]\n        job = await jobs.read(q.job_id)\n        if job and job.status == \"paused\":\n            await jobs.update_status(q.job_id, \"pending\")\n    return q\n
        "},{"location":"08_human_in_loop/#cli-parity","title":"CLI parity","text":"

        The same flow is available through scripts/CLI: list, get, create, questions, resolve, retry.

        "},{"location":"09_infra_adaptors/","title":"Infra Adapters","text":""},{"location":"09_infra_adaptors/#summary","title":"Summary","text":"

        All external systems are reached through adapters in infra/. Prefer an existing internal library over writing a new adapter; where no library exists, copy the Tier B scaffold from templates/app. See the Genericity Matrix for the Tier A / B / C split.

        "},{"location":"09_infra_adaptors/#database-mongodb","title":"Database (MongoDB)","text":"
        • A single connection manager owns the client lifecycle. setup_database(uri, db_name) / shutdown_database().
        • A repository factory selects the repository flavour:
        repo_factory(collection, document, engine=None,\n             populate_rules=None, cache=None)\n
        • base repository \u2014 plain CRUD;
        • populating repository \u2014 resolves referenced documents (foreign keys);
        • cached repository \u2014 an LRU/TTL cache in front of reads.

        • Referenced documents are described declaratively:

        PopulateRule(field=\"owner\", collection=\"users\",\n             nested_rules=[], max_depth=2, projection=None)\n
        • Transactions use a session context manager provided by the library.

        Tier A \u2014 Library (Mongo Ops). Do not write raw Motor calls in the domain; consult the registry and the library's MCP before touching the driver.

        "},{"location":"09_infra_adaptors/#http-response-cache","title":"HTTP response cache","text":"

        Tier B \u2014 template. Copy from templates/app; not a standalone library.

        A generic GET cache sits behind a decorator.

        @cache_response(ttl=60)\nasync def list_items(request): ...\n
        • Keyed on path + query + user.
        • No-op when no store is wired.
        • invalidate(*resources) bumps a generation counter for write-triggered invalidation.
        "},{"location":"09_infra_adaptors/#event-progress-bus","title":"Event / progress bus","text":"

        Tier B \u2014 template. Copy from templates/app.

        A port with an in-memory default adapter (publish, get_events). Swap for a durable broker in production (see Worker & Progress).

        "},{"location":"09_infra_adaptors/#external-clients-ingestion","title":"External clients (ingestion)","text":"

        External fetchers (files, mail, HTTP) are adapters that wrap ingestion libraries. Construct them in the composition root and inject into orchestrators.

        Tier A \u2014 Libraries: Omniread (formats) and Mail Intake (email).

        "},{"location":"09_infra_adaptors/#llm-clients","title":"LLM clients","text":"

        Tier B \u2014 template. Copy the pool scaffold from templates/app.

        When a pipeline calls a model provider, route through a pool adaptor that handles provider selection, retries, and client lifecycle:

        pool = setup_async_pool(provider=settings.llm_provider)\nresult = await pool.complete(model=\"...\", messages=[...])\n

        Providers are selected by LLM_PROVIDER and configured by environment.

        "},{"location":"09_infra_adaptors/#key-value-store","title":"Key-value store","text":"

        Tier B \u2014 template. Copy from templates/app.

        For report/snapshot storage, provide a pluggable store port with a Redis adapter and an in-memory fallback.

        "},{"location":"09_infra_adaptors/#rule","title":"Rule","text":"
        • Tier A (libraries: DB, DI, ingestion, mail, auth, docs, pipelines) \u2014 use the library; writing a private copy is a defect. Query find_library first.
        • Tier B (cache, progress bus, LLM pool, KV store, composition shape, worker, tests) \u2014 these are template code, not libraries: copy them from templates/app rather than searching the registry.
        • Tier C (models, business logic, pipeline families, rules) \u2014 author them yourself.
        "},{"location":"12_genericity_matrix/","title":"Genericity Matrix","text":""},{"location":"12_genericity_matrix/#summary","title":"Summary","text":"

        This page states precisely what is library-backed, what is template-provided, and what is app-specific and must be authored. Use it to scope a new build and to know, for any given capability, whether to call a library, copy template code, or write it yourself.

        "},{"location":"12_genericity_matrix/#tier-a-library-backed-use-never-reimplement","title":"Tier A \u2014 Library-backed (use; never reimplement)","text":"

        These capabilities already exist as internal libraries. Reach them through the library registry MCP (find_library(capability)) and read the library's own MCP for its API. Writing a private copy of any of these is a defect.

        Capability Library MCP Persistence: repositories, cache, populate, transactions Mongo Ops (mongo_ops) :8007 Dependency injection / composition Hexa (hexa) :8008 Deterministic multi-step transformation engine DAG Pipe (dagpipe) :8006 Spec-first HTTP edge, client, codegen OpenAPI First (openapi_first) :8004 Format-agnostic fetch + parse (HTML, PDF, CSV, XLSX) Omniread (omniread) :8003 Read-only email ingestion Mail Intake (mail_intake) :8002 JWT auth primitives (register, login, introspect) Py JWT (jwtlib) :8005 Documentation build (MkDocs + MCP) Doc Forge (docforge) :8001"},{"location":"12_genericity_matrix/#tier-b-template-provided-copy-from-templatesapp-then-adapt","title":"Tier B \u2014 Template-provided (copy from templates/app, then adapt)","text":"

        These are reusable internal components shipped inside the template \u2014 they are not independent libraries. Copy them as-is, then rename/populate only what is app-specific. They give every app the same wiring shape as the reference app. Fetch them by kind with get_app_component, or the whole tree with get_app_template.

        Component Template location Action Shipped Layering & dependency rule the package layout + import rules keep \u2705 Composition root shape init.py (get_svc/get_db, idempotency) keep \u2705 Spec-first web edge interfaces/openapi.py, thin handlers bound to operationId keep \u2705 CRUD slice pattern domain/<resource>/{models.py, ports/db.py, ports/service.py, converters.py} rename + fill \u2705 Generic CRUD service infra/adapters/service.py (CrudsServiceAdaptor) keep \u2705 Repository factory infra/db/repos/mongo.py (_repo_factory) keep \u2705 Worker poll + status loop worker.py keep \u2705 Progress bus infra/adapters/sse.py (keyed in-memory bus) + an app SSE endpoint keep \u2705 HTTP response cache infra/http_cache.py (cache_response + generation invalidation) keep \u2705 Key-value store infra/adapters/kv.py (port + in-memory + Redis adaptor) keep \u2705 Pipeline scaffold pipelines/shared/{ports,loader,registry}.py + pipelines/<family>/ rename + fill \u2705 Operator scripts scripts/ (init() / get_svc() / shutdown()) rename \u2705 Testing harness integration fakes + fixture-parity pattern keep \u2705 Project scaffolding pyproject.toml, Dockerfile, docker-compose.yaml, .env.example, openapi.yaml rename + fill \u2705"},{"location":"12_genericity_matrix/#tier-b-patterns-port-from-the-reference-app","title":"Tier B (patterns) \u2014 port from the reference app","text":"

        A few Tier B capabilities are real and reusable, but are shaped by the app or the provider, so the template does not ship them verbatim \u2014 copying them blindly would carry app-specific names. Treat them as patterns: lift the shape from the reference app and rename per app.

        Component Reference location Action Shipped Human-in-the-loop durable pause + question document + resolve endpoint + resume map port + reshape \u2717 LLM client pool provider-select + retry/rotation pool scaffold port + reshape \u2717"},{"location":"12_genericity_matrix/#tier-c-app-specific-must-be-authored","title":"Tier C \u2014 App-specific (must be authored)","text":"Artifact Why Resource models the business shape Repository populate/cache rules the relationships Service business logic domain behaviour Pipeline families the transformation graph Pattern/rule resources data-driven policy Fixtures & golden outputs test data"},{"location":"12_genericity_matrix/#the-two-axes","title":"The two axes","text":"

        Everything app-specific reduces to two axes:

        1. Resource model \u2014 the set of resources, their fields, relationships, and the OpenAPI contract.
        2. Pipeline family \u2014 only if inputs require multi-step transformation.

        If you can specify those two axes, the rest of the app follows mechanically: Tier A is imported, Tier B is copied, and Tier C is the only thing you author.

        "},{"location":"13_library_registry/","title":"Library Registry","text":""},{"location":"13_library_registry/#summary","title":"Summary","text":"

        The Aetos ecosystem provides internal libraries for the shared concerns of every application. Use them. Writing a private copy of any capability below is a defect.

        Each library also exposes its own documentation as an MCP server. An AI agent can read a library's full API by connecting to its MCP and reading docs://index, docs://nav, and docs://modules/{module}.

        "},{"location":"13_library_registry/#catalog","title":"Catalog","text":"Library Import Purpose Use instead of MCP DAG Pipe dagpipe Deterministic state-transformation DAG engine hand-rolled orchestration/topological ordering :8006 Hexa hexa Declarative DI / composition-root tooling manual wiring, hand-written factories :8008 Mongo Ops mongo_ops Async MongoDB repositories, cache, populate, transactions raw Motor CRUD and connection lifecycle :8007 Omniread omniread Format-agnostic fetch + parse (HTML, PDF, CSV, XLSX) custom scrapers/parsers :8003 OpenAPI First openapi_first Spec-first FastAPI bootstrap, client, codegen decorator routers and hand-written clients :8004 Mail Intake mail_intake Provider-agnostic read-only email ingestion Gmail OAuth + MIME parsing :8002 Py JWT jwtlib JWT auth primitives (register/login/introspect) password hashing + token code :8005 Doc Forge docforge Docstring compiler \u2192 MkDocs + MCP docs custom doc generators :8001 Auth Server (service) Central identity service (register/login/me/introspect) building auth endpoints yourself (deferred)"},{"location":"13_library_registry/#how-to-choose","title":"How to choose","text":"You need to\u2026 Use persist and query documents Mongo Ops compose dependencies Hexa run a multi-step transformation DAG Pipe read/parse an arbitrary input format Omniread expose a contract-first HTTP API OpenAPI First ingest email Mail Intake authenticate users / verify tokens Py JWT (+ Auth Server) generate documentation Doc Forge"},{"location":"13_library_registry/#discovery","title":"Discovery","text":"

        A single standards MCP exposes find_library(capability), which returns the matching library, its import name, its \"use instead of\" guidance, and its MCP endpoint. Query it before writing any infrastructure.

        "},{"location":"13_library_registry/#notes","title":"Notes","text":"
        • Hexa is a full library with its own MCP server on :8008: read docs://index, docs://nav, and docs://modules/{module} (e.g. docs://modules/hexa.container) to discover the DI surface \u2014 build(cls, *, instances, config) and the PortNode model.
        • Auth Server is deferred: it has no pyproject.toml/importable package and no MCP bundle. Do not reach for it yet; py-jwt covers token primitives.
        • Library MCP servers expose documentation resources (not executable tools): they tell an agent the available API surface so it does not invent code.
        "}]} \ No newline at end of file diff --git a/standards/mcp/components.json b/standards/mcp/components.json index b144741..f76d541 100644 --- a/standards/mcp/components.json +++ b/standards/mcp/components.json @@ -36,6 +36,12 @@ "placeholder": "resource", "description": "Thin HTTP handlers bound to operationIds." }, + { + "kind": "openapi-routes", + "path": "interfaces/openapi.py", + "placeholder": "", + "description": "Route registry: flat re-export of every handler namespace." + }, { "kind": "test-model", "path": "tests/unit/test_item_models.py", @@ -48,6 +54,18 @@ "placeholder": "resource", "description": "Integration test at the HTTP edge." }, + { + "kind": "test-openapi-build", + "path": "tests/integration/test_openapi_app_builds.py", + "placeholder": "", + "description": "Generic test: every spec operationId resolves and the app builds." + }, + { + "kind": "test-pipeline", + "path": "tests/unit/test_item_sync.py", + "placeholder": "", + "description": "Unit test for the smallest pipeline form." + }, { "kind": "pipeline-abc", "path": "pipelines/example/abc.py", @@ -168,6 +186,12 @@ "placeholder": "", "description": "API + worker image with private-index wiring." }, + { + "kind": "dockerignore", + "path": ".dockerignore", + "placeholder": "", + "description": "Docker build-context exclusions." + }, { "kind": "compose", "path": "docker-compose.yaml", @@ -198,6 +222,30 @@ "placeholder": "", "description": "Generic CRUD service adaptor." }, + { + "kind": "http-cache", + "path": "infra/http_cache.py", + "placeholder": "", + "description": "Response cache decorator + Redis store (generation invalidation)." + }, + { + "kind": "progress-bus", + "path": "infra/adapters/sse.py", + "placeholder": "", + "description": "In-memory SSE progress bus (keyed by an event attribute)." + }, + { + "kind": "kv-store", + "path": "infra/adapters/kv.py", + "placeholder": "", + "description": "KV port + in-memory fallback + optional Redis adaptor." + }, + { + "kind": "package-init", + "path": "domain/__init__.py", + "placeholder": "", + "description": "Empty package marker; add an __init__.py to every package dir." + }, { "kind": "script-seed", "path": "scripts/seed.py", diff --git a/standards/mcp/modules/build_recipe.json b/standards/mcp/modules/build_recipe.json index 289a526..3604400 100644 --- a/standards/mcp/modules/build_recipe.json +++ b/standards/mcp/modules/build_recipe.json @@ -2,7 +2,7 @@ "module": "build_recipe", "content": { "path": "build_recipe", - "docstring": "# Build Recipe\n\n## Summary\n\nThe ordered, deterministic checklist for generating a new application of the\nstandard shape. Every step is fixed; only the resource model (step 2) and the\npipeline family (step 7) need app-specific authoring.\n\nThis page is served by the `standards` MCP as `docs://modules/build_recipe`. The\n`start_app` prompt on that server collects the two axes and hands off here; pull\nexact files with `get_app_component`.\n\n---\n\n## 🚀 Bootstrap\n\nFrom the standards docs checkout:\n\n```bash\ncp -r /standards/src/templates/app apps/\ncd apps/\n```\n\nInstall the internal libraries — editable local checkouts (preferred in\ndevelopment) or the private index (CI / fresh machines). Credentials are\nenvironment-only; never commit them.\n\n```bash\n# local checkouts\npip install -e ../aetos/openapi-first -e ../aetos/mongo-ops -e ../aetos/hexa \\\n -e ../aetos/dagpipe -e ../aetos/doc-forge\n# private index\npip install --extra-index-url \\\n \"https://$PIP_REGISTRY_USERNAME:$PIP_REGISTRY_PASSWORD@pip.aetoskia.com/simple\" .\n```\n\n---\n\n## 📋 Agent prompt (copyable)\n\n````markdown\n# Build a new application from the Aetos Standards blueprint\n\nYou are coding a complete new backend application conforming to the Aetos\nstandard app blueprint. Execute every step in order. Only steps 2 and 3 use the\napp-specific details provided by the user; everything else is deterministic.\n\n## Before writing any code\n\nConnect to the `standards` MCP server and read:\n- `docs://nav` (the module list)\n- `docs://modules/genericity_matrix` (Tier A library / Tier B template / Tier C authored)\n- `docs://modules/layering_rules` (the dependency rules)\n\nThen:\n- call `find_library` with `db`, `di`, `routing`, `ingestion`, `pipeline`, `docs`\n and note the Tier A libraries that already exist — never reimplement one;\n- call `list_app_components()` and pull each file you need with\n `get_app_component(kind, resource=..., family=...)` instead of writing it from\n scratch.\n\n## Steps\n\n1. **Scaffold** — copy `standards/src/templates/app/` into `apps//`.\n Keep the tree: `domain/`, `infra/`, `interfaces/`, `pipelines/`, `tests/`,\n `scripts/`, plus `pyproject.toml`, `openapi.yaml`, `config.py`, `init.py`,\n `main.py`, `worker.py`, `Dockerfile`, `docker-compose.yaml`, `.env.example`.\n\n2. **Author the spec (app-specific)** — write `openapi.yaml`: resources,\n schemas, `operationId`s, security, and extension metadata. This is the\n contract and drives all routing.\n\n3. **Write each CRUD slice (app-specific)** — per resource create the four-file\n slice: `models.py`, `ports/db.py`, `ports/service.py`, and optional\n `converters.py`. Follow the CRUD template.\n\n4. **Wire the composition root** — in `init.py`, build leaf repositories, then\n dependent repositories, then services; add infra adapters (db, cache, stores,\n progress bus, fetchers). Expose `get_svc` overloads.\n\n5. **Add routes** — create `interfaces//routes.py` thin handlers bound\n to `operationId`s and re-export them in `interfaces/openapi.py`. Add the cache\n decorator to read endpoints.\n\n6. **Bootstrap app and worker** — confirm `main.py` runs the OpenAPI-first app\n with its lifespan. If the app has long-running jobs, adapt `worker.py` to poll\n the job collection, run the pipeline, and publish progress.\n\n7. **Add pipeline families (only if data-driven)** — copy `pipelines/example/`\n to `pipelines//` with `abc.py`, `impl.py`, `pipeline.yaml`,\n `resources/`, and reuse `pipelines/shared/` (loader + registry). Register the\n root. Add fixtures and a parity test.\n\n8. **Add infrastructure only where needed** — Tier A capability: use the\n library. Tier B capability: copy the template. Never hand-write a Tier A\n library.\n\n9. **Tests** — integration tests with fakes at the HTTP edge; unit tests for\n domain and pipeline logic; a config-consistency test for rule-driven\n pipelines.\n\n10. **Scripts and seeds** — operator scripts under `scripts/` using\n `init()` / `get_svc()` / `shutdown()`; run as `python -m scripts.`.\n\n11. **Docs** — author GSDFC docstrings, generate `.pyi` stubs, mark the package\n `py.typed`, and build the docs.\n\n**Definition of done:**\n- `python -c \"import main\"` succeeds.\n- `pytest` passes.\n- The app serves every operation in the spec; the worker drains its queue.\n- No domain module imports a web framework.\n- No Tier A library was reimplemented.\n````\n\n---\n\n## 🧭 Where the steps land\n\nThe steps map onto the layers as follows:\n\n| Step | Package / file | Depends on |\n|---------------------|-----------------------------------|---------------------------------------------|\n| 1 · Scaffold | whole `templates/app/` | — |\n| 2 · Spec | `openapi.yaml` | — |\n| 3 · CRUD slices | `domain//` | spec, CRUD template |\n| 4 · Wiring | `init.py` | every slice |\n| 5 · Routes | `interfaces//` | services, openapi route registry |\n| 6 · Bootstrap | `main.py`, `worker.py` | composition root |\n| 7 · Pipelines | `pipelines//`, `shared/` | library pipeline engine |\n| 8 · Infra | `infra/` | domain ports |\n| 9 · Tests | `tests/` | app + fixtures |\n| 10 · Scripts | `scripts/` | composition root |\n| 11 · Docs | docstrings + docs build | code, Doc Forge |\n\n---\n\n## ⚠️ Definition of done\n\n- `python -c \"import main\"` succeeds.\n- `pytest` passes.\n- The app serves the spec's operations; the worker drains its queue.\n- No domain module imports a web framework.\n- No Tier A library was reimplemented.\n\n---\n\n## Related\n\n- [02 – Layering Rules](02_layering_rules.md) · [05 – CRUD Template](05_crud_template.md) · [06 – Pipeline Scaffold](06_pipeline_scaffold.md) · [12 – Genericity Matrix](12_genericity_matrix.md)\n", + "docstring": "# Build Recipe\n\n## Summary\n\nThe ordered, deterministic checklist for generating a new application of the\nstandard shape. Every step is fixed; only the resource model (step 2), the\ncodegen pass (step 3), and the pipeline family (step 8) need app-specific work.\n\nThis page is served by the `standards` MCP as `docs://modules/build_recipe`. The\n`start_app` prompt on that server collects the two axes and hands off here; pull\nexact files with `get_app_component`.\n\n---\n\n## 🚀 Bootstrap\n\nFrom the standards docs checkout:\n\n```bash\ncp -r /standards/src/templates/app apps/\ncd apps/\n```\n\nInstall the internal libraries — editable local checkouts (preferred in\ndevelopment) or the private index (CI / fresh machines). Credentials are\nenvironment-only; never commit them.\n\n```bash\n# local checkouts\npip install -e ../aetos/openapi-first -e ../aetos/mongo-ops -e ../aetos/hexa \\\n -e ../aetos/dagpipe -e ../aetos/doc-forge\n# private index\npip install --extra-index-url \\\n \"https://$PIP_REGISTRY_USERNAME:$PIP_REGISTRY_PASSWORD@pip.aetoskia.com/simple\" .\n```\n\n---\n\n## 📋 Agent prompt (copyable)\n\n````markdown\n# Build a new application from the Aetos Standards blueprint\n\nYou are coding a complete new backend application conforming to the Aetos\nstandard app blueprint. Execute every step in order. Only steps 2–4 use the\napp-specific details provided by the user; everything else is deterministic.\n\n## Before writing any code\n\nConnect to the `standards` MCP server and read:\n- `docs://nav` (the module list)\n- `docs://modules/genericity_matrix` (Tier A library / Tier B template / Tier C authored)\n- `docs://modules/layering_rules` (the dependency rules)\n\nThen:\n- call `find_library` with `db`, `di`, `routing`, `ingestion`, `pipeline`, `docs`\n and note the Tier A libraries that already exist — never reimplement one;\n- call `list_app_components()` and pull each file you need with\n `get_app_component(kind, resource=..., family=...)` instead of writing it from\n scratch.\n\n## Steps\n\n1. **Scaffold** — copy `standards/src/templates/app/` into `apps//`\n (or pull the same tree via `get_app_template`). Keep the tree: `domain/`,\n `infra/`, `interfaces/`, `pipelines/`, `tests/`, `scripts/`, plus\n `pyproject.toml`, `openapi.yaml`, `config.py`, `init.py`, `main.py`,\n `worker.py`, `Dockerfile`, `docker-compose.yaml`, `.env.example`.\n\n2. **Author the spec (app-specific)** — write `openapi.yaml`: resources,\n schemas, `operationId`s, security, and extension metadata. This is the\n contract and drives all routing. Every operation MUST have an `operationId`.\n\n3. **Generate the scaffold (codegen)** — from the spec, generate the wire\n models and the exact handler signatures:\n ```bash\n openapi-first models openapi.yaml -o _gen/models.py\n openapi-first routes openapi.yaml -o _gen/routes \\\n --use-models --models-module _gen.models --inject-request --package\n ```\n `_gen/models.py` is the **wire contract** (Pydantic, one class per\n `components.schemas` entry). `_gen/routes//routes.py` lists every\n `operationId` with its exact parameter signature. Treat `_gen/` as generated\n (never hand-edit it) — the canonical handlers live in `interfaces/`.\n\n4. **Write each CRUD slice (app-specific)** — per resource create the four-file\n slice: `models.py` (domain model, **no `id`**), `ports/db.py`\n (`class XDocument(BaseDocument, X)` — id comes from `BaseDocument`),\n `ports/service.py`, and optional `converters.py`. Follow the CRUD template.\n\n5. **Wire the composition root** — in `init.py`, build leaf repositories, then\n dependent repositories, then services; add infra adapters (db, cache, stores,\n progress bus, fetchers). Expose `get_svc` overloads.\n\n6. **Add routes** — create `interfaces//routes.py` thin handlers whose\n names match the `operationId`s (use the signatures from step 3 as your\n checklist) and re-export them in `interfaces/openapi.py`. Add the cache\n decorator to read endpoints.\n\n7. **Bootstrap app and worker** — confirm `main.py` runs the OpenAPI-first app\n with its lifespan. If the app has long-running jobs, adapt `worker.py` to poll\n the job collection, run the pipeline, and publish progress.\n\n8. **Add pipeline families (only if data-driven)** — copy `pipelines/example/`\n to `pipelines//` with `abc.py`, `impl.py`, `pipeline.yaml`,\n `resources/`, and reuse `pipelines/shared/` (loader + registry). Register the\n root. Add fixtures and a parity test.\n\n9. **Add infrastructure only where needed** — Tier A capability: use the\n library. Tier B capability: copy the template. Never hand-write a Tier A\n library.\n\n10. **Tests** — integration tests with fakes at the HTTP edge; unit tests for\n domain and pipeline logic; a config-consistency test for rule-driven\n pipelines. Include `tests/integration/test_openapi_app_builds.py` (imports\n the app, asserts `import main` binds every operation).\n\n11. **Scripts and seeds** — operator scripts under `scripts/` using\n `init()` / `get_svc()` / `shutdown()`; run as `python -m scripts.`.\n\n12. **Docs** — author GSDFC docstrings, generate `.pyi` stubs, mark the package\n `py.typed`, and build the docs.\n\n**Definition of done:**\n- `python -c \"import main\"` succeeds.\n- `pytest` passes.\n- The app serves every operation in the spec; the worker drains its queue.\n- No domain module imports a web framework.\n- No Tier A library was reimplemented.\n````\n\n---\n\n## 🧭 Where the steps land\n\nThe steps map onto the layers as follows:\n\n| Step | Package / file | Depends on |\n|---------------------|-----------------------------------|---------------------------------------------|\n| 1 · Scaffold | whole `templates/app/` | — |\n| 2 · Spec | `openapi.yaml` | — |\n| 3 · Codegen | `_gen/` (generated) | spec, openapi-first |\n| 4 · CRUD slices | `domain//` | spec, CRUD template |\n| 5 · Wiring | `init.py` | every slice |\n| 6 · Routes | `interfaces//` | services, openapi route registry |\n| 7 · Bootstrap | `main.py`, `worker.py` | composition root |\n| 8 · Pipelines | `pipelines//`, `shared/` | library pipeline engine |\n| 9 · Infra | `infra/` | domain ports |\n| 10 · Tests | `tests/` | app + fixtures |\n| 11 · Scripts | `scripts/` | composition root |\n| 12 · Docs | docstrings + docs build | code, Doc Forge |\n\n---\n\n## 🔧 The codegen fast path, in detail\n\n`openapi-first` is spec → code only (it never generates a spec). Use it to erase\nthe two mechanical parts of the build:\n\n| Generated artifact | Replaces | Notes |\n|--------------------|----------|-------|\n| `_gen/models.py` | hand-writing Pydantic wire models | one class per `components.schemas` entry |\n| `_gen/routes//routes.py` | hand-typing handler signatures | names == `operationId`s; `request: Request` injected |\n\nRules of the road:\n\n- **Wire models are not domain models.** `_gen/models.py` is the HTTP contract;\n `domain//models.py` is your business shape. If the spec's schema has\n a required `id: str`, keep it off the domain model and let the document get its\n id from `BaseDocument`.\n- **Resource names are sanitized.** `/fetch-requests` → `fetch_requests.py`.\n- **Multipart bodies expand** to `UploadFile = File(...)` / `Form(...)` params.\n- **`_gen/` is disposable.** Regenerate on every spec change; `git diff` shows\n exactly what moved.\n\n---\n\n## ⚠️ Definition of done\n\n- `python -c \"import main\"` succeeds.\n- `pytest` passes.\n- The app serves the spec's operations; the worker drains its queue.\n- No domain module imports a web framework.\n- No Tier A library was reimplemented.\n\n---\n\n## Related\n\n- [02 – Layering Rules](02_layering_rules.md) · [05 – CRUD Template](05_crud_template.md) · [06 – Pipeline Scaffold](06_pipeline_scaffold.md) · [12 – Genericity Matrix](12_genericity_matrix.md)\n", "objects": {} } } \ No newline at end of file diff --git a/standards/mcp/modules/crud_template.json b/standards/mcp/modules/crud_template.json index f2d855a..c9b8f98 100644 --- a/standards/mcp/modules/crud_template.json +++ b/standards/mcp/modules/crud_template.json @@ -2,7 +2,7 @@ "module": "crud_template", "content": { "path": "crud_template", - "docstring": "# CRUD Template\n\n## Summary\n\nEvery resource follows the same four-file slice. Repetition is intentional: it\nmakes the codebase predictable and generatable.\n\n---\n\n## The slice\n\n```\ndomain//\n models.py # pure Pydantic model (no DB fields)\n ports/\n db.py # document model + repository factory\n service.py # service class + factory\n converters.py # (optional) transforms\n```\n\n## models.py\n\nA framework-free Pydantic model describing the business shape.\n\n```python\nclass Item(BaseModel):\n name: str\n quantity: int = 0\n```\n\n## ports/db.py\n\nThe persistence model extends the business model and adds database concerns.\nThe repository factory selects the right repository flavour.\n\n```python\nclass ItemDocument(Item, BaseDocument): # BaseDocument adds id/created_at/...\n pass\n\ndef create_item_repo(*, owner_repo=None) -> ItemRepository:\n return repo_factory(\n collection=\"items\",\n document=ItemDocument,\n populate_rules=[PopulateRule(field=\"owner\", collection=\"users\")],\n cache=None, # or a CachedBaseRepository config\n )\n```\n\n## ports/service.py\n\nThe service is built through the DI library, injecting the repository. The\ngeneric CRUD base provides `create/read/update/patch/delete/search/...`.\n\n```python\nclass ItemService(CrudsServiceAdaptor[Item, ItemDocument]):\n model = Item\n schema = ItemDocument\n\ndef get_item_service(repo) -> ItemService:\n return build(ItemService, instances={\"repo\": repo})\n```\n\n## Naming conventions\n\n| Concept | Convention |\n|---|---|\n| collection | plural snake case (`items`) |\n| document | `Document` |\n| repository factory | `create__repo` |\n| service | `Service` |\n| service factory | `get__service` |\n| resource key | `_svc` |\n| operationId | `list_items`, `create_item`, ... |\n\n## Optional files\n\n- `converters.py` — pure functions mapping between domain types.\n- `ports/enrichment.py` — adapts a pipeline into an async callable the service\n can invoke (used when a resource is populated by a pipeline).\n", + "docstring": "# CRUD Template\n\n## Summary\n\nEvery resource follows the same four-file slice. Repetition is intentional: it\nmakes the codebase predictable and generatable.\n\n---\n\n## The slice\n\n```\ndomain//\n models.py # pure Pydantic model (no DB fields)\n ports/\n db.py # document model + repository factory\n service.py # service class + factory\n converters.py # (optional) transforms\n```\n\n## The id rule (model vs. document)\n\nTwo different objects, two different jobs:\n\n| Object | Lives in | Has `id`? | Purpose |\n|---|---|---|---|\n| **Domain model** | `domain//models.py` | **No** | business shape, framework-free |\n| **Document** | `domain//ports/db.py` | **Yes** (from `BaseDocument`) | Mongo persistence shape |\n\n`BaseDocument` (mongo-ops) supplies `id: Optional[PyObjectId]` (aliased to\n`_id`) plus `created_at` / `updated_at`. The domain model must **not** declare\n`id`; Mongo owns it.\n\n> **Trap — generated wire models.** `openapi-first models` produces the HTTP\n> *contract* (see the [Build Recipe](01_build_recipe.md), step 3). Response\n> schemas frequently declare a required `id: str`, so a generated class is\n> **not** a domain model. If you write\n> `class ItemDocument(Item, BaseDocument)` and `Item` re-exports a required\n> `id: str`, that field shadows `BaseDocument.id` and no Mongo `_id` can ever be\n> set. Fix it one of three ways:\n>\n> 1. **Preferred:** keep the domain model id-free (hand-author it from the CRUD\n> template), and keep the required `id` only on the wire model.\n> 2. Order the bases so the document wins:\n> `class ItemDocument(BaseDocument, Item)`.\n> 3. Keep a separate document type and translate with `ports/converters.py`.\n\n## models.py\n\nA framework-free Pydantic model describing the business shape (no `id`).\n\n```python\nclass Item(BaseModel):\n name: str\n quantity: int = 0\n```\n\n## ports/db.py\n\nThe persistence model extends the business model and adds database concerns.\nThe repository factory selects the right repository flavour.\n\n```python\n# Item is id-free; BaseDocument adds id/created_at/updated_at.\nclass ItemDocument(Item, BaseDocument):\n pass\n\ndef create_item_repo(*, owner_repo=None) -> ItemRepository:\n return repo_factory(\n collection=\"items\",\n document=ItemDocument,\n populate_rules=[PopulateRule(field=\"owner\", collection=\"users\")],\n cache=None, # or a CachedBaseRepository config\n )\n```\n\n## ports/service.py\n\nThe service is built through the DI library, injecting the repository. The\ngeneric CRUD base provides `create/read/update/patch/delete/search/...`.\n\n```python\nclass ItemService(CrudsServiceAdaptor[Item, ItemDocument]):\n model = Item\n schema = ItemDocument\n\ndef get_item_service(repo) -> ItemService:\n return build(ItemService, instances={\"repo\": repo})\n```\n\n## Naming conventions\n\n| Concept | Convention |\n|---|---|\n| collection | plural snake case (`items`) |\n| document | `Document` |\n| repository factory | `create__repo` |\n| service | `Service` |\n| service factory | `get__service` |\n| resource key | `_svc` |\n| operationId | `list_items`, `create_item`, ... |\n\n## Optional files\n\n- `converters.py` — pure functions mapping between domain types.\n- `ports/enrichment.py` — adapts a pipeline into an async callable the service\n can invoke (used when a resource is populated by a pipeline).\n", "objects": {} } } \ No newline at end of file diff --git a/standards/mcp/modules/genericity_matrix.json b/standards/mcp/modules/genericity_matrix.json index 688e482..51f85db 100644 --- a/standards/mcp/modules/genericity_matrix.json +++ b/standards/mcp/modules/genericity_matrix.json @@ -2,7 +2,7 @@ "module": "genericity_matrix", "content": { "path": "genericity_matrix", - "docstring": "# Genericity Matrix\n\n## Summary\n\nThis page states precisely what is **library-backed**, what is\n**template-provided**, and what is **app-specific and must be authored**. Use it\nto scope a new build and to know, for any given capability, whether to call a\nlibrary, copy template code, or write it yourself.\n\n---\n\n## Tier A — Library-backed (use; never reimplement)\n\nThese capabilities already exist as internal libraries. Reach them through the\nlibrary registry MCP (`find_library(capability)`) and read the library's own MCP\nfor its API. Writing a private copy of any of these is a defect.\n\n| Capability | Library | MCP |\n|---|---|---|\n| Persistence: repositories, cache, populate, transactions | Mongo Ops (`mongo_ops`) | `:8007` |\n| Dependency injection / composition | Hexa (`hexa`) | `:8008` |\n| Deterministic multi-step transformation engine | DAG Pipe (`dagpipe`) | `:8006` |\n| Spec-first HTTP edge, client, codegen | OpenAPI First (`openapi_first`) | `:8004` |\n| Format-agnostic fetch + parse (HTML, PDF, CSV, XLSX) | Omniread (`omniread`) | `:8003` |\n| Read-only email ingestion | Mail Intake (`mail_intake`) | `:8002` |\n| JWT auth primitives (register, login, introspect) | Py JWT (`jwtlib`) | `:8005` |\n| Documentation build (MkDocs + MCP) | Doc Forge (`docforge`) | `:8001` |\n\n## Tier B — Template-provided (copy from `templates/app`, then adapt)\n\nThese are reusable internal components shipped **inside** the template — they are\nnot independent libraries. Copy them as-is, then rename/populate only what is\napp-specific. They give every app the same wiring shape as the reference app.\n\n| Component | Template location | Action |\n|---|---|---|\n| Layering & dependency rule | the package layout + import rules | keep |\n| Composition root shape | `init.py` (`get_svc`/`get_db`, idempotency) | keep |\n| Spec-first web edge | `interfaces/openapi.py`, thin handlers bound to `operationId` | keep |\n| CRUD slice pattern | `domain//{models.py, ports/db.py, ports/service.py, converters.py}` | rename + fill |\n| Generic CRUD service | `infra/adapters/service.py` (`CrudsServiceAdaptor`) | keep |\n| Repository factory | `infra/db/repos/mongo.py` (`_repo_factory`) | keep |\n| Worker poll + status loop | `worker.py` | keep |\n| Progress bus + streaming | bus port + in-memory adapter + SSE endpoint | keep |\n| Human-in-the-loop | durable pause + question document + resolve endpoint + resume map | keep |\n| HTTP response cache | `cache_response` decorator + generation invalidation | keep |\n| Key-value store | KV store port + Redis adapter + in-memory fallback | keep |\n| LLM client pool | provider-select + retry pool scaffold | keep |\n| Pipeline scaffold | `pipelines/shared/{ports,loader,registry}.py` + `pipelines//` | rename + fill |\n| Operator scripts | `scripts/` (`init()` / `get_svc()` / `shutdown()`) | rename |\n| Testing harness | integration fakes + fixture-parity pattern | keep |\n| Project scaffolding | `pyproject.toml`, `Dockerfile`, `docker-compose.yaml`, `.env.example`, `openapi.yaml` | rename + fill |\n\n## Tier C — App-specific (must be authored)\n\n| Artifact | Why |\n|---|---|\n| Resource models | the business shape |\n| Repository populate/cache rules | the relationships |\n| Service business logic | domain behaviour |\n| Pipeline families | the transformation graph |\n| Pattern/rule resources | data-driven policy |\n| Fixtures & golden outputs | test data |\n\n---\n\n## The two axes\n\nEverything app-specific reduces to **two axes**:\n\n1. **Resource model** — the set of resources, their fields, relationships, and\n the OpenAPI contract.\n2. **Pipeline family** — only if inputs require multi-step transformation.\n\nIf you can specify those two axes, the rest of the app follows mechanically:\nTier A is imported, Tier B is copied, and Tier C is the only thing you author.\n", + "docstring": "# Genericity Matrix\n\n## Summary\n\nThis page states precisely what is **library-backed**, what is\n**template-provided**, and what is **app-specific and must be authored**. Use it\nto scope a new build and to know, for any given capability, whether to call a\nlibrary, copy template code, or write it yourself.\n\n---\n\n## Tier A — Library-backed (use; never reimplement)\n\nThese capabilities already exist as internal libraries. Reach them through the\nlibrary registry MCP (`find_library(capability)`) and read the library's own MCP\nfor its API. Writing a private copy of any of these is a defect.\n\n| Capability | Library | MCP |\n|---|---|---|\n| Persistence: repositories, cache, populate, transactions | Mongo Ops (`mongo_ops`) | `:8007` |\n| Dependency injection / composition | Hexa (`hexa`) | `:8008` |\n| Deterministic multi-step transformation engine | DAG Pipe (`dagpipe`) | `:8006` |\n| Spec-first HTTP edge, client, codegen | OpenAPI First (`openapi_first`) | `:8004` |\n| Format-agnostic fetch + parse (HTML, PDF, CSV, XLSX) | Omniread (`omniread`) | `:8003` |\n| Read-only email ingestion | Mail Intake (`mail_intake`) | `:8002` |\n| JWT auth primitives (register, login, introspect) | Py JWT (`jwtlib`) | `:8005` |\n| Documentation build (MkDocs + MCP) | Doc Forge (`docforge`) | `:8001` |\n\n## Tier B — Template-provided (copy from `templates/app`, then adapt)\n\nThese are reusable internal components shipped **inside** the template — they are\nnot independent libraries. Copy them as-is, then rename/populate only what is\napp-specific. They give every app the same wiring shape as the reference app.\nFetch them by kind with `get_app_component`, or the whole tree with\n`get_app_template`.\n\n| Component | Template location | Action | Shipped |\n|---|---|---|---|\n| Layering & dependency rule | the package layout + import rules | keep | ✅ |\n| Composition root shape | `init.py` (`get_svc`/`get_db`, idempotency) | keep | ✅ |\n| Spec-first web edge | `interfaces/openapi.py`, thin handlers bound to `operationId` | keep | ✅ |\n| CRUD slice pattern | `domain//{models.py, ports/db.py, ports/service.py, converters.py}` | rename + fill | ✅ |\n| Generic CRUD service | `infra/adapters/service.py` (`CrudsServiceAdaptor`) | keep | ✅ |\n| Repository factory | `infra/db/repos/mongo.py` (`_repo_factory`) | keep | ✅ |\n| Worker poll + status loop | `worker.py` | keep | ✅ |\n| Progress bus | `infra/adapters/sse.py` (keyed in-memory bus) + an app SSE endpoint | keep | ✅ |\n| HTTP response cache | `infra/http_cache.py` (`cache_response` + generation invalidation) | keep | ✅ |\n| Key-value store | `infra/adapters/kv.py` (port + in-memory + Redis adaptor) | keep | ✅ |\n| Pipeline scaffold | `pipelines/shared/{ports,loader,registry}.py` + `pipelines//` | rename + fill | ✅ |\n| Operator scripts | `scripts/` (`init()` / `get_svc()` / `shutdown()`) | rename | ✅ |\n| Testing harness | integration fakes + fixture-parity pattern | keep | ✅ |\n| Project scaffolding | `pyproject.toml`, `Dockerfile`, `docker-compose.yaml`, `.env.example`, `openapi.yaml` | rename + fill | ✅ |\n\n## Tier B (patterns) — port from the reference app\n\nA few Tier B capabilities are real and reusable, but are **shaped by the app or\nthe provider**, so the template does not ship them verbatim — copying them\nblindly would carry app-specific names. Treat them as patterns: lift the shape\nfrom the reference app and rename per app.\n\n| Component | Reference location | Action | Shipped |\n|---|---|---|---|\n| Human-in-the-loop | durable pause + question document + resolve endpoint + resume map | port + reshape | ✗ |\n| LLM client pool | provider-select + retry/rotation pool scaffold | port + reshape | ✗ |\n\n## Tier C — App-specific (must be authored)\n\n| Artifact | Why |\n|---|---|\n| Resource models | the business shape |\n| Repository populate/cache rules | the relationships |\n| Service business logic | domain behaviour |\n| Pipeline families | the transformation graph |\n| Pattern/rule resources | data-driven policy |\n| Fixtures & golden outputs | test data |\n\n---\n\n## The two axes\n\nEverything app-specific reduces to **two axes**:\n\n1. **Resource model** — the set of resources, their fields, relationships, and\n the OpenAPI contract.\n2. **Pipeline family** — only if inputs require multi-step transformation.\n\nIf you can specify those two axes, the rest of the app follows mechanically:\nTier A is imported, Tier B is copied, and Tier C is the only thing you author.\n", "objects": {} } } \ No newline at end of file diff --git a/standards/mcp/modules/spec_first_routing.json b/standards/mcp/modules/spec_first_routing.json index 96a3371..6b155e4 100644 --- a/standards/mcp/modules/spec_first_routing.json +++ b/standards/mcp/modules/spec_first_routing.json @@ -2,7 +2,7 @@ "module": "spec_first_routing", "content": { "path": "spec_first_routing", - "docstring": "# Spec-First Routing\n\n## Summary\n\nThe OpenAPI document is the single source of truth for the HTTP contract.\nHandlers are bound to it by name; there is no decorator router.\n\n---\n\n## The contract\n\n- `openapi.yaml` (or `.json`) declares paths, schemas, parameters, responses,\n and security.\n- A custom `operationId` names the handler function.\n- The framework (`openapi_first`) validates requests/responses against the\n spec and dispatches to the handler whose name equals `operationId`.\n\n## App bootstrap\n\n```python\n# main.py\nfrom openapi_first.app import OpenAPIFirstApp\n\napp = OpenAPIFirstApp(\n openapi_path=\"openapi.yaml\",\n routes_module=interfaces.openapi, # module whose namespace holds handlers\n title=\" API\",\n version=\"0.1.0\",\n lifespan=lifespan,\n)\n```\n\n`lifespan` calls the composition root:\n\n```python\n@asynccontextmanager\nasync def lifespan(app):\n app.state.db_resources = await init()\n yield\n await shutdown()\n```\n\n## Handler registry\n\n`interfaces/openapi.py` is a flat re-export of every handler, so the framework\ncan resolve `operationId` across the whole app:\n\n```python\nfrom interfaces.item.routes import *\nfrom interfaces.wallet.routes import *\nfrom interfaces.progress.sse import *\n```\n\n## Handler shape\n\nHandlers are thin and synchronous with respect to business logic:\n\n```python\nasync def create_item(request: Request, body: dict):\n svc = request.app.state.db_resources[\"item_svc\"]\n return await svc.create(Item.model_validate(body))\n```\n\n## Conventions\n\n- `operationId` naming: `list_`, `create_`,\n `get_`, `update_`, `delete_`.\n- Read-only endpoints may use a cache decorator keyed on path + query + user.\n- Validation errors -> `400`; not found -> `404`; delete -> `204`.\n- Security is declared once in the spec and enforced by the framework; handlers\n contain no auth code.\n\n## Codegen\n\nThe `openapi_first` library can scaffold models and route stubs from the spec,\nand ships app templates (CRUD app, model app, service). Prefer codegen over\nhand-writing boilerplate.\n", + "docstring": "# Spec-First Routing\n\n## Summary\n\nThe OpenAPI document is the single source of truth for the HTTP contract.\nHandlers are bound to it by name; there is no decorator router.\n\n---\n\n## The contract\n\n- `openapi.yaml` (or `.json`) declares paths, schemas, parameters, responses,\n and security.\n- A custom `operationId` names the handler function.\n- The framework (`openapi_first`) validates requests/responses against the\n spec and dispatches to the handler whose name equals `operationId`.\n\n## App bootstrap\n\n```python\n# main.py\nfrom openapi_first.app import OpenAPIFirstApp\n\napp = OpenAPIFirstApp(\n openapi_path=\"openapi.yaml\",\n routes_module=interfaces.openapi, # module whose namespace holds handlers\n title=\" API\",\n version=\"0.1.0\",\n lifespan=lifespan,\n)\n```\n\n`lifespan` calls the composition root:\n\n```python\n@asynccontextmanager\nasync def lifespan(app):\n app.state.db_resources = await init()\n yield\n await shutdown()\n```\n\n## Handler registry\n\n`interfaces/openapi.py` is a flat re-export of every handler, so the framework\ncan resolve `operationId` across the whole app:\n\n```python\nfrom interfaces.item.routes import *\nfrom interfaces.wallet.routes import *\nfrom interfaces.progress.sse import *\n```\n\n## Handler shape\n\nHandlers are thin and synchronous with respect to business logic:\n\n```python\nasync def create_item(request: Request, body: dict):\n svc = request.app.state.db_resources[\"item_svc\"]\n return await svc.create(Item.model_validate(body))\n```\n\n## Conventions\n\n- `operationId` naming: `list_`, `create_`,\n `get_`, `update_`, `delete_`.\n- Read-only endpoints may use a cache decorator keyed on path + query + user.\n- Validation errors -> `400`; not found -> `404`; delete -> `204`.\n- Security is declared once in the spec and enforced by the framework; handlers\n contain no auth code.\n\n## Authless apps\n\nAuth is **deferred** in the standard: the same spec-first app runs with or\nwithout it, because security lives entirely in the spec, never in handlers.\nFor an app that needs no auth yet, do **not** invent per-handler auth checks —\njust omit the security keys:\n\n- Remove the top-level `security:` block.\n- Remove `components.securitySchemes`.\n- Do **not** add `security: []` to operations. An explicit empty array is a\n per-operation *override* that means \"public\", and it only matters when a\n global `security` block exists. Leaving every operation without a `security`\n key is the cleanest authless spec.\n\nThe handlers, services, and composition root are unchanged. When auth is needed\nlater, add the global `security` block and an introspecting dependency; only\nhandlers that must opt out get `security: []`.\n\n## Codegen\n\n`openapi_first` scaffolds models and route stubs from the spec (see the\n[Build Recipe](01_build_recipe.md), step 3):\n\n```bash\nopenapi-first models openapi.yaml -o _gen/models.py\nopenapi-first routes openapi.yaml -o _gen/routes \\\n --use-models --models-module _gen.models --inject-request --package\n```\n\nPrefer codegen over hand-writing boilerplate. The result is the wire contract\nand a set of `operationId`-named signatures — not the canonical handlers.\n", "objects": {} } } \ No newline at end of file diff --git a/standards/mcp/templates/app/infra/adapters/kv.py b/standards/mcp/templates/app/infra/adapters/kv.py new file mode 100644 index 0000000..00e3ff9 --- /dev/null +++ b/standards/mcp/templates/app/infra/adapters/kv.py @@ -0,0 +1,147 @@ +"""# Summary + +Key-value store port with in-memory and Redis adaptors. + +--- + +Reserve infrastructure for caching small values by key (sessions, locks, +result blobs, rate counters). The in-memory adaptor needs no services and is a +safe default; the Redis adaptor is opt-in and imports ``redis`` lazily so it +never becomes a hard dependency. + +Example: + ```python + from infra.adapters.kv import InMemoryKV + + kv = InMemoryKV() + await kv.set("greeting", "hello", ttl=30) + assert await kv.get("greeting") == "hello" + ``` +""" +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Dict, List, Optional + + +class KVStore(ABC): + """Abstract key-value store port.""" + + @abstractmethod + async def get(self, key: str) -> Optional[str]: + """Return the value at ``key``, or ``None``.""" + + @abstractmethod + async def set(self, key: str, value: str, ttl: Optional[int] = None) -> None: + """Store ``value`` at ``key`` with an optional TTL in seconds.""" + + @abstractmethod + async def delete(self, key: str) -> None: + """Delete ``key`` if present.""" + + @abstractmethod + async def scan(self, pattern: str) -> List[str]: + """Return keys matching a glob ``pattern``.""" + + async def clear(self, pattern: str = "*") -> None: + """Delete all keys matching ``pattern``. + + Args: + pattern (str): Glob-style key pattern. + """ + for key in await self.scan(pattern): + await self.delete(key) + + async def close(self) -> None: + """Release any underlying resources. Default is a no-op.""" + + +class InMemoryKV(KVStore): + """Process-local key-value store used as a dependency-free fallback. + + Attributes: + _data (Dict[str, str]): In-memory value map. + _expiry (Dict[str, float]): Absolute expiry timestamps by key; ``0.0`` + means "no expiry". + """ + + def __init__(self) -> None: + """Initialize an empty store with no imports or connections.""" + self._data: Dict[str, str] = {} + self._expiry: Dict[str, float] = {} + + def _expired(self, key: str) -> bool: + import time + + deadline = self._expiry.get(key, 0.0) + if deadline and deadline < time.time(): + self._data.pop(key, None) + self._expiry.pop(key, None) + return True + return False + + async def get(self, key: str) -> Optional[str]: + """Return the value at ``key``, or ``None`` when absent/expired.""" + if self._expired(key): + return None + return self._data.get(key) + + async def set(self, key: str, value: str, ttl: Optional[int] = None) -> None: + """Store ``value`` at ``key`` with an optional TTL in seconds.""" + import time + + self._data[key] = value + self._expiry[key] = (time.time() + ttl) if ttl else 0.0 + + async def delete(self, key: str) -> None: + """Delete ``key`` if present.""" + self._data.pop(key, None) + self._expiry.pop(key, None) + + async def scan(self, pattern: str) -> List[str]: + """Return live keys matching a glob ``pattern``.""" + import fnmatch + + return [k for k in list(self._data) if fnmatch.fnmatch(k, pattern) and not self._expired(k)] + + +class RedisKV(KVStore): + """Redis-backed key-value store. + + Attributes: + _redis: Asynchronous Redis client (imported lazily). + """ + + def __init__(self, url: str = "redis://localhost:6379/0", decode_responses: bool = True): + """Initialize the store with a Redis connection. + + Args: + url (str): Redis connection URL. + decode_responses (bool): Whether Redis returns decoded strings. + + Raises: + ImportError: When the optional ``redis`` package is not installed. + """ + from redis.asyncio import Redis + + self._redis = Redis.from_url(url, decode_responses=decode_responses) + + async def get(self, key: str) -> Optional[str]: + """Return the value at ``key``, or ``None``.""" + return await self._redis.get(key) + + async def set(self, key: str, value: str, ttl: Optional[int] = None) -> None: + """Store ``value`` at ``key`` with an optional TTL in seconds.""" + await self._redis.set(key, value, ex=ttl) + + async def delete(self, key: str) -> None: + """Delete ``key`` if present.""" + await self._redis.delete(key) + + async def scan(self, pattern: str) -> List[str]: + """Return keys matching a glob ``pattern``.""" + return [key async for key in self._redis.scan_iter(match=pattern)] + + async def close(self) -> None: + """Close the underlying Redis connection.""" + await self._redis.aclose() diff --git a/standards/mcp/templates/app/infra/adapters/sse.py b/standards/mcp/templates/app/infra/adapters/sse.py new file mode 100644 index 0000000..70c6f31 --- /dev/null +++ b/standards/mcp/templates/app/infra/adapters/sse.py @@ -0,0 +1,54 @@ +"""# Summary + +SSEInMemoryBus — in-memory event bus for SSE progress events. + +--- + +Buffer published progress events so they can be replayed (and streamed) per +correlation key, such as a job, upload, or fetch-request id. +""" +from typing import Generic, List, TypeVar + +T = TypeVar("T") + + +class SSEInMemoryBus(Generic[T]): + """Buffer SSE progress events in memory, keyed by an event attribute. + + Attributes: + _events (List[T]): Events published since the last clear. + _key_attr (str): Event attribute used to correlate a stream. + """ + + def __init__(self, key_attr: str = "id"): + """Initialize an empty event buffer. + + Args: + key_attr (str): Attribute name on each event holding the stream + correlation key (for example ``"job_id"``). + """ + self._events: List[T] = [] + self._key_attr = key_attr + + async def publish(self, event: T) -> None: + """Append an event to the buffer. + + Args: + event (T): Event to append. + """ + self._events.append(event) + + def get_events(self, key: str) -> List[T]: + """Return buffered events for a correlation key. + + Args: + key (str): Value matched against the configured ``key_attr``. + + Returns: + List[T]: Events whose ``key_attr`` equals ``key``. + """ + return [e for e in self._events if getattr(e, self._key_attr, None) == key] + + def clear(self) -> None: + """Remove all buffered events.""" + self._events.clear() diff --git a/standards/mcp/templates/app/infra/http_cache.py b/standards/mcp/templates/app/infra/http_cache.py new file mode 100644 index 0000000..8debf88 --- /dev/null +++ b/standards/mcp/templates/app/infra/http_cache.py @@ -0,0 +1,229 @@ +"""# Summary + +Domain-agnostic HTTP response cache. + +--- + +A thin Redis-backed response cache that wraps route handlers as a decorator. + +Example: + ```python + from infra.http_cache import cache_response + + @cache_response(ttl=60) + async def list_items(request, skip: int = 0, limit: int = 100): + ... + ``` + +Notes: + The cache operates directly on the endpoint and is independent of any + repository-level cache. Cached responses expire via TTL. When no + ``http_cache_store`` is wired on ``request.app.state.db_resources`` the + decorator is a no-op pass-through, so applying/removing it is safe. +""" +import hashlib +import json +from functools import wraps +from typing import Any, Callable, Optional + +from fastapi import Request, Response +from fastapi.encoders import jsonable_encoder + +CACHE_PREFIX = "apicache:" +NS_PREFIX = CACHE_PREFIX + "ns:" +DEFAULT_TTL = 60 + + +class HttpCacheStore: + """Store string responses in Redis with TTL under the ``apicache:`` key space. + + Attributes: + _redis (Redis): Asynchronous Redis client bound to the configured URL. + """ + + def __init__(self, url: str = "redis://localhost:6379/0", decode_responses: bool = True): + """Initialize the store with a Redis connection. + + Args: + url (str): Redis connection URL. + decode_responses (bool): Whether Redis returns decoded strings. + + Raises: + ImportError: When the optional ``redis`` package is not installed. + """ + from redis.asyncio import Redis + + self._redis = Redis.from_url(url, decode_responses=decode_responses) + + async def get(self, key: str) -> Optional[str]: + """Read the value stored at ``key``. + + Args: + key (str): Cache key to read. + + Returns: + Optional[str]: Cached value, or ``None`` when absent. + """ + return await self._redis.get(CACHE_PREFIX + key) + + async def set(self, key: str, value: str, ttl: int) -> None: + """Store ``value`` at ``key`` with a TTL. + + Args: + key (str): Cache key to write. + value (str): Serialized value to persist. + ttl (int): Lifetime in seconds. + """ + await self._redis.set(CACHE_PREFIX + key, value, ex=ttl) + + async def delete(self, key: str) -> None: + """Delete the value stored at ``key``. + + Args: + key (str): Cache key to remove. + """ + await self._redis.delete(CACHE_PREFIX + key) + + async def generation(self, resource: str) -> str: + """Return the current generation counter for a resource namespace. + + Args: + resource (str): Resource namespace to read. + + Returns: + str: Generation counter, or ``"0"`` when unset. + """ + value = await self._redis.get(NS_PREFIX + resource) + return value if value is not None else "0" + + async def invalidate(self, *resources: str) -> None: + """Invalidate all cached responses for the given resource namespaces. + + Args: + *resources (str): Resource namespaces to invalidate. + + Notes: + Bumps each namespace's generation counter so existing entries become + unreachable immediately; Redis reclaims them at TTL expiry. + """ + if not resources: + return + pipe = self._redis.pipeline(transaction=False) + for resource in resources: + pipe.incr(NS_PREFIX + resource) + await pipe.execute() + + async def close(self) -> None: + """Close the underlying Redis connection.""" + await self._redis.aclose() + + +def _user_scope(request: Request) -> str: + """Build a stable per-user cache scope. + + Args: + request (Request): Incoming HTTP request. + + Returns: + str: Scoped identifier, or ``""`` for auth-free endpoints. + """ + user = getattr(request.state, "user", None) + if isinstance(user, dict): + for field in ("username", "id", "email"): + if user.get(field): + return str(user[field]) + token = getattr(request.state, "token", None) + if token: + return hashlib.sha256(str(token).encode()).hexdigest()[:16] + return "" + + +def _resource_name(request: Request) -> str: + """Return the first path segment identifying the resource. + + Args: + request (Request): Incoming HTTP request. + + Returns: + str: First path segment (for example ``"items"``), or ``"root"`` when + the path is empty. + """ + return request.url.path.strip("/").split("/", 1)[0] or "root" + + +def _cache_key(request: Request, generation: str = "0") -> str: + """Compute a stable cache key for a request. + + Args: + request (Request): Incoming HTTP request. + generation (str): Resource generation counter guarding stale entries. + + Returns: + str: SHA-256 digest of the request method, path, query and path + parameters, user scope, and generation. + """ + parts = [ + request.method, + request.url.path, + "&".join(f"{k}={v}" for k, v in sorted(request.query_params.items())), + "&".join(f"{k}={v}" for k, v in sorted(request.path_params.items())), + _user_scope(request), + generation, + ] + return hashlib.sha256("|".join(parts).encode()).hexdigest() + + +def cache_response(ttl: Optional[int] = None) -> Callable: + """Decorate a GET-style route handler to cache its JSON response. + + Args: + ttl (Optional[int]): Cache lifetime in seconds; defaults to + ``DEFAULT_TTL``. + + Returns: + Callable: Decorator that wraps the route handler. + + Notes: + The wrapped handler must accept a ``request: Request`` parameter (as + every openapi_first handler does). Responses that are already a + ``fastapi.Response`` (for example 204) are returned untouched and + never cached. When no ``http_cache_store`` is wired on + ``request.app.state.db_resources`` the decorator passes through to the + handler without caching. + """ + ttl = ttl if ttl is not None else DEFAULT_TTL + + def decorator(func: Callable) -> Callable: + @wraps(func) + async def wrapper(*args: Any, **kwargs: Any): + request = kwargs.get("request") + if request is None: + request = next((a for a in args if isinstance(a, Request)), None) + if request is None: + return await func(*args, **kwargs) + + db_resources = getattr(request.app.state, "db_resources", None) or {} + store: Optional[HttpCacheStore] = db_resources.get("http_cache_store") + if store is None: + return await func(*args, **kwargs) + + key = _cache_key(request, await store.generation(_resource_name(request))) + cached = await store.get(key) + if cached is not None: + return Response(content=cached, media_type="application/json") + + result = await func(*args, **kwargs) + if isinstance(result, Response): + return result + + payload = json.dumps( + jsonable_encoder(result), + separators=(",", ":"), + ensure_ascii=False, + ) + await store.set(key, payload, ttl) + return Response(content=payload, media_type="application/json") + + return wrapper + + return decorator diff --git a/standards/mcp/templates/app/pyproject.toml b/standards/mcp/templates/app/pyproject.toml index 48288f1..408c0e1 100644 --- a/standards/mcp/templates/app/pyproject.toml +++ b/standards/mcp/templates/app/pyproject.toml @@ -11,6 +11,7 @@ dependencies = [ "uvicorn[standard]", "pydantic>=2", "motor", + "redis", "python-dotenv", "pyyaml", "openapi-first", diff --git a/standards/mcp/templates/app/tests/integration/test_openapi_app_builds.py b/standards/mcp/templates/app/tests/integration/test_openapi_app_builds.py new file mode 100644 index 0000000..fe811c0 --- /dev/null +++ b/standards/mcp/templates/app/tests/integration/test_openapi_app_builds.py @@ -0,0 +1,44 @@ +import importlib + +import yaml + +import interfaces.openapi as routes_module + +from openapi_first.app import OpenAPIFirstApp + +_HTTP_METHODS = {"get", "post", "put", "patch", "delete", "head", "options"} + + +def _operation_ids(spec: dict) -> list[str]: + """Collect every declared operationId from an OpenAPI spec. + + Args: + spec (dict): Parsed OpenAPI document. + + Returns: + list[str]: Operation ids in declaration order. + """ + ids: list[str] = [] + for path_item in (spec.get("paths") or {}).values(): + for method, operation in path_item.items(): + if method.lower() in _HTTP_METHODS and isinstance(operation, dict): + if operation.get("operationId"): + ids.append(operation["operationId"]) + return ids + + +def test_openapi_app_builds(): + """Every operationId in the spec must resolve to a handler, and the app must build.""" + spec = yaml.safe_load(open("openapi.yaml", encoding="utf-8")) + module = importlib.import_module(routes_module.__name__) + + missing = [oid for oid in _operation_ids(spec) if not hasattr(module, oid)] + assert not missing, f"handlers missing for operationIds: {missing}" + + app = OpenAPIFirstApp( + openapi_path="openapi.yaml", + routes_module=module, + title="App API", + version="0.1.0", + ) + assert app is not None diff --git a/standards/src/lib/01_build_recipe.md b/standards/src/lib/01_build_recipe.md index 289de8a..c3734e1 100644 --- a/standards/src/lib/01_build_recipe.md +++ b/standards/src/lib/01_build_recipe.md @@ -3,10 +3,10 @@ ## Summary The ordered, deterministic checklist for generating a new application of the -standard shape. Every step is fixed; only the resource model (step 2) and the -pipeline family (step 7) need app-specific authoring. +standard shape. Every step is fixed; only the resource model (step 2), the +codegen pass (step 3), and the pipeline family (step 8) need app-specific work. -This page is served by the `standards` MCP as `doc://modules/build_recipe`. The +This page is served by the `standards` MCP as `docs://modules/build_recipe`. The `start_app` prompt on that server collects the two axes and hands off here; pull exact files with `get_app_component`. @@ -42,15 +42,15 @@ pip install --extra-index-url \ # Build a new application from the Aetos Standards blueprint You are coding a complete new backend application conforming to the Aetos -standard app blueprint. Execute every step in order. Only steps 2 and 3 use the +standard app blueprint. Execute every step in order. Only steps 2–4 use the app-specific details provided by the user; everything else is deterministic. ## Before writing any code Connect to the `standards` MCP server and read: -- `doc://nav` (the module list) -- `doc://modules/genericity_matrix` (Tier A library / Tier B template / Tier C authored) -- `doc://modules/layering_rules` (the dependency rules) +- `docs://nav` (the module list) +- `docs://modules/genericity_matrix` (Tier A library / Tier B template / Tier C authored) +- `docs://modules/layering_rules` (the dependency rules) Then: - call `find_library` with `db`, `di`, `routing`, `ingestion`, `pipeline`, `docs` @@ -61,48 +61,64 @@ Then: ## Steps -1. **Scaffold** — copy `standards/src/templates/app/` into `apps//`. - Keep the tree: `domain/`, `infra/`, `interfaces/`, `pipelines/`, `tests/`, - `scripts/`, plus `pyproject.toml`, `openapi.yaml`, `config.py`, `init.py`, - `main.py`, `worker.py`, `Dockerfile`, `docker-compose.yaml`, `.env.example`. +1. **Scaffold** — copy `standards/src/templates/app/` into `apps//` + (or pull the same tree via `get_app_template`). Keep the tree: `domain/`, + `infra/`, `interfaces/`, `pipelines/`, `tests/`, `scripts/`, plus + `pyproject.toml`, `openapi.yaml`, `config.py`, `init.py`, `main.py`, + `worker.py`, `Dockerfile`, `docker-compose.yaml`, `.env.example`. 2. **Author the spec (app-specific)** — write `openapi.yaml`: resources, schemas, `operationId`s, security, and extension metadata. This is the - contract and drives all routing. + contract and drives all routing. Every operation MUST have an `operationId`. -3. **Write each CRUD slice (app-specific)** — per resource create the four-file - slice: `models.py`, `ports/db.py`, `ports/service.py`, and optional - `converters.py`. Follow the CRUD template. +3. **Generate the scaffold (codegen)** — from the spec, generate the wire + models and the exact handler signatures: + ```bash + openapi-first models openapi.yaml -o _gen/models.py + openapi-first routes openapi.yaml -o _gen/routes \ + --use-models --models-module _gen.models --inject-request --package + ``` + `_gen/models.py` is the **wire contract** (Pydantic, one class per + `components.schemas` entry). `_gen/routes//routes.py` lists every + `operationId` with its exact parameter signature. Treat `_gen/` as generated + (never hand-edit it) — the canonical handlers live in `interfaces/`. -4. **Wire the composition root** — in `init.py`, build leaf repositories, then +4. **Write each CRUD slice (app-specific)** — per resource create the four-file + slice: `models.py` (domain model, **no `id`**), `ports/db.py` + (`class XDocument(BaseDocument, X)` — id comes from `BaseDocument`), + `ports/service.py`, and optional `converters.py`. Follow the CRUD template. + +5. **Wire the composition root** — in `init.py`, build leaf repositories, then dependent repositories, then services; add infra adapters (db, cache, stores, progress bus, fetchers). Expose `get_svc` overloads. -5. **Add routes** — create `interfaces//routes.py` thin handlers bound - to `operationId`s and re-export them in `interfaces/openapi.py`. Add the cache +6. **Add routes** — create `interfaces//routes.py` thin handlers whose + names match the `operationId`s (use the signatures from step 3 as your + checklist) and re-export them in `interfaces/openapi.py`. Add the cache decorator to read endpoints. -6. **Bootstrap app and worker** — confirm `main.py` runs the OpenAPI-first app +7. **Bootstrap app and worker** — confirm `main.py` runs the OpenAPI-first app with its lifespan. If the app has long-running jobs, adapt `worker.py` to poll the job collection, run the pipeline, and publish progress. -7. **Add pipeline families (only if data-driven)** — copy `pipelines/example/` +8. **Add pipeline families (only if data-driven)** — copy `pipelines/example/` to `pipelines//` with `abc.py`, `impl.py`, `pipeline.yaml`, `resources/`, and reuse `pipelines/shared/` (loader + registry). Register the root. Add fixtures and a parity test. -8. **Add infrastructure only where needed** — Tier A capability: use the +9. **Add infrastructure only where needed** — Tier A capability: use the library. Tier B capability: copy the template. Never hand-write a Tier A library. -9. **Tests** — integration tests with fakes at the HTTP edge; unit tests for - domain and pipeline logic; a config-consistency test for rule-driven - pipelines. +10. **Tests** — integration tests with fakes at the HTTP edge; unit tests for + domain and pipeline logic; a config-consistency test for rule-driven + pipelines. Include `tests/integration/test_openapi_app_builds.py` (imports + the app, asserts `import main` binds every operation). -10. **Scripts and seeds** — operator scripts under `scripts/` using +11. **Scripts and seeds** — operator scripts under `scripts/` using `init()` / `get_svc()` / `shutdown()`; run as `python -m scripts.`. -11. **Docs** — author GSDFC docstrings, generate `.pyi` stubs, mark the package +12. **Docs** — author GSDFC docstrings, generate `.pyi` stubs, mark the package `py.typed`, and build the docs. **Definition of done:** @@ -123,15 +139,39 @@ The steps map onto the layers as follows: |---------------------|-----------------------------------|---------------------------------------------| | 1 · Scaffold | whole `templates/app/` | — | | 2 · Spec | `openapi.yaml` | — | -| 3 · CRUD slices | `domain//` | spec, CRUD template | -| 4 · Wiring | `init.py` | every slice | -| 5 · Routes | `interfaces//` | services, openapi route registry | -| 6 · Bootstrap | `main.py`, `worker.py` | composition root | -| 7 · Pipelines | `pipelines//`, `shared/` | library pipeline engine | -| 8 · Infra | `infra/` | domain ports | -| 9 · Tests | `tests/` | app + fixtures | -| 10 · Scripts | `scripts/` | composition root | -| 11 · Docs | docstrings + docs build | code, Doc Forge | +| 3 · Codegen | `_gen/` (generated) | spec, openapi-first | +| 4 · CRUD slices | `domain//` | spec, CRUD template | +| 5 · Wiring | `init.py` | every slice | +| 6 · Routes | `interfaces//` | services, openapi route registry | +| 7 · Bootstrap | `main.py`, `worker.py` | composition root | +| 8 · Pipelines | `pipelines//`, `shared/` | library pipeline engine | +| 9 · Infra | `infra/` | domain ports | +| 10 · Tests | `tests/` | app + fixtures | +| 11 · Scripts | `scripts/` | composition root | +| 12 · Docs | docstrings + docs build | code, Doc Forge | + +--- + +## 🔧 The codegen fast path, in detail + +`openapi-first` is spec → code only (it never generates a spec). Use it to erase +the two mechanical parts of the build: + +| Generated artifact | Replaces | Notes | +|--------------------|----------|-------| +| `_gen/models.py` | hand-writing Pydantic wire models | one class per `components.schemas` entry | +| `_gen/routes//routes.py` | hand-typing handler signatures | names == `operationId`s; `request: Request` injected | + +Rules of the road: + +- **Wire models are not domain models.** `_gen/models.py` is the HTTP contract; + `domain//models.py` is your business shape. If the spec's schema has + a required `id: str`, keep it off the domain model and let the document get its + id from `BaseDocument`. +- **Resource names are sanitized.** `/fetch-requests` → `fetch_requests.py`. +- **Multipart bodies expand** to `UploadFile = File(...)` / `Form(...)` params. +- **`_gen/` is disposable.** Regenerate on every spec change; `git diff` shows + exactly what moved. --- diff --git a/standards/src/lib/04_spec_first_routing.md b/standards/src/lib/04_spec_first_routing.md index acb1a76..b7fca3c 100644 --- a/standards/src/lib/04_spec_first_routing.md +++ b/standards/src/lib/04_spec_first_routing.md @@ -70,8 +70,34 @@ async def create_item(request: Request, body: dict): - Security is declared once in the spec and enforced by the framework; handlers contain no auth code. +## Authless apps + +Auth is **deferred** in the standard: the same spec-first app runs with or +without it, because security lives entirely in the spec, never in handlers. +For an app that needs no auth yet, do **not** invent per-handler auth checks — +just omit the security keys: + +- Remove the top-level `security:` block. +- Remove `components.securitySchemes`. +- Do **not** add `security: []` to operations. An explicit empty array is a + per-operation *override* that means "public", and it only matters when a + global `security` block exists. Leaving every operation without a `security` + key is the cleanest authless spec. + +The handlers, services, and composition root are unchanged. When auth is needed +later, add the global `security` block and an introspecting dependency; only +handlers that must opt out get `security: []`. + ## Codegen -The `openapi_first` library can scaffold models and route stubs from the spec, -and ships app templates (CRUD app, model app, service). Prefer codegen over -hand-writing boilerplate. +`openapi_first` scaffolds models and route stubs from the spec (see the +[Build Recipe](01_build_recipe.md), step 3): + +```bash +openapi-first models openapi.yaml -o _gen/models.py +openapi-first routes openapi.yaml -o _gen/routes \ + --use-models --models-module _gen.models --inject-request --package +``` + +Prefer codegen over hand-writing boilerplate. The result is the wire contract +and a set of `operationId`-named signatures — not the canonical handlers. diff --git a/standards/src/lib/05_crud_template.md b/standards/src/lib/05_crud_template.md index da5a475..fec4cd6 100644 --- a/standards/src/lib/05_crud_template.md +++ b/standards/src/lib/05_crud_template.md @@ -18,9 +18,36 @@ domain// converters.py # (optional) transforms ``` +## The id rule (model vs. document) + +Two different objects, two different jobs: + +| Object | Lives in | Has `id`? | Purpose | +|---|---|---|---| +| **Domain model** | `domain//models.py` | **No** | business shape, framework-free | +| **Document** | `domain//ports/db.py` | **Yes** (from `BaseDocument`) | Mongo persistence shape | + +`BaseDocument` (mongo-ops) supplies `id: Optional[PyObjectId]` (aliased to +`_id`) plus `created_at` / `updated_at`. The domain model must **not** declare +`id`; Mongo owns it. + +> **Trap — generated wire models.** `openapi-first models` produces the HTTP +> *contract* (see the [Build Recipe](01_build_recipe.md), step 3). Response +> schemas frequently declare a required `id: str`, so a generated class is +> **not** a domain model. If you write +> `class ItemDocument(Item, BaseDocument)` and `Item` re-exports a required +> `id: str`, that field shadows `BaseDocument.id` and no Mongo `_id` can ever be +> set. Fix it one of three ways: +> +> 1. **Preferred:** keep the domain model id-free (hand-author it from the CRUD +> template), and keep the required `id` only on the wire model. +> 2. Order the bases so the document wins: +> `class ItemDocument(BaseDocument, Item)`. +> 3. Keep a separate document type and translate with `ports/converters.py`. + ## models.py -A framework-free Pydantic model describing the business shape. +A framework-free Pydantic model describing the business shape (no `id`). ```python class Item(BaseModel): @@ -34,7 +61,8 @@ The persistence model extends the business model and adds database concerns. The repository factory selects the right repository flavour. ```python -class ItemDocument(Item, BaseDocument): # BaseDocument adds id/created_at/... +# Item is id-free; BaseDocument adds id/created_at/updated_at. +class ItemDocument(Item, BaseDocument): pass def create_item_repo(*, owner_repo=None) -> ItemRepository: diff --git a/standards/src/lib/12_genericity_matrix.md b/standards/src/lib/12_genericity_matrix.md index 4e65b32..e051604 100644 --- a/standards/src/lib/12_genericity_matrix.md +++ b/standards/src/lib/12_genericity_matrix.md @@ -31,25 +31,37 @@ for its API. Writing a private copy of any of these is a defect. These are reusable internal components shipped **inside** the template — they are not independent libraries. Copy them as-is, then rename/populate only what is app-specific. They give every app the same wiring shape as the reference app. +Fetch them by kind with `get_app_component`, or the whole tree with +`get_app_template`. -| Component | Template location | Action | -|---|---|---| -| Layering & dependency rule | the package layout + import rules | keep | -| Composition root shape | `init.py` (`get_svc`/`get_db`, idempotency) | keep | -| Spec-first web edge | `interfaces/openapi.py`, thin handlers bound to `operationId` | keep | -| CRUD slice pattern | `domain//{models.py, ports/db.py, ports/service.py, converters.py}` | rename + fill | -| Generic CRUD service | `infra/adapters/service.py` (`CrudsServiceAdaptor`) | keep | -| Repository factory | `infra/db/repos/mongo.py` (`_repo_factory`) | keep | -| Worker poll + status loop | `worker.py` | keep | -| Progress bus + streaming | bus port + in-memory adapter + SSE endpoint | keep | -| Human-in-the-loop | durable pause + question document + resolve endpoint + resume map | keep | -| HTTP response cache | `cache_response` decorator + generation invalidation | keep | -| Key-value store | KV store port + Redis adapter + in-memory fallback | keep | -| LLM client pool | provider-select + retry pool scaffold | keep | -| Pipeline scaffold | `pipelines/shared/{ports,loader,registry}.py` + `pipelines//` | rename + fill | -| Operator scripts | `scripts/` (`init()` / `get_svc()` / `shutdown()`) | rename | -| Testing harness | integration fakes + fixture-parity pattern | keep | -| Project scaffolding | `pyproject.toml`, `Dockerfile`, `docker-compose.yaml`, `.env.example`, `openapi.yaml` | rename + fill | +| Component | Template location | Action | Shipped | +|---|---|---|---| +| Layering & dependency rule | the package layout + import rules | keep | ✅ | +| Composition root shape | `init.py` (`get_svc`/`get_db`, idempotency) | keep | ✅ | +| Spec-first web edge | `interfaces/openapi.py`, thin handlers bound to `operationId` | keep | ✅ | +| CRUD slice pattern | `domain//{models.py, ports/db.py, ports/service.py, converters.py}` | rename + fill | ✅ | +| Generic CRUD service | `infra/adapters/service.py` (`CrudsServiceAdaptor`) | keep | ✅ | +| Repository factory | `infra/db/repos/mongo.py` (`_repo_factory`) | keep | ✅ | +| Worker poll + status loop | `worker.py` | keep | ✅ | +| Progress bus | `infra/adapters/sse.py` (keyed in-memory bus) + an app SSE endpoint | keep | ✅ | +| HTTP response cache | `infra/http_cache.py` (`cache_response` + generation invalidation) | keep | ✅ | +| Key-value store | `infra/adapters/kv.py` (port + in-memory + Redis adaptor) | keep | ✅ | +| Pipeline scaffold | `pipelines/shared/{ports,loader,registry}.py` + `pipelines//` | rename + fill | ✅ | +| Operator scripts | `scripts/` (`init()` / `get_svc()` / `shutdown()`) | rename | ✅ | +| Testing harness | integration fakes + fixture-parity pattern | keep | ✅ | +| Project scaffolding | `pyproject.toml`, `Dockerfile`, `docker-compose.yaml`, `.env.example`, `openapi.yaml` | rename + fill | ✅ | + +## Tier B (patterns) — port from the reference app + +A few Tier B capabilities are real and reusable, but are **shaped by the app or +the provider**, so the template does not ship them verbatim — copying them +blindly would carry app-specific names. Treat them as patterns: lift the shape +from the reference app and rename per app. + +| Component | Reference location | Action | Shipped | +|---|---|---|---| +| Human-in-the-loop | durable pause + question document + resolve endpoint + resume map | port + reshape | ✗ | +| LLM client pool | provider-select + retry/rotation pool scaffold | port + reshape | ✗ | ## Tier C — App-specific (must be authored) diff --git a/standards/src/lib/13_library_registry.md b/standards/src/lib/13_library_registry.md index 3fc36a7..97ea183 100644 --- a/standards/src/lib/13_library_registry.md +++ b/standards/src/lib/13_library_registry.md @@ -8,7 +8,7 @@ is a defect. Each library also exposes its own documentation as an MCP server. An AI agent can read a library's full API by connecting to its MCP and reading -`doc://index`, `doc://nav`, and `doc://modules/{module}`. +`docs://index`, `docs://nav`, and `docs://modules/{module}`. --- @@ -48,8 +48,8 @@ endpoint. Query it before writing any infrastructure. ## Notes - **Hexa** is a full library with its own MCP server on `:8008`: read - `doc://index`, `doc://nav`, and `doc://modules/{module}` (e.g. - `doc://modules/hexa.container`) to discover the DI surface — `build(cls, *, + `docs://index`, `docs://nav`, and `docs://modules/{module}` (e.g. + `docs://modules/hexa.container`) to discover the DI surface — `build(cls, *, instances, config)` and the `PortNode` model. - **Auth Server** is deferred: it has no `pyproject.toml`/importable package and no MCP bundle. Do not reach for it yet; `py-jwt` covers token primitives. diff --git a/standards/src/templates/app/infra/adapters/kv.py b/standards/src/templates/app/infra/adapters/kv.py new file mode 100644 index 0000000..00e3ff9 --- /dev/null +++ b/standards/src/templates/app/infra/adapters/kv.py @@ -0,0 +1,147 @@ +"""# Summary + +Key-value store port with in-memory and Redis adaptors. + +--- + +Reserve infrastructure for caching small values by key (sessions, locks, +result blobs, rate counters). The in-memory adaptor needs no services and is a +safe default; the Redis adaptor is opt-in and imports ``redis`` lazily so it +never becomes a hard dependency. + +Example: + ```python + from infra.adapters.kv import InMemoryKV + + kv = InMemoryKV() + await kv.set("greeting", "hello", ttl=30) + assert await kv.get("greeting") == "hello" + ``` +""" +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Dict, List, Optional + + +class KVStore(ABC): + """Abstract key-value store port.""" + + @abstractmethod + async def get(self, key: str) -> Optional[str]: + """Return the value at ``key``, or ``None``.""" + + @abstractmethod + async def set(self, key: str, value: str, ttl: Optional[int] = None) -> None: + """Store ``value`` at ``key`` with an optional TTL in seconds.""" + + @abstractmethod + async def delete(self, key: str) -> None: + """Delete ``key`` if present.""" + + @abstractmethod + async def scan(self, pattern: str) -> List[str]: + """Return keys matching a glob ``pattern``.""" + + async def clear(self, pattern: str = "*") -> None: + """Delete all keys matching ``pattern``. + + Args: + pattern (str): Glob-style key pattern. + """ + for key in await self.scan(pattern): + await self.delete(key) + + async def close(self) -> None: + """Release any underlying resources. Default is a no-op.""" + + +class InMemoryKV(KVStore): + """Process-local key-value store used as a dependency-free fallback. + + Attributes: + _data (Dict[str, str]): In-memory value map. + _expiry (Dict[str, float]): Absolute expiry timestamps by key; ``0.0`` + means "no expiry". + """ + + def __init__(self) -> None: + """Initialize an empty store with no imports or connections.""" + self._data: Dict[str, str] = {} + self._expiry: Dict[str, float] = {} + + def _expired(self, key: str) -> bool: + import time + + deadline = self._expiry.get(key, 0.0) + if deadline and deadline < time.time(): + self._data.pop(key, None) + self._expiry.pop(key, None) + return True + return False + + async def get(self, key: str) -> Optional[str]: + """Return the value at ``key``, or ``None`` when absent/expired.""" + if self._expired(key): + return None + return self._data.get(key) + + async def set(self, key: str, value: str, ttl: Optional[int] = None) -> None: + """Store ``value`` at ``key`` with an optional TTL in seconds.""" + import time + + self._data[key] = value + self._expiry[key] = (time.time() + ttl) if ttl else 0.0 + + async def delete(self, key: str) -> None: + """Delete ``key`` if present.""" + self._data.pop(key, None) + self._expiry.pop(key, None) + + async def scan(self, pattern: str) -> List[str]: + """Return live keys matching a glob ``pattern``.""" + import fnmatch + + return [k for k in list(self._data) if fnmatch.fnmatch(k, pattern) and not self._expired(k)] + + +class RedisKV(KVStore): + """Redis-backed key-value store. + + Attributes: + _redis: Asynchronous Redis client (imported lazily). + """ + + def __init__(self, url: str = "redis://localhost:6379/0", decode_responses: bool = True): + """Initialize the store with a Redis connection. + + Args: + url (str): Redis connection URL. + decode_responses (bool): Whether Redis returns decoded strings. + + Raises: + ImportError: When the optional ``redis`` package is not installed. + """ + from redis.asyncio import Redis + + self._redis = Redis.from_url(url, decode_responses=decode_responses) + + async def get(self, key: str) -> Optional[str]: + """Return the value at ``key``, or ``None``.""" + return await self._redis.get(key) + + async def set(self, key: str, value: str, ttl: Optional[int] = None) -> None: + """Store ``value`` at ``key`` with an optional TTL in seconds.""" + await self._redis.set(key, value, ex=ttl) + + async def delete(self, key: str) -> None: + """Delete ``key`` if present.""" + await self._redis.delete(key) + + async def scan(self, pattern: str) -> List[str]: + """Return keys matching a glob ``pattern``.""" + return [key async for key in self._redis.scan_iter(match=pattern)] + + async def close(self) -> None: + """Close the underlying Redis connection.""" + await self._redis.aclose() diff --git a/standards/src/templates/app/infra/adapters/sse.py b/standards/src/templates/app/infra/adapters/sse.py new file mode 100644 index 0000000..70c6f31 --- /dev/null +++ b/standards/src/templates/app/infra/adapters/sse.py @@ -0,0 +1,54 @@ +"""# Summary + +SSEInMemoryBus — in-memory event bus for SSE progress events. + +--- + +Buffer published progress events so they can be replayed (and streamed) per +correlation key, such as a job, upload, or fetch-request id. +""" +from typing import Generic, List, TypeVar + +T = TypeVar("T") + + +class SSEInMemoryBus(Generic[T]): + """Buffer SSE progress events in memory, keyed by an event attribute. + + Attributes: + _events (List[T]): Events published since the last clear. + _key_attr (str): Event attribute used to correlate a stream. + """ + + def __init__(self, key_attr: str = "id"): + """Initialize an empty event buffer. + + Args: + key_attr (str): Attribute name on each event holding the stream + correlation key (for example ``"job_id"``). + """ + self._events: List[T] = [] + self._key_attr = key_attr + + async def publish(self, event: T) -> None: + """Append an event to the buffer. + + Args: + event (T): Event to append. + """ + self._events.append(event) + + def get_events(self, key: str) -> List[T]: + """Return buffered events for a correlation key. + + Args: + key (str): Value matched against the configured ``key_attr``. + + Returns: + List[T]: Events whose ``key_attr`` equals ``key``. + """ + return [e for e in self._events if getattr(e, self._key_attr, None) == key] + + def clear(self) -> None: + """Remove all buffered events.""" + self._events.clear() diff --git a/standards/src/templates/app/infra/http_cache.py b/standards/src/templates/app/infra/http_cache.py new file mode 100644 index 0000000..8debf88 --- /dev/null +++ b/standards/src/templates/app/infra/http_cache.py @@ -0,0 +1,229 @@ +"""# Summary + +Domain-agnostic HTTP response cache. + +--- + +A thin Redis-backed response cache that wraps route handlers as a decorator. + +Example: + ```python + from infra.http_cache import cache_response + + @cache_response(ttl=60) + async def list_items(request, skip: int = 0, limit: int = 100): + ... + ``` + +Notes: + The cache operates directly on the endpoint and is independent of any + repository-level cache. Cached responses expire via TTL. When no + ``http_cache_store`` is wired on ``request.app.state.db_resources`` the + decorator is a no-op pass-through, so applying/removing it is safe. +""" +import hashlib +import json +from functools import wraps +from typing import Any, Callable, Optional + +from fastapi import Request, Response +from fastapi.encoders import jsonable_encoder + +CACHE_PREFIX = "apicache:" +NS_PREFIX = CACHE_PREFIX + "ns:" +DEFAULT_TTL = 60 + + +class HttpCacheStore: + """Store string responses in Redis with TTL under the ``apicache:`` key space. + + Attributes: + _redis (Redis): Asynchronous Redis client bound to the configured URL. + """ + + def __init__(self, url: str = "redis://localhost:6379/0", decode_responses: bool = True): + """Initialize the store with a Redis connection. + + Args: + url (str): Redis connection URL. + decode_responses (bool): Whether Redis returns decoded strings. + + Raises: + ImportError: When the optional ``redis`` package is not installed. + """ + from redis.asyncio import Redis + + self._redis = Redis.from_url(url, decode_responses=decode_responses) + + async def get(self, key: str) -> Optional[str]: + """Read the value stored at ``key``. + + Args: + key (str): Cache key to read. + + Returns: + Optional[str]: Cached value, or ``None`` when absent. + """ + return await self._redis.get(CACHE_PREFIX + key) + + async def set(self, key: str, value: str, ttl: int) -> None: + """Store ``value`` at ``key`` with a TTL. + + Args: + key (str): Cache key to write. + value (str): Serialized value to persist. + ttl (int): Lifetime in seconds. + """ + await self._redis.set(CACHE_PREFIX + key, value, ex=ttl) + + async def delete(self, key: str) -> None: + """Delete the value stored at ``key``. + + Args: + key (str): Cache key to remove. + """ + await self._redis.delete(CACHE_PREFIX + key) + + async def generation(self, resource: str) -> str: + """Return the current generation counter for a resource namespace. + + Args: + resource (str): Resource namespace to read. + + Returns: + str: Generation counter, or ``"0"`` when unset. + """ + value = await self._redis.get(NS_PREFIX + resource) + return value if value is not None else "0" + + async def invalidate(self, *resources: str) -> None: + """Invalidate all cached responses for the given resource namespaces. + + Args: + *resources (str): Resource namespaces to invalidate. + + Notes: + Bumps each namespace's generation counter so existing entries become + unreachable immediately; Redis reclaims them at TTL expiry. + """ + if not resources: + return + pipe = self._redis.pipeline(transaction=False) + for resource in resources: + pipe.incr(NS_PREFIX + resource) + await pipe.execute() + + async def close(self) -> None: + """Close the underlying Redis connection.""" + await self._redis.aclose() + + +def _user_scope(request: Request) -> str: + """Build a stable per-user cache scope. + + Args: + request (Request): Incoming HTTP request. + + Returns: + str: Scoped identifier, or ``""`` for auth-free endpoints. + """ + user = getattr(request.state, "user", None) + if isinstance(user, dict): + for field in ("username", "id", "email"): + if user.get(field): + return str(user[field]) + token = getattr(request.state, "token", None) + if token: + return hashlib.sha256(str(token).encode()).hexdigest()[:16] + return "" + + +def _resource_name(request: Request) -> str: + """Return the first path segment identifying the resource. + + Args: + request (Request): Incoming HTTP request. + + Returns: + str: First path segment (for example ``"items"``), or ``"root"`` when + the path is empty. + """ + return request.url.path.strip("/").split("/", 1)[0] or "root" + + +def _cache_key(request: Request, generation: str = "0") -> str: + """Compute a stable cache key for a request. + + Args: + request (Request): Incoming HTTP request. + generation (str): Resource generation counter guarding stale entries. + + Returns: + str: SHA-256 digest of the request method, path, query and path + parameters, user scope, and generation. + """ + parts = [ + request.method, + request.url.path, + "&".join(f"{k}={v}" for k, v in sorted(request.query_params.items())), + "&".join(f"{k}={v}" for k, v in sorted(request.path_params.items())), + _user_scope(request), + generation, + ] + return hashlib.sha256("|".join(parts).encode()).hexdigest() + + +def cache_response(ttl: Optional[int] = None) -> Callable: + """Decorate a GET-style route handler to cache its JSON response. + + Args: + ttl (Optional[int]): Cache lifetime in seconds; defaults to + ``DEFAULT_TTL``. + + Returns: + Callable: Decorator that wraps the route handler. + + Notes: + The wrapped handler must accept a ``request: Request`` parameter (as + every openapi_first handler does). Responses that are already a + ``fastapi.Response`` (for example 204) are returned untouched and + never cached. When no ``http_cache_store`` is wired on + ``request.app.state.db_resources`` the decorator passes through to the + handler without caching. + """ + ttl = ttl if ttl is not None else DEFAULT_TTL + + def decorator(func: Callable) -> Callable: + @wraps(func) + async def wrapper(*args: Any, **kwargs: Any): + request = kwargs.get("request") + if request is None: + request = next((a for a in args if isinstance(a, Request)), None) + if request is None: + return await func(*args, **kwargs) + + db_resources = getattr(request.app.state, "db_resources", None) or {} + store: Optional[HttpCacheStore] = db_resources.get("http_cache_store") + if store is None: + return await func(*args, **kwargs) + + key = _cache_key(request, await store.generation(_resource_name(request))) + cached = await store.get(key) + if cached is not None: + return Response(content=cached, media_type="application/json") + + result = await func(*args, **kwargs) + if isinstance(result, Response): + return result + + payload = json.dumps( + jsonable_encoder(result), + separators=(",", ":"), + ensure_ascii=False, + ) + await store.set(key, payload, ttl) + return Response(content=payload, media_type="application/json") + + return wrapper + + return decorator diff --git a/standards/src/templates/app/pyproject.toml b/standards/src/templates/app/pyproject.toml index 48288f1..408c0e1 100644 --- a/standards/src/templates/app/pyproject.toml +++ b/standards/src/templates/app/pyproject.toml @@ -11,6 +11,7 @@ dependencies = [ "uvicorn[standard]", "pydantic>=2", "motor", + "redis", "python-dotenv", "pyyaml", "openapi-first", diff --git a/standards/src/templates/app/tests/integration/test_openapi_app_builds.py b/standards/src/templates/app/tests/integration/test_openapi_app_builds.py new file mode 100644 index 0000000..fe811c0 --- /dev/null +++ b/standards/src/templates/app/tests/integration/test_openapi_app_builds.py @@ -0,0 +1,44 @@ +import importlib + +import yaml + +import interfaces.openapi as routes_module + +from openapi_first.app import OpenAPIFirstApp + +_HTTP_METHODS = {"get", "post", "put", "patch", "delete", "head", "options"} + + +def _operation_ids(spec: dict) -> list[str]: + """Collect every declared operationId from an OpenAPI spec. + + Args: + spec (dict): Parsed OpenAPI document. + + Returns: + list[str]: Operation ids in declaration order. + """ + ids: list[str] = [] + for path_item in (spec.get("paths") or {}).values(): + for method, operation in path_item.items(): + if method.lower() in _HTTP_METHODS and isinstance(operation, dict): + if operation.get("operationId"): + ids.append(operation["operationId"]) + return ids + + +def test_openapi_app_builds(): + """Every operationId in the spec must resolve to a handler, and the app must build.""" + spec = yaml.safe_load(open("openapi.yaml", encoding="utf-8")) + module = importlib.import_module(routes_module.__name__) + + missing = [oid for oid in _operation_ids(spec) if not hasattr(module, oid)] + assert not missing, f"handlers missing for operationIds: {missing}" + + app = OpenAPIFirstApp( + openapi_path="openapi.yaml", + routes_module=module, + title="App API", + version="0.1.0", + ) + assert app is not None diff --git a/standards/src/wiki/03_start_an_app.md b/standards/src/wiki/03_start_an_app.md index 02e9bb3..151ef87 100644 --- a/standards/src/wiki/03_start_an_app.md +++ b/standards/src/wiki/03_start_an_app.md @@ -7,7 +7,7 @@ copying Tier B template components. > **Doc model:** this page is the human entry point. The deterministic, > machine-facing checklist lives in the reference page -> [Build Recipe](../lib/01_build_recipe/), served as `doc://modules/build_recipe`. +> [Build Recipe](../lib/01_build_recipe/), served as `docs://modules/build_recipe`. --- @@ -20,7 +20,9 @@ Gather these before starting — the `start_app` MCP prompt asks for them: `openapi.yaml` if you already have it. 3. **Axis 2 — pipelines.** Only if an input needs a multi-step transformation: the families and their input formats (or "none"). -4. **Auth needed now?** Default no; auth is deferred (`py-jwt` only). +4. **Auth needed now?** Default **no**. Auth is deferred (`py-jwt` primitives + only), and an authless app is just a spec with no `security` keys — see + [Spec-First Routing → Authless apps](../lib/04_spec_first_routing/#authless-apps). 5. **Integrations.** Mongo, ingestion formats, email, LLM provider. If you can state axes 1 and 2, the rest is mechanical. @@ -31,13 +33,18 @@ If you can state axes 1 and 2, the rest is mechanical. From an MCP client connected to the `standards` server: -1. Invoke the **`start_app`** prompt with no arguments to get the intake - questions. -2. Answer them, then invoke - `start_app(app_name=..., resources=..., pipelines=..., auth=...)`. -3. The prompt hands off to `doc://modules/build_recipe`; the agent pulls exact - files with `get_app_component(kind, resource=..., family=...)` and authors - only the app-specific parts. +1. Invoke the **`intake`** prompt (or `start_app` with no arguments) to get the + intake questions. +2. Answer them, then invoke **`build`** + (`build(app_name=..., resources=..., pipelines=..., auth=...)`), or use + **`start_app`** to intake-and-build in one call. +3. The prompt hands off to `docs://modules/build_recipe`; the agent scaffolds + with `get_app_template()` and pulls exact files with + `get_app_component(kind, resource=..., family=...)`, authoring only the + app-specific parts. +4. To validate the standard without touching a real app, invoke **`trial`** + (`trial(app_name=..., build_path=...)`) to build into a scratch directory, + prove it builds, and report findings. Manual alternative — copy the skeleton and follow the reference recipe: diff --git a/standards/src/wiki/index.md b/standards/src/wiki/index.md index 7ba6f49..ee9bb80 100644 --- a/standards/src/wiki/index.md +++ b/standards/src/wiki/index.md @@ -55,8 +55,8 @@ Registry](../lib/13_library_registry/). ## 🔗 Related Resources -- **MCP bundle** — `standards` server on `:8009`: read `doc://index`, - `doc://nav`, `doc://modules/{module}`; call `find_library(capability)` +- **MCP bundle** — `standards` server on `:8009`: read `docs://index`, + `docs://nav`, `docs://modules/{module}`; call `find_library(capability)` - **Library registry** — the catalog of internal libraries behind every app (`libraries.json`, served as `registry://libraries`) - **Template** — the runnable skeleton at `standards/src/templates/app/` diff --git a/standards/wiki/03_start_an_app/index.html b/standards/wiki/03_start_an_app/index.html index 2b2de20..2a9c4fe 100644 --- a/standards/wiki/03_start_an_app/index.html +++ b/standards/wiki/03_start_an_app/index.html @@ -538,7 +538,7 @@ copying Tier B template components.

        Doc model: this page is the human entry point. The deterministic, machine-facing checklist lives in the reference page -Build Recipe, served as doc://modules/build_recipe.

        +Build Recipe, served as docs://modules/build_recipe.


        🧾 What the agent needs from you

        @@ -549,7 +549,9 @@ machine-facing checklist lives in the reference page openapi.yaml if you already have it.
      • Axis 2 — pipelines. Only if an input needs a multi-step transformation: the families and their input formats (or "none").
      • -
      • Auth needed now? Default no; auth is deferred (py-jwt only).
      • +
      • Auth needed now? Default no. Auth is deferred (py-jwt primitives + only), and an authless app is just a spec with no security keys — see + Spec-First Routing → Authless apps.
      • Integrations. Mongo, ingestion formats, email, LLM provider.
      • If you can state axes 1 and 2, the rest is mechanical.

        @@ -557,13 +559,18 @@ machine-facing checklist lives in the reference page

        ▶️ How to start

        From an MCP client connected to the standards server:

          -
        1. Invoke the start_app prompt with no arguments to get the intake - questions.
        2. -
        3. Answer them, then invoke - start_app(app_name=..., resources=..., pipelines=..., auth=...).
        4. -
        5. The prompt hands off to doc://modules/build_recipe; the agent pulls exact - files with get_app_component(kind, resource=..., family=...) and authors - only the app-specific parts.
        6. +
        7. Invoke the intake prompt (or start_app with no arguments) to get the + intake questions.
        8. +
        9. Answer them, then invoke build + (build(app_name=..., resources=..., pipelines=..., auth=...)), or use + start_app to intake-and-build in one call.
        10. +
        11. The prompt hands off to docs://modules/build_recipe; the agent scaffolds + with get_app_template() and pulls exact files with + get_app_component(kind, resource=..., family=...), authoring only the + app-specific parts.
        12. +
        13. To validate the standard without touching a real app, invoke trial + (trial(app_name=..., build_path=...)) to build into a scratch directory, + prove it builds, and report findings.

        Manual alternative — copy the skeleton and follow the reference recipe:

      cp -r <docs>/standards/src/templates/app apps/<your-app>
      diff --git a/standards/wiki/index.html b/standards/wiki/index.html
      index 36fd20c..c9d561b 100644
      --- a/standards/wiki/index.html
      +++ b/standards/wiki/index.html
      @@ -588,8 +588,8 @@ Registry.


        -
      • MCP bundle — standards server on :8009: read doc://index, - doc://nav, doc://modules/{module}; call find_library(capability)
      • +
      • MCP bundle — standards server on :8009: read docs://index, + docs://nav, docs://modules/{module}; call find_library(capability)
      • Library registry — the catalog of internal libraries behind every app (libraries.json, served as registry://libraries)
      • Template — the runnable skeleton at standards/src/templates/app/
      • diff --git a/standards/wiki/search/search_index.json b/standards/wiki/search/search_index.json index 3d999d6..0e2f493 100644 --- a/standards/wiki/search/search_index.json +++ b/standards/wiki/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"\ud83e\udde9 Standards \u2014 the Aetos App Blueprint","text":"

        One reusable architecture for every backend application in the Aetos ecosystem: a spec-first HTTP edge, a framework-free domain core, a pluggable infrastructure layer, and a single composition root.

        Fill in two axes only and a new app is done: your resource model, and (if needed) your pipeline family. The blueprint itself never names a business domain \u2014 expenses, health metrics, inventory, and analytics are all the same shape underneath.

        Doc model: this wiki is written for humans \u2014 how-to guides, the build recipe, and quality gates. The enforceable rules (layering, composition root, CRUD template, pipeline scaffold) and the library registry live in the reference pages, mirrored by the machine-readable standards MCP bundle. Query find_library before writing any infrastructure.

        "},{"location":"#key-features","title":"\ud83d\ude80 Key Features","text":"
        • \ud83e\udded Spec-first routing \u2014 the OpenAPI document, not decorator routers, is the source of truth (operationId lookup)
        • \ud83c\udfd7\ufe0f One-way layering \u2014 interfaces \u2192 domain \u2192 infra, enforced
        • \ud83d\udd0c Single composition root \u2014 every dependency is wired in exactly one place
        • \ud83e\udde9 Generic, not app-specific \u2014 reusable rules versus two authoring axes
        • \ud83d\udcda Library-backed \u2014 DB, DI, pipelines, ingestion, auth, and docs come from internal libraries, never hand-written
        • \ud83d\udea6 Explicit state machines \u2014 durable job statuses, progress, and human-in-the-loop pauses
        "},{"location":"#documentation-structure","title":"\ud83d\udcc1 Documentation Structure","text":"Section Description Overview Philosophy, the one-way dependency rule, architecture App Components The component model and how the pieces wire together Start an App The entry point: gather the two axes, then build Testing & Quality Test layout and acceptance gates

        Reference (separate site, mirrors the MCP bundle): the enforceable rules \u2014 Build Recipe, Layering, Composition Root, Spec-First Routing, CRUD Template, Pipeline Scaffold, Worker & Progress, Human-in-the-Loop, Infra Adapters, Genericity Matrix, Library Registry.

        "},{"location":"#related-resources","title":"\ud83d\udd17 Related Resources","text":"
        • MCP bundle \u2014 standards server on :8009: read doc://index, doc://nav, doc://modules/{module}; call find_library(capability)
        • Library registry \u2014 the catalog of internal libraries behind every app (libraries.json, served as registry://libraries)
        • Template \u2014 the runnable skeleton at standards/src/templates/app/
        • Start a build \u2014 invoke the start_app prompt on the standards server, or read the Start an App guide

        \u00a9 Aetoskia Internal \u2014 standards

        "},{"location":"01_overview/","title":"Overview","text":"

        The blueprint is a hexagonal (ports & adapters) application with a spec-first web edge and a single composition root. It favors explicit wiring over implicit magic and existing internal libraries over hand-written infrastructure. Start here to build the mental model; the enforceable details live in the reference pages.

        "},{"location":"01_overview/#architecture","title":"\ud83c\udfd7\ufe0f Architecture","text":"
        flowchart LR\n    subgraph EDGE[\"interfaces/ \u2014 HTTP edge\"]\n        OAS[\"openapi.yaml<br/>operationId contract\"]\n        RT[\"<resource>/routes.py<br/>thin handlers\"]\n    end\n\n    subgraph CORE[\"domain/ \u2014 business core\"]\n        MD[\"models\"]\n        PT[\"ports (adapter binding seam)\"]\n        SV[\"services\"]\n    end\n\n    subgraph INFRA[\"infra/ \u2014 adapters\"]\n        RP[\"mongo repos (mongo-ops)\"]\n        AD[\"cache \u00b7 stores \u00b7 llm \u00b7 mail\"]\n        DB[(\"MongoDB\")]\n    end\n\n    CR[\"init.py<br/>composition root\"]\n\n    OAS --> RT\n    RT --> SV\n    SV --> PT\n    SV --> RP\n    PT -. depends on .- RP\n    CR --- RT\n    CR --- SV\n    CR --- RP\n    RP --> DB\n    AD --- CR

        The rule holds at every edge: code in an outer ring may depend on a ring closer to the core, never the reverse. domain/ never imports a web framework; only its ports/ seam reaches into infra/ to bind adapters.

        Legend \u2014 solid arrows are compile-time calls; dotted arrows are the port/adapter binding, performed by the composition root at startup.

        "},{"location":"01_overview/#the-one-way-dependency-rule","title":"\ud83d\udd04 The one-way dependency rule","text":"Ring May import Never imports interfaces/ domain, framework glue (glue only) business logic, infra directly domain/ its own models + its ports/ seam (which binds infra/libraries) web frameworks, interfaces/ infra/ domain ports, drivers, libraries \u2014 init.py everything (the wiring point) \u2014"},{"location":"01_overview/#core-principles","title":"\ud83e\udded Core principles","text":"
        1. One-way dependencies. interfaces -> domain -> infra is allowed; the reverse is forbidden.
        2. Ports in the domain, adapters in infra. A service depends on a port; the port's factory binds the concrete adapter, and the composition root calls those factories.
        3. One composition root. Nothing constructs its own dependencies. All wiring lives in one module (init.py) exposed as a flat resource registry.
        4. Spec-first. The HTTP contract is declared in openapi.yaml; route handlers are looked up by operationId. There are no decorator routers.
        5. Reuse the libraries. Orchestration, DI, Mongo repositories, ingestion, auth, and docs come from internal libraries \u2014 see the Library Registry.
        6. Explicit state machines. Long-running work moves through explicit, durable statuses and can pause for human input.
        "},{"location":"01_overview/#runtime-processes","title":"\u2699\ufe0f Runtime processes","text":"

        A standard app may run as up to three processes that share the same composition root:

        Process Entry point Responsibility API main.py serve HTTP; start/stop resources via lifespan Worker worker.py poll durable jobs, run pipelines, publish progress Scripts python -m scripts.<name> one-shot operator tasks"},{"location":"01_overview/#read-next","title":"\u27a1\ufe0f Read Next","text":"
        • 02 \u2013 App Components \u00b7 03 \u2013 Start an App
        "},{"location":"02_components/","title":"App Components","text":"

        What ships in every standard app, and how the pieces relate. This is the blueprint's component model \u2014 templates/app/ is a runnable instance of it.

        "},{"location":"02_components/#component-model","title":"\ud83d\uddc2\ufe0f Component model","text":"

        The blueprint has two orthogonal views: runtime flow (who calls whom) and composition-root wiring (who constructs whom). Keeping them separate makes the layering visible instead of hiding it behind a hub.

        "},{"location":"02_components/#runtime-flow","title":"\ud83d\udd00 Runtime flow","text":"

        Who calls whom at request and job time:

        %%{init: {\"flowchart\": {\"nodeSpacing\": 50, \"rankSpacing\": 80}}}%%\nflowchart TB\n    subgraph REQ[\"Request path \u2014 API process\"]\n        direction LR\n        CL([\"Client<br/>operationId\"]) --> RT[\"interfaces/&lt;resource&gt;/routes.py\"]\n        RT --> SVC[\"&lt;resource&gt;_svc\"]\n        SVC --> REPO[\"&lt;resource&gt;_repo\"]\n    end\n\n    subgraph WRK[\"Background path \u2014 worker process\"]\n        direction LR\n        W[\"worker.py\"] --> JSVC[\"job_svc\"]\n        W --> PIPE[\"pipelines/&lt;family&gt;\"]\n        PIPE --> ISVC[\"item_svc\"]\n        JSVC --> JREPO[\"job_repo\"]\n        ISVC --> IREPO[\"item_repo\"]\n    end\n\n    subgraph INFRA[\"infra/ \u2014 adapters\"]\n        MONGO[(\"MongoDB / mongo_ops\")]\n    end\n\n    REPO --> MONGO\n    JREPO --> MONGO\n    IREPO --> MONGO

        Legend \u2014 solid arrows are runtime calls. Nothing is self-constructed.

        "},{"location":"02_components/#composition-root-wiring","title":"\ud83e\uddf5 Composition-root wiring","text":"

        Who constructs what at startup:

        %%{init: {\"flowchart\": {\"nodeSpacing\": 50, \"rankSpacing\": 80}}}%%\nflowchart TB\n    INIT[\"init.py \u2014 composition root\"]\n    SETUP[\"infra/db/setup.py\"]\n    FACT[\"domain/*/ports/*<br/>factory seams\"]\n    REPOS[\"repos<br/>infra/db/repos/mongo.py\"]\n    SVCES[\"services<br/>CrudsServiceAdaptor\"]\n    STATE[\"app.state.db_resources\"]\n    HEXA[\"hexa.build\"]\n    RT[\"routes.py\"]\n    WK[\"worker.py\"]\n\n    INIT --> SETUP\n    INIT --> FACT\n    FACT --> REPOS\n    FACT --> SVCES\n    REPOS --> SVCES\n    HEXA -.-> SVCES\n    INIT --> STATE\n    STATE --> RT\n    INIT --> WK

        Legend \u2014 init.py is the only place that imports concrete adapters. The ports/ factory seams bind infra; the API reads resources from app.state.db_resources, the worker calls init() directly.

        "},{"location":"02_components/#component-roles","title":"\ud83d\udce6 Component roles","text":""},{"location":"02_components/#1-interfaces-the-edge","title":"1. interfaces/ \u2014 the edge","text":"

        Thin handlers bound to openapi.yaml operationIds. They parse the request, call a service, and shape the response. No business rules, no I/O.

        • interfaces/openapi.py re-exports every route module so the OpenAPI-first app can resolve handlers.
        • Routes may mount the cache decorator on read endpoints.
        "},{"location":"02_components/#2-domain-the-core","title":"2. domain/ \u2014 the core","text":"

        Models, ports, and services (use cases). No framework imports. The ports/ package is the one seam allowed to reach into infra/: each port module declares the document shape and a factory that binds the adapter.

        • <aggregate>/models.py \u2014 dataclasses, enums, document shapes.
        • <aggregate>/ports/db.py \u2014 document model + create_<aggregate>_repo().
        • <aggregate>/ports/service.py \u2014 service + get_<aggregate>_service(repo).
        • Services compose ports and enforce the business rules; the port factories bind the implementations, and the composition root calls them.
        "},{"location":"02_components/#3-infra-the-adapters","title":"3. infra/ \u2014 the adapters","text":"

        Concrete implementations of the domain ports plus cross-cutting infrastructure:

        • db/ \u2014 mongo-ops repositories and the connection lifecycle.
        • adapters/ \u2014 cache store, progress bus, storages, fetchers (library-backed).
        • Anything else the domain needs, implemented against its ports.
        "},{"location":"02_components/#4-pipelines-data-driven-families-optional","title":"4. pipelines/ \u2014 data-driven families (optional)","text":"

        Extraction and enrichment families, each a deterministic step chain (abc.py / impl.py / pipeline.yaml / resources/). Roots are registered in the pattern registry and driven either by a worker job or a CLI.

        "},{"location":"02_components/#5-workerpy-the-job-worker-optional","title":"5. worker.py \u2014 the job worker (optional)","text":"

        A separate process that polls durable job collections, runs pipeline roots, and publishes progress to the progress bus. It shares the same composition root as the API.

        "},{"location":"02_components/#6-initpy-the-composition-root","title":"6. init.py \u2014 the composition root","text":"

        The only place anything is constructed: leaf repositories \u2192 dependent repositories \u2192 services \u2192 adapters. Exposes get_svc overloads and starts / stops resources on the app lifespan.

        "},{"location":"02_components/#how-the-request-flows","title":"\ud83d\udd17 How the request flows","text":"
        sequenceDiagram\n    participant C as Client\n    participant R as route (interfaces)\n    participant S as service (port factory / adapter)\n    participant P as repo (infra)\n    participant DB as MongoDB\n\n    C->>R: POST /items (operationId)\n    R->>S: svc.create(payload)\n    S->>P: repo.insert(doc)\n    P->>DB: insert\n    DB-->>P: ack\n    P-->>S: doc\n    S-->>R: result\n    R-->>C: 200 JSON
        "},{"location":"02_components/#related","title":"Related","text":"
        • 01 \u2013 Overview \u00b7 03 \u2013 Start an App \u00b7 04 \u2013 Testing & Quality
        "},{"location":"03_start_an_app/","title":"\ud83d\ude80 Start an App","text":"

        This is the entry point for building a new application from the blueprint. The goal is one deterministic pass: you supply only the two axes, and the agent does the rest by reading the standards MCP, importing Tier A libraries, and copying Tier B template components.

        Doc model: this page is the human entry point. The deterministic, machine-facing checklist lives in the reference page Build Recipe, served as doc://modules/build_recipe.

        "},{"location":"03_start_an_app/#what-the-agent-needs-from-you","title":"\ud83e\uddfe What the agent needs from you","text":"

        Gather these before starting \u2014 the start_app MCP prompt asks for them:

        1. App name + one-line purpose.
        2. Axis 1 \u2014 resources. Each resource, its fields, and relationships. Paste openapi.yaml if you already have it.
        3. Axis 2 \u2014 pipelines. Only if an input needs a multi-step transformation: the families and their input formats (or \"none\").
        4. Auth needed now? Default no; auth is deferred (py-jwt only).
        5. Integrations. Mongo, ingestion formats, email, LLM provider.

        If you can state axes 1 and 2, the rest is mechanical.

        "},{"location":"03_start_an_app/#how-to-start","title":"\u25b6\ufe0f How to start","text":"

        From an MCP client connected to the standards server:

        1. Invoke the start_app prompt with no arguments to get the intake questions.
        2. Answer them, then invoke start_app(app_name=..., resources=..., pipelines=..., auth=...).
        3. The prompt hands off to doc://modules/build_recipe; the agent pulls exact files with get_app_component(kind, resource=..., family=...) and authors only the app-specific parts.

        Manual alternative \u2014 copy the skeleton and follow the reference recipe:

        cp -r <docs>/standards/src/templates/app apps/<your-app>\n
        "},{"location":"03_start_an_app/#reference","title":"\ud83d\udcda Reference","text":"
        • Build Recipe \u2014 the deterministic checklist
        • Genericity Matrix \u2014 Tier A / B / C
        • Library Registry \u2014 the Tier A catalog
        • App Components \u2014 the component model
        "},{"location":"03_start_an_app/#related","title":"Related","text":"
        • 01 \u2013 Overview \u00b7 02 \u2013 App Components \u00b7 04 \u2013 Testing & Quality
        "},{"location":"04_development/01_testing/","title":"Testing & Quality Gates","text":"

        Two suites run under pytest with asyncio auto mode: integration (the HTTP edge against fakes) and unit (pure domain and pipeline logic). Finish a blueprint app only when every gate below passes.

        "},{"location":"04_development/01_testing/#layout","title":"\ud83d\uddc2\ufe0f Layout","text":"
        tests/\n    integration/     # TestClient(app) + mocked resources\n    unit/            # models, converters, pipeline stages/roots\n    conftest.py      # fixtures\n
        "},{"location":"04_development/01_testing/#integration-tests","title":"\ud83d\ude80 Integration tests","text":"
        • Build the app with TestClient.
        • Replace app.state.db_resources with fake repositories/services so no database is required.
        • Patch the auth introspection call (httpx.AsyncClient.post) to return a canned identity.
        • Assert status codes and bodies for list/get/create/update/delete, plus 400, 404, 204, and the streaming endpoint's fallback.
        client = TestClient(app)\napp.state.db_resources = {\"item_svc\": FakeItemService(), ...}\n\ndef test_create_item():\n    r = client.post(\"/items\", json={\"name\": \"x\"})\n    assert r.status_code == 200\n
        "},{"location":"04_development/01_testing/#unit-tests","title":"\ud83e\uddea Unit tests","text":"
        • Domain models and converters: pure, no I/O.
        • Pipeline stages: feed a fixture state, assert the transformed state.
        • Assembled pipeline roots: fixture parity \u2014 replay a stored fixture input and compare against a golden output file.
        "},{"location":"04_development/01_testing/#config-consistency-tests","title":"\ud83d\udd10 Config-consistency tests","text":"

        For rule-driven pipelines, add a test that cross-references rules against the declared pattern registry: every flag emitted must be defined; every defined flag must be reachable. This catches drift without running the pipeline.

        "},{"location":"04_development/01_testing/#quality-gates","title":"\u2705 Quality gates","text":"

        A definition of done for any standard app:

        • python -c \"import main\" succeeds.
        • pytest passes.
        • The app serves the spec's operations; the worker drains its queue.
        • No domain module imports a web framework.
        • No infrastructure was reimplemented that a library already provides.
        "},{"location":"04_development/01_testing/#conventions","title":"\ud83d\udca1 Conventions","text":"
        • asyncio_mode = \"auto\"; no manual event loops.
        • Fixtures are checked into the repo (inputs + golden outputs).
        • Tests skip (not fail) when a required fixture is absent.
        • Do not mock the domain; mock only the infrastructure edge.
        "},{"location":"04_development/01_testing/#related","title":"Related","text":"
        • 02 \u2013 App Components \u00b7 03 \u2013 Start an App \u00b7 Overview
        "}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"\ud83e\udde9 Standards \u2014 the Aetos App Blueprint","text":"

        One reusable architecture for every backend application in the Aetos ecosystem: a spec-first HTTP edge, a framework-free domain core, a pluggable infrastructure layer, and a single composition root.

        Fill in two axes only and a new app is done: your resource model, and (if needed) your pipeline family. The blueprint itself never names a business domain \u2014 expenses, health metrics, inventory, and analytics are all the same shape underneath.

        Doc model: this wiki is written for humans \u2014 how-to guides, the build recipe, and quality gates. The enforceable rules (layering, composition root, CRUD template, pipeline scaffold) and the library registry live in the reference pages, mirrored by the machine-readable standards MCP bundle. Query find_library before writing any infrastructure.

        "},{"location":"#key-features","title":"\ud83d\ude80 Key Features","text":"
        • \ud83e\udded Spec-first routing \u2014 the OpenAPI document, not decorator routers, is the source of truth (operationId lookup)
        • \ud83c\udfd7\ufe0f One-way layering \u2014 interfaces \u2192 domain \u2192 infra, enforced
        • \ud83d\udd0c Single composition root \u2014 every dependency is wired in exactly one place
        • \ud83e\udde9 Generic, not app-specific \u2014 reusable rules versus two authoring axes
        • \ud83d\udcda Library-backed \u2014 DB, DI, pipelines, ingestion, auth, and docs come from internal libraries, never hand-written
        • \ud83d\udea6 Explicit state machines \u2014 durable job statuses, progress, and human-in-the-loop pauses
        "},{"location":"#documentation-structure","title":"\ud83d\udcc1 Documentation Structure","text":"Section Description Overview Philosophy, the one-way dependency rule, architecture App Components The component model and how the pieces wire together Start an App The entry point: gather the two axes, then build Testing & Quality Test layout and acceptance gates

        Reference (separate site, mirrors the MCP bundle): the enforceable rules \u2014 Build Recipe, Layering, Composition Root, Spec-First Routing, CRUD Template, Pipeline Scaffold, Worker & Progress, Human-in-the-Loop, Infra Adapters, Genericity Matrix, Library Registry.

        "},{"location":"#related-resources","title":"\ud83d\udd17 Related Resources","text":"
        • MCP bundle \u2014 standards server on :8009: read docs://index, docs://nav, docs://modules/{module}; call find_library(capability)
        • Library registry \u2014 the catalog of internal libraries behind every app (libraries.json, served as registry://libraries)
        • Template \u2014 the runnable skeleton at standards/src/templates/app/
        • Start a build \u2014 invoke the start_app prompt on the standards server, or read the Start an App guide

        \u00a9 Aetoskia Internal \u2014 standards

        "},{"location":"01_overview/","title":"Overview","text":"

        The blueprint is a hexagonal (ports & adapters) application with a spec-first web edge and a single composition root. It favors explicit wiring over implicit magic and existing internal libraries over hand-written infrastructure. Start here to build the mental model; the enforceable details live in the reference pages.

        "},{"location":"01_overview/#architecture","title":"\ud83c\udfd7\ufe0f Architecture","text":"
        flowchart LR\n    subgraph EDGE[\"interfaces/ \u2014 HTTP edge\"]\n        OAS[\"openapi.yaml<br/>operationId contract\"]\n        RT[\"<resource>/routes.py<br/>thin handlers\"]\n    end\n\n    subgraph CORE[\"domain/ \u2014 business core\"]\n        MD[\"models\"]\n        PT[\"ports (adapter binding seam)\"]\n        SV[\"services\"]\n    end\n\n    subgraph INFRA[\"infra/ \u2014 adapters\"]\n        RP[\"mongo repos (mongo-ops)\"]\n        AD[\"cache \u00b7 stores \u00b7 llm \u00b7 mail\"]\n        DB[(\"MongoDB\")]\n    end\n\n    CR[\"init.py<br/>composition root\"]\n\n    OAS --> RT\n    RT --> SV\n    SV --> PT\n    SV --> RP\n    PT -. depends on .- RP\n    CR --- RT\n    CR --- SV\n    CR --- RP\n    RP --> DB\n    AD --- CR

        The rule holds at every edge: code in an outer ring may depend on a ring closer to the core, never the reverse. domain/ never imports a web framework; only its ports/ seam reaches into infra/ to bind adapters.

        Legend \u2014 solid arrows are compile-time calls; dotted arrows are the port/adapter binding, performed by the composition root at startup.

        "},{"location":"01_overview/#the-one-way-dependency-rule","title":"\ud83d\udd04 The one-way dependency rule","text":"Ring May import Never imports interfaces/ domain, framework glue (glue only) business logic, infra directly domain/ its own models + its ports/ seam (which binds infra/libraries) web frameworks, interfaces/ infra/ domain ports, drivers, libraries \u2014 init.py everything (the wiring point) \u2014"},{"location":"01_overview/#core-principles","title":"\ud83e\udded Core principles","text":"
        1. One-way dependencies. interfaces -> domain -> infra is allowed; the reverse is forbidden.
        2. Ports in the domain, adapters in infra. A service depends on a port; the port's factory binds the concrete adapter, and the composition root calls those factories.
        3. One composition root. Nothing constructs its own dependencies. All wiring lives in one module (init.py) exposed as a flat resource registry.
        4. Spec-first. The HTTP contract is declared in openapi.yaml; route handlers are looked up by operationId. There are no decorator routers.
        5. Reuse the libraries. Orchestration, DI, Mongo repositories, ingestion, auth, and docs come from internal libraries \u2014 see the Library Registry.
        6. Explicit state machines. Long-running work moves through explicit, durable statuses and can pause for human input.
        "},{"location":"01_overview/#runtime-processes","title":"\u2699\ufe0f Runtime processes","text":"

        A standard app may run as up to three processes that share the same composition root:

        Process Entry point Responsibility API main.py serve HTTP; start/stop resources via lifespan Worker worker.py poll durable jobs, run pipelines, publish progress Scripts python -m scripts.<name> one-shot operator tasks"},{"location":"01_overview/#read-next","title":"\u27a1\ufe0f Read Next","text":"
        • 02 \u2013 App Components \u00b7 03 \u2013 Start an App
        "},{"location":"02_components/","title":"App Components","text":"

        What ships in every standard app, and how the pieces relate. This is the blueprint's component model \u2014 templates/app/ is a runnable instance of it.

        "},{"location":"02_components/#component-model","title":"\ud83d\uddc2\ufe0f Component model","text":"

        The blueprint has two orthogonal views: runtime flow (who calls whom) and composition-root wiring (who constructs whom). Keeping them separate makes the layering visible instead of hiding it behind a hub.

        "},{"location":"02_components/#runtime-flow","title":"\ud83d\udd00 Runtime flow","text":"

        Who calls whom at request and job time:

        %%{init: {\"flowchart\": {\"nodeSpacing\": 50, \"rankSpacing\": 80}}}%%\nflowchart TB\n    subgraph REQ[\"Request path \u2014 API process\"]\n        direction LR\n        CL([\"Client<br/>operationId\"]) --> RT[\"interfaces/&lt;resource&gt;/routes.py\"]\n        RT --> SVC[\"&lt;resource&gt;_svc\"]\n        SVC --> REPO[\"&lt;resource&gt;_repo\"]\n    end\n\n    subgraph WRK[\"Background path \u2014 worker process\"]\n        direction LR\n        W[\"worker.py\"] --> JSVC[\"job_svc\"]\n        W --> PIPE[\"pipelines/&lt;family&gt;\"]\n        PIPE --> ISVC[\"item_svc\"]\n        JSVC --> JREPO[\"job_repo\"]\n        ISVC --> IREPO[\"item_repo\"]\n    end\n\n    subgraph INFRA[\"infra/ \u2014 adapters\"]\n        MONGO[(\"MongoDB / mongo_ops\")]\n    end\n\n    REPO --> MONGO\n    JREPO --> MONGO\n    IREPO --> MONGO

        Legend \u2014 solid arrows are runtime calls. Nothing is self-constructed.

        "},{"location":"02_components/#composition-root-wiring","title":"\ud83e\uddf5 Composition-root wiring","text":"

        Who constructs what at startup:

        %%{init: {\"flowchart\": {\"nodeSpacing\": 50, \"rankSpacing\": 80}}}%%\nflowchart TB\n    INIT[\"init.py \u2014 composition root\"]\n    SETUP[\"infra/db/setup.py\"]\n    FACT[\"domain/*/ports/*<br/>factory seams\"]\n    REPOS[\"repos<br/>infra/db/repos/mongo.py\"]\n    SVCES[\"services<br/>CrudsServiceAdaptor\"]\n    STATE[\"app.state.db_resources\"]\n    HEXA[\"hexa.build\"]\n    RT[\"routes.py\"]\n    WK[\"worker.py\"]\n\n    INIT --> SETUP\n    INIT --> FACT\n    FACT --> REPOS\n    FACT --> SVCES\n    REPOS --> SVCES\n    HEXA -.-> SVCES\n    INIT --> STATE\n    STATE --> RT\n    INIT --> WK

        Legend \u2014 init.py is the only place that imports concrete adapters. The ports/ factory seams bind infra; the API reads resources from app.state.db_resources, the worker calls init() directly.

        "},{"location":"02_components/#component-roles","title":"\ud83d\udce6 Component roles","text":""},{"location":"02_components/#1-interfaces-the-edge","title":"1. interfaces/ \u2014 the edge","text":"

        Thin handlers bound to openapi.yaml operationIds. They parse the request, call a service, and shape the response. No business rules, no I/O.

        • interfaces/openapi.py re-exports every route module so the OpenAPI-first app can resolve handlers.
        • Routes may mount the cache decorator on read endpoints.
        "},{"location":"02_components/#2-domain-the-core","title":"2. domain/ \u2014 the core","text":"

        Models, ports, and services (use cases). No framework imports. The ports/ package is the one seam allowed to reach into infra/: each port module declares the document shape and a factory that binds the adapter.

        • <aggregate>/models.py \u2014 dataclasses, enums, document shapes.
        • <aggregate>/ports/db.py \u2014 document model + create_<aggregate>_repo().
        • <aggregate>/ports/service.py \u2014 service + get_<aggregate>_service(repo).
        • Services compose ports and enforce the business rules; the port factories bind the implementations, and the composition root calls them.
        "},{"location":"02_components/#3-infra-the-adapters","title":"3. infra/ \u2014 the adapters","text":"

        Concrete implementations of the domain ports plus cross-cutting infrastructure:

        • db/ \u2014 mongo-ops repositories and the connection lifecycle.
        • adapters/ \u2014 cache store, progress bus, storages, fetchers (library-backed).
        • Anything else the domain needs, implemented against its ports.
        "},{"location":"02_components/#4-pipelines-data-driven-families-optional","title":"4. pipelines/ \u2014 data-driven families (optional)","text":"

        Extraction and enrichment families, each a deterministic step chain (abc.py / impl.py / pipeline.yaml / resources/). Roots are registered in the pattern registry and driven either by a worker job or a CLI.

        "},{"location":"02_components/#5-workerpy-the-job-worker-optional","title":"5. worker.py \u2014 the job worker (optional)","text":"

        A separate process that polls durable job collections, runs pipeline roots, and publishes progress to the progress bus. It shares the same composition root as the API.

        "},{"location":"02_components/#6-initpy-the-composition-root","title":"6. init.py \u2014 the composition root","text":"

        The only place anything is constructed: leaf repositories \u2192 dependent repositories \u2192 services \u2192 adapters. Exposes get_svc overloads and starts / stops resources on the app lifespan.

        "},{"location":"02_components/#how-the-request-flows","title":"\ud83d\udd17 How the request flows","text":"
        sequenceDiagram\n    participant C as Client\n    participant R as route (interfaces)\n    participant S as service (port factory / adapter)\n    participant P as repo (infra)\n    participant DB as MongoDB\n\n    C->>R: POST /items (operationId)\n    R->>S: svc.create(payload)\n    S->>P: repo.insert(doc)\n    P->>DB: insert\n    DB-->>P: ack\n    P-->>S: doc\n    S-->>R: result\n    R-->>C: 200 JSON
        "},{"location":"02_components/#related","title":"Related","text":"
        • 01 \u2013 Overview \u00b7 03 \u2013 Start an App \u00b7 04 \u2013 Testing & Quality
        "},{"location":"03_start_an_app/","title":"\ud83d\ude80 Start an App","text":"

        This is the entry point for building a new application from the blueprint. The goal is one deterministic pass: you supply only the two axes, and the agent does the rest by reading the standards MCP, importing Tier A libraries, and copying Tier B template components.

        Doc model: this page is the human entry point. The deterministic, machine-facing checklist lives in the reference page Build Recipe, served as docs://modules/build_recipe.

        "},{"location":"03_start_an_app/#what-the-agent-needs-from-you","title":"\ud83e\uddfe What the agent needs from you","text":"

        Gather these before starting \u2014 the start_app MCP prompt asks for them:

        1. App name + one-line purpose.
        2. Axis 1 \u2014 resources. Each resource, its fields, and relationships. Paste openapi.yaml if you already have it.
        3. Axis 2 \u2014 pipelines. Only if an input needs a multi-step transformation: the families and their input formats (or \"none\").
        4. Auth needed now? Default no. Auth is deferred (py-jwt primitives only), and an authless app is just a spec with no security keys \u2014 see Spec-First Routing \u2192 Authless apps.
        5. Integrations. Mongo, ingestion formats, email, LLM provider.

        If you can state axes 1 and 2, the rest is mechanical.

        "},{"location":"03_start_an_app/#how-to-start","title":"\u25b6\ufe0f How to start","text":"

        From an MCP client connected to the standards server:

        1. Invoke the intake prompt (or start_app with no arguments) to get the intake questions.
        2. Answer them, then invoke build (build(app_name=..., resources=..., pipelines=..., auth=...)), or use start_app to intake-and-build in one call.
        3. The prompt hands off to docs://modules/build_recipe; the agent scaffolds with get_app_template() and pulls exact files with get_app_component(kind, resource=..., family=...), authoring only the app-specific parts.
        4. To validate the standard without touching a real app, invoke trial (trial(app_name=..., build_path=...)) to build into a scratch directory, prove it builds, and report findings.

        Manual alternative \u2014 copy the skeleton and follow the reference recipe:

        cp -r <docs>/standards/src/templates/app apps/<your-app>\n
        "},{"location":"03_start_an_app/#reference","title":"\ud83d\udcda Reference","text":"
        • Build Recipe \u2014 the deterministic checklist
        • Genericity Matrix \u2014 Tier A / B / C
        • Library Registry \u2014 the Tier A catalog
        • App Components \u2014 the component model
        "},{"location":"03_start_an_app/#related","title":"Related","text":"
        • 01 \u2013 Overview \u00b7 02 \u2013 App Components \u00b7 04 \u2013 Testing & Quality
        "},{"location":"04_development/01_testing/","title":"Testing & Quality Gates","text":"

        Two suites run under pytest with asyncio auto mode: integration (the HTTP edge against fakes) and unit (pure domain and pipeline logic). Finish a blueprint app only when every gate below passes.

        "},{"location":"04_development/01_testing/#layout","title":"\ud83d\uddc2\ufe0f Layout","text":"
        tests/\n    integration/     # TestClient(app) + mocked resources\n    unit/            # models, converters, pipeline stages/roots\n    conftest.py      # fixtures\n
        "},{"location":"04_development/01_testing/#integration-tests","title":"\ud83d\ude80 Integration tests","text":"
        • Build the app with TestClient.
        • Replace app.state.db_resources with fake repositories/services so no database is required.
        • Patch the auth introspection call (httpx.AsyncClient.post) to return a canned identity.
        • Assert status codes and bodies for list/get/create/update/delete, plus 400, 404, 204, and the streaming endpoint's fallback.
        client = TestClient(app)\napp.state.db_resources = {\"item_svc\": FakeItemService(), ...}\n\ndef test_create_item():\n    r = client.post(\"/items\", json={\"name\": \"x\"})\n    assert r.status_code == 200\n
        "},{"location":"04_development/01_testing/#unit-tests","title":"\ud83e\uddea Unit tests","text":"
        • Domain models and converters: pure, no I/O.
        • Pipeline stages: feed a fixture state, assert the transformed state.
        • Assembled pipeline roots: fixture parity \u2014 replay a stored fixture input and compare against a golden output file.
        "},{"location":"04_development/01_testing/#config-consistency-tests","title":"\ud83d\udd10 Config-consistency tests","text":"

        For rule-driven pipelines, add a test that cross-references rules against the declared pattern registry: every flag emitted must be defined; every defined flag must be reachable. This catches drift without running the pipeline.

        "},{"location":"04_development/01_testing/#quality-gates","title":"\u2705 Quality gates","text":"

        A definition of done for any standard app:

        • python -c \"import main\" succeeds.
        • pytest passes.
        • The app serves the spec's operations; the worker drains its queue.
        • No domain module imports a web framework.
        • No infrastructure was reimplemented that a library already provides.
        "},{"location":"04_development/01_testing/#conventions","title":"\ud83d\udca1 Conventions","text":"
        • asyncio_mode = \"auto\"; no manual event loops.
        • Fixtures are checked into the repo (inputs + golden outputs).
        • Tests skip (not fail) when a required fixture is absent.
        • Do not mock the domain; mock only the infrastructure edge.
        "},{"location":"04_development/01_testing/#related","title":"Related","text":"
        • 02 \u2013 App Components \u00b7 03 \u2013 Start an App \u00b7 Overview
        "}]} \ No newline at end of file