feat: renumber lib reference into 4-pillar anatomy
Renumber the 15 reference docs into pillar groups (recipe -> anatomy): - 01 Build Recipe (entry), 02 Composition Root - domains: 10 Layering Rules - contract: 20 Spec-First Routing, 21 CRUD Template, 22 Auth Integration - interfaces/infra: 30 Infra Adapters - UI: 40 UI: Auto-Rendered Admin & Custom Pages (new; @aetoskia packages, client template, x- catalog via react-openapi MCP module) - pipelines: 50 Pipeline Scaffold, 51 Worker & Progress, 52 Human-in-the-Loop - delivery/meta: 90 Drone Pipelines, 91 Versioning & Changelog, 92 Genericity Matrix, 93 Library Registry, 94 Repository Inventory MCP module names are unchanged (NN_ prefix stripped); all cross-references (wiki, templates, hooks, registry) updated; lib index rewritten pillar-first; build recipe bootstraps the client template. Bundle + HTML sites regenerated.
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
"""# Summary
|
||||
|
||||
Repository factory — picks the in-memory or Mongo integer-id adapter based
|
||||
on ``settings.repo_backend``.
|
||||
|
||||
---
|
||||
|
||||
Every domain slice calls :func:`create_repo` so the backend choice lives in
|
||||
one place: ``REPO_BACKEND=memory`` (default) yields the zero-dependency
|
||||
process-local store; ``REPO_BACKEND=mongo`` yields the persistent adapter.
|
||||
"""
|
||||
from typing import Any
|
||||
|
||||
from config import settings
|
||||
from infra.db.repos.memory import IntIdMemoryRepository
|
||||
from infra.db.repos.mongo_int import IntIdMongoRepository
|
||||
|
||||
|
||||
def create_repo(collection: str, document_cls: type[Any]):
|
||||
"""Create the integer-id repository selected by ``settings.repo_backend``.
|
||||
|
||||
Args:
|
||||
collection (str): Collection / store name for the resource.
|
||||
document_cls (type[Any]): Document model bound to the repository.
|
||||
|
||||
Returns:
|
||||
IntIdMemoryRepository | IntIdMongoRepository: The configured repository.
|
||||
"""
|
||||
if settings.repo_backend == "mongo":
|
||||
return IntIdMongoRepository(collection, document_cls)
|
||||
return IntIdMemoryRepository(document_cls)
|
||||
|
||||
161
standards/mcp/templates/app/infra/db/repos/memory.py
Normal file
161
standards/mcp/templates/app/infra/db/repos/memory.py
Normal file
@@ -0,0 +1,161 @@
|
||||
"""# Summary
|
||||
|
||||
In-memory integer-id repository — the zero-dependency persistence adapter.
|
||||
|
||||
---
|
||||
|
||||
One process-local dictionary per resource, keyed by auto-incrementing integer
|
||||
``id``. The wire contract (``openapi.yaml``) declares integer primary keys,
|
||||
so this adapter speaks integers end-to-end and is the default backend
|
||||
(``REPO_BACKEND=memory``).
|
||||
|
||||
The method surface mirrors the mongo-ops repository duck-type consumed by
|
||||
``infra.adapters.service.CrudsServiceAdaptor``: ``create``, ``get_by_id``,
|
||||
``get_many``, ``update``, ``patch``, ``delete``.
|
||||
|
||||
Notes:
|
||||
Not thread-safe and not durable — deliberately. Swap to the Mongo
|
||||
adapter via ``REPO_BACKEND=mongo`` without touching domain code.
|
||||
"""
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IntIdMemoryRepository:
|
||||
"""Dictionary-backed repository with auto-increment integer ids.
|
||||
|
||||
Attributes:
|
||||
document_cls (type[BaseModel]): Document model instantiated for
|
||||
every stored record.
|
||||
"""
|
||||
|
||||
def __init__(self, document_cls: type[BaseModel]):
|
||||
"""Initialize an empty store for ``document_cls``.
|
||||
|
||||
Args:
|
||||
document_cls (type[BaseModel]): Document model bound to this repo.
|
||||
"""
|
||||
self.document_cls = document_cls
|
||||
self._docs: dict[int, BaseModel] = {}
|
||||
self._seq = 0
|
||||
|
||||
def _to_doc(self, data: Any) -> BaseModel:
|
||||
"""Coerce ``data`` into a document instance.
|
||||
|
||||
Args:
|
||||
data (Any): Document instance or field mapping.
|
||||
|
||||
Returns:
|
||||
BaseModel: Document built from ``data``.
|
||||
"""
|
||||
if isinstance(data, self.document_cls):
|
||||
return data
|
||||
return self.document_cls(**dict(data))
|
||||
|
||||
async def create(self, data: Any) -> BaseModel:
|
||||
"""Persist a new document with a freshly allocated integer id.
|
||||
|
||||
Args:
|
||||
data (Any): Document instance or field mapping (id ignored).
|
||||
|
||||
Returns:
|
||||
BaseModel: The stored document with ``id`` assigned.
|
||||
"""
|
||||
doc = self._to_doc(data)
|
||||
self._seq += 1
|
||||
doc.id = self._seq
|
||||
self._docs[doc.id] = doc
|
||||
return doc
|
||||
|
||||
async def get_by_id(self, id: int) -> BaseModel | None:
|
||||
"""Fetch one document by integer id.
|
||||
|
||||
Args:
|
||||
id (int): Document identifier.
|
||||
|
||||
Returns:
|
||||
BaseModel | None: The stored document, or ``None`` when absent.
|
||||
"""
|
||||
return self._docs.get(int(id))
|
||||
|
||||
async def get_many(self, filter: dict | None = None, **_: Any) -> list[BaseModel]:
|
||||
"""List documents, optionally filtered by top-level equality.
|
||||
|
||||
Args:
|
||||
filter (dict | None): Equality filter on document fields.
|
||||
|
||||
Returns:
|
||||
list[BaseModel]: Matching documents in insertion order.
|
||||
"""
|
||||
docs = list(self._docs.values())
|
||||
if filter:
|
||||
docs = [d for d in docs if all(getattr(d, k, None) == v for k, v in filter.items())]
|
||||
return docs
|
||||
|
||||
async def update(self, id: int, data: Any) -> BaseModel | None:
|
||||
"""Replace a document by integer id.
|
||||
|
||||
Args:
|
||||
id (int): Document identifier.
|
||||
data (Any): Replacement document or field mapping.
|
||||
|
||||
Returns:
|
||||
BaseModel | None: The stored document, or ``None`` when absent.
|
||||
"""
|
||||
if int(id) not in self._docs:
|
||||
return None
|
||||
doc = self._to_doc(data)
|
||||
doc.id = int(id)
|
||||
self._docs[doc.id] = doc
|
||||
return doc
|
||||
|
||||
async def patch(self, id: int, data: dict) -> BaseModel | None:
|
||||
"""Partially update a document by integer id.
|
||||
|
||||
Args:
|
||||
id (int): Document identifier.
|
||||
data (dict): Fields to merge into the stored document.
|
||||
|
||||
Returns:
|
||||
BaseModel | None: The stored document, or ``None`` when absent.
|
||||
"""
|
||||
current = self._docs.get(int(id))
|
||||
if current is None:
|
||||
return None
|
||||
merged = current.model_dump()
|
||||
merged.update(data)
|
||||
doc = self.document_cls(**merged)
|
||||
doc.id = int(id)
|
||||
self._docs[doc.id] = doc
|
||||
return doc
|
||||
|
||||
async def delete(self, id: int) -> bool:
|
||||
"""Delete a document by integer id.
|
||||
|
||||
Args:
|
||||
id (int): Document identifier.
|
||||
|
||||
Returns:
|
||||
bool: ``True`` when a document was removed.
|
||||
"""
|
||||
return self._docs.pop(int(id), None) is not None
|
||||
|
||||
async def delete_many(self, filter: dict | None = None) -> int:
|
||||
"""Delete every document (optionally filtered by equality).
|
||||
|
||||
Args:
|
||||
filter (dict | None): Equality filter on document fields.
|
||||
|
||||
Returns:
|
||||
int: Number of documents removed.
|
||||
"""
|
||||
victims = await self.get_many(filter)
|
||||
for doc in victims:
|
||||
self._docs.pop(doc.id, None)
|
||||
return len(victims)
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Remove every document and reset the id counter."""
|
||||
self._docs.clear()
|
||||
self._seq = 0
|
||||
184
standards/mcp/templates/app/infra/db/repos/mongo_int.py
Normal file
184
standards/mcp/templates/app/infra/db/repos/mongo_int.py
Normal file
@@ -0,0 +1,184 @@
|
||||
"""# Summary
|
||||
|
||||
MongoDB integer-id repository — the persistent persistence adapter.
|
||||
|
||||
---
|
||||
|
||||
The wire contract declares integer primary keys, while mongo-ops'
|
||||
``BaseRepository`` allocates ``ObjectId`` keys. This adapter therefore talks
|
||||
to the shared ``MongoConnectionManager`` database directly (the Tier A
|
||||
connection lifecycle is reused; only the id strategy differs) and stores
|
||||
records with auto-incrementing integer ``_id`` values allocated from a
|
||||
``_counters`` collection.
|
||||
|
||||
The method surface mirrors the in-memory adapter exactly, so
|
||||
``CrudsServiceAdaptor`` and the domain services are backend-agnostic.
|
||||
"""
|
||||
from typing import Any
|
||||
|
||||
from mongo_ops import MongoConnectionManager
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class IntIdMongoRepository:
|
||||
"""Motor-backed repository with auto-increment integer ids.
|
||||
|
||||
Attributes:
|
||||
collection (str): MongoDB collection name.
|
||||
document_cls (type[BaseModel]): Document model bound to this repo.
|
||||
"""
|
||||
|
||||
def __init__(self, collection: str, document_cls: type[BaseModel]):
|
||||
"""Bind the repository to a collection and document model.
|
||||
|
||||
Args:
|
||||
collection (str): Collection name to operate on.
|
||||
document_cls (type[BaseModel]): Document model persisted here.
|
||||
"""
|
||||
self.collection = collection
|
||||
self.document_cls = document_cls
|
||||
|
||||
@property
|
||||
def _col(self):
|
||||
"""AsyncIOMotorCollection: The live collection handle."""
|
||||
return MongoConnectionManager._database[self.collection]
|
||||
|
||||
async def _next_id(self) -> int:
|
||||
"""Allocate the next integer id from the shared counters collection.
|
||||
|
||||
Returns:
|
||||
int: A monotonically increasing identifier.
|
||||
"""
|
||||
counters = MongoConnectionManager._database["_counters"]
|
||||
doc = await counters.find_one_and_update(
|
||||
{"_id": self.collection},
|
||||
{"$inc": {"seq": 1}},
|
||||
upsert=True,
|
||||
return_document=True,
|
||||
)
|
||||
return int(doc["seq"])
|
||||
|
||||
def _to_doc(self, data: Any) -> BaseModel:
|
||||
"""Coerce ``data`` into a document instance.
|
||||
|
||||
Args:
|
||||
data (Any): Document instance or field mapping.
|
||||
|
||||
Returns:
|
||||
BaseModel: Document built from ``data``.
|
||||
"""
|
||||
if isinstance(data, self.document_cls):
|
||||
return data
|
||||
return self.document_cls(**dict(data))
|
||||
|
||||
def _to_payload(self, doc: BaseModel, id: int | None = None) -> dict:
|
||||
"""Serialize a document into a Mongo-ready payload.
|
||||
|
||||
Args:
|
||||
doc (BaseModel): Document to serialize.
|
||||
id (int | None): Explicit ``_id``; taken from the doc when omitted.
|
||||
|
||||
Returns:
|
||||
dict: Mapping with an integer ``_id`` key.
|
||||
"""
|
||||
payload = doc.model_dump()
|
||||
payload.pop("id", None)
|
||||
payload["_id"] = id if id is not None else doc.id
|
||||
return payload
|
||||
|
||||
async def create(self, data: Any) -> BaseModel:
|
||||
"""Persist a new document with a freshly allocated integer id.
|
||||
|
||||
Args:
|
||||
data (Any): Document instance or field mapping (id ignored).
|
||||
|
||||
Returns:
|
||||
BaseModel: The stored document with ``id`` assigned.
|
||||
"""
|
||||
doc = self._to_doc(data)
|
||||
new_id = await self._next_id()
|
||||
await self._col.insert_one(self._to_payload(doc, new_id))
|
||||
doc.id = new_id
|
||||
return doc
|
||||
|
||||
async def get_by_id(self, id: int) -> BaseModel | None:
|
||||
"""Fetch one document by integer id.
|
||||
|
||||
Args:
|
||||
id (int): Document identifier.
|
||||
|
||||
Returns:
|
||||
BaseModel | None: The stored document, or ``None`` when absent.
|
||||
"""
|
||||
raw = await self._col.find_one({"_id": int(id)})
|
||||
return self.document_cls.model_validate(raw) if raw else None
|
||||
|
||||
async def get_many(self, filter: dict | None = None, **_: Any) -> list[BaseModel]:
|
||||
"""List documents, optionally filtered by top-level equality.
|
||||
|
||||
Args:
|
||||
filter (dict | None): Equality filter on document fields.
|
||||
|
||||
Returns:
|
||||
list[BaseModel]: Matching documents in insertion order.
|
||||
"""
|
||||
cursor = self._col.find(filter or {})
|
||||
return [self.document_cls.model_validate(raw) async for raw in cursor]
|
||||
|
||||
async def update(self, id: int, data: Any) -> BaseModel | None:
|
||||
"""Replace a document by integer id.
|
||||
|
||||
Args:
|
||||
id (int): Document identifier.
|
||||
data (Any): Replacement document or field mapping.
|
||||
|
||||
Returns:
|
||||
BaseModel | None: The stored document, or ``None`` when absent.
|
||||
"""
|
||||
doc = self._to_doc(data)
|
||||
payload = self._to_payload(doc, int(id))
|
||||
result = await self._col.replace_one({"_id": int(id)}, payload)
|
||||
if result.matched_count == 0:
|
||||
return None
|
||||
doc.id = int(id)
|
||||
return doc
|
||||
|
||||
async def patch(self, id: int, data: dict) -> BaseModel | None:
|
||||
"""Partially update a document by integer id.
|
||||
|
||||
Args:
|
||||
id (int): Document identifier.
|
||||
data (dict): Fields to merge into the stored document.
|
||||
|
||||
Returns:
|
||||
BaseModel | None: The stored document, or ``None`` when absent.
|
||||
"""
|
||||
updates = {k: v for k, v in data.items() if k != "id"}
|
||||
result = await self._col.update_one({"_id": int(id)}, {"$set": updates})
|
||||
if result.matched_count == 0:
|
||||
return None
|
||||
return await self.get_by_id(id)
|
||||
|
||||
async def delete(self, id: int) -> bool:
|
||||
"""Delete a document by integer id.
|
||||
|
||||
Args:
|
||||
id (int): Document identifier.
|
||||
|
||||
Returns:
|
||||
bool: ``True`` when a document was removed.
|
||||
"""
|
||||
result = await self._col.delete_one({"_id": int(id)})
|
||||
return result.deleted_count > 0
|
||||
|
||||
async def delete_many(self, filter: dict | None = None) -> int:
|
||||
"""Delete every document (optionally filtered by equality).
|
||||
|
||||
Args:
|
||||
filter (dict | None): Equality filter on document fields.
|
||||
|
||||
Returns:
|
||||
int: Number of documents removed.
|
||||
"""
|
||||
result = await self._col.delete_many(filter or {})
|
||||
return result.deleted_count
|
||||
Reference in New Issue
Block a user