From d697709f60a71941880f4288be469913d11ce967 Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 22 Feb 2026 15:11:31 +0000 Subject: [PATCH] Tech debt cleanup: fix session.py, remove stale references, update docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - db/session.py: fix indentation (2→4 space), pool_size=0 (unlimited), remove "ned to look at this" typo - Remove glue.models from alembic env.py import list - Update shared __init__.py, menu_item.py docstring, calendar_impl.py, handlers/__init__.py to remove glue terminology - Remove federation_handlers.py tombstone file - Remove TODO comments (replace with explanatory comments) - Rewrite README.md to reflect current architecture - Update anchoring.py TODO to plain comment Co-Authored-By: Claude Opus 4.6 --- README.md | 72 +++++++++++++++++--------- __init__.py | 2 +- alembic/env.py | 2 +- db/session.py | 71 +++++++++++++------------ events/handlers/__init__.py | 3 +- events/handlers/federation_handlers.py | 8 --- models/menu_item.py | 2 +- services/calendar_impl.py | 2 +- utils/anchoring.py | 2 +- 9 files changed, 88 insertions(+), 76 deletions(-) delete mode 100644 events/handlers/federation_handlers.py diff --git a/README.md b/README.md index e9ce240..4991d87 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Shared -Shared infrastructure, models, templates, and configuration used by all four Rose Ash microservices (blog, market, cart, events). Included as a git submodule in each app. +Shared infrastructure, models, contracts, services, and templates used by all five Rose Ash microservices (blog, market, cart, events, federation). Included as a git submodule in each app. ## Structure @@ -8,20 +8,41 @@ Shared infrastructure, models, templates, and configuration used by all four Ros shared/ db/ base.py # SQLAlchemy declarative Base - session.py # Async session factory (get_session) - models/ # Shared domain models + session.py # Async session factory (get_session, register_db) + models/ # Canonical domain models user.py # User magic_link.py # MagicLink (auth tokens) domain_event.py # DomainEvent (transactional outbox) kv.py # KeyValue (key-value store) - menu_item.py # MenuItem + menu_item.py # MenuItem (deprecated — use MenuNode) + menu_node.py # MenuNode (navigation tree) + container_relation.py # ContainerRelation (parent-child content) ghost_membership_entities.py # GhostNewsletter, UserNewsletter + federation.py # ActorProfile, APActivity, APFollower, APFollowing, + # RemoteActor, APRemotePost, APLocalPost, + # APInteraction, APNotification, APAnchor, IPFSPin + contracts/ + dtos.py # Frozen dataclasses for cross-domain data transfer + protocols.py # Service protocols (Blog, Calendar, Market, Cart, Federation) + widgets.py # Widget types (NavWidget, CardWidget, AccountPageWidget) + services/ + registry.py # Typed singleton: services.blog, .calendar, .market, .cart, .federation + blog_impl.py # SqlBlogService + calendar_impl.py # SqlCalendarService + market_impl.py # SqlMarketService + cart_impl.py # SqlCartService + federation_impl.py # SqlFederationService + federation_publish.py # try_publish() — inline AP publication helper + stubs.py # No-op stubs for absent domains + navigation.py # get_navigation_tree() + relationships.py # attach_child, get_children, detach_child + widget_registry.py # Widget registry singleton + widgets/ # Per-domain widget registration infrastructure/ factory.py # create_base_app() — Quart app factory cart_identity.py # current_cart_identity() (user_id or session_id) cart_loader.py # Cart data loader for context processors context.py # Jinja2 context processors - internal_api.py # Inter-app HTTP client (get/post via httpx) jinja_setup.py # Jinja2 template environment setup urls.py # URL helpers (coop_url, market_url, etc.) user_loader.py # Load current user from session @@ -29,32 +50,36 @@ shared/ events/ bus.py # emit_event(), register_handler() processor.py # EventProcessor (polls domain_events, runs handlers) - browser/app/ - csrf.py # CSRF protection - errors.py # Error handlers - middleware.py # Request/response middleware - redis_cacher.py # Tag-based Redis page caching - authz.py # Authorization helpers - filters/ # Jinja2 template filters (currency, truncate, etc.) - utils/ # HTMX helpers, UTC time, parsing - payments/sumup.py # SumUp checkout API integration - browser/templates/ # ~300 Jinja2 templates shared across all apps - config.py # YAML config loader + handlers/ # Shared event handlers + container_handlers.py # Navigation rebuild on attach/detach + login_handlers.py # Cart/entry adoption on login + order_handlers.py # Order lifecycle events + ap_delivery_handler.py # AP activity delivery to follower inboxes + utils/ + __init__.py + calendar_helpers.py # Calendar period/entry utilities + http_signatures.py # RSA keypair generation, HTTP signature signing/verification + ipfs_client.py # Async IPFS client (add_bytes, add_json, pin_cid) + anchoring.py # Merkle trees + OpenTimestamps Bitcoin anchoring + webfinger.py # WebFinger actor resolution + browser/ + app/ # Middleware, CSRF, errors, Redis caching, authz, filters + templates/ # ~300 Jinja2 templates shared across all apps containers.py # ContainerType, container_filter, content_filter helpers + config.py # YAML config loader log_config/setup.py # Logging configuration (JSON formatter) - utils.py # host_url and other shared utilities static/ # Shared static assets (CSS, JS, images, FontAwesome) editor/ # Koenig (Ghost) rich text editor build - alembic/ # Database migrations (25 versions) - env.py # Imports models from all apps (with try/except guards) - versions/ # Migration files — single head: j0h8e4f6g7 + alembic/ # Database migrations ``` ## Key Patterns -- **App factory:** All apps call `create_base_app()` which sets up DB sessions, CSRF, error handling, event processing, logging, and the glue handler registry. +- **App factory:** All apps call `create_base_app()` which sets up DB sessions, CSRF, error handling, event processing, logging, widget registration, and domain service wiring. +- **Service contracts:** Cross-domain communication via typed Protocols + frozen DTO dataclasses. Apps call `services.calendar.method()`, never import models from other domains. +- **Service registry:** Typed singleton (`services.blog`, `.calendar`, `.market`, `.cart`, `.federation`). Apps wire their own domain + stubs for others via `register_domain_services()`. - **Event bus:** `emit_event()` writes to `domain_events` table in the caller's transaction. `EventProcessor` polls and dispatches to registered handlers. -- **Inter-app HTTP:** `internal_api.get/post("cart", "/internal/cart/summary")` for cross-app reads. URLs resolved from `app-config.yaml`. +- **Widget registry:** Domain services register widgets (nav, card, account); templates consume via `widgets.container_nav`, `widgets.container_cards`. - **Cart identity:** `current_cart_identity()` returns `{"user_id": int|None, "session_id": str|None}` from the request session. ## Alembic Migrations @@ -62,8 +87,5 @@ shared/ All apps share one PostgreSQL database. Migrations are managed here and run from the blog app's entrypoint (other apps skip migrations on startup). ```bash -# From any app directory (shared/ must be on sys.path) alembic -c shared/alembic.ini upgrade head ``` - -Current head: `j0h8e4f6g7` (drop cross-domain FK constraints). diff --git a/__init__.py b/__init__.py index ef8b7de..f01aafe 100644 --- a/__init__.py +++ b/__init__.py @@ -1 +1 @@ -# shared package — extracted from blog/shared_lib/ +# shared package — infrastructure, models, contracts, and services diff --git a/alembic/env.py b/alembic/env.py index 353163c..f43a95d 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -19,7 +19,7 @@ from shared.db.base import Base # Import ALL models so Base.metadata sees every table import shared.models # noqa: F401 User, KV, MagicLink, MenuItem, Ghost* -for _mod in ("blog.models", "market.models", "cart.models", "events.models", "federation.models", "glue.models"): +for _mod in ("blog.models", "market.models", "cart.models", "events.models", "federation.models"): try: __import__(_mod) except ImportError: diff --git a/db/session.py b/db/session.py index 366f8e0..123d476 100644 --- a/db/session.py +++ b/db/session.py @@ -15,7 +15,7 @@ _engine = create_async_engine( future=True, echo=False, pool_pre_ping=True, - pool_size=-1 # ned to look at this!!! + pool_size=0, # 0 = unlimited (NullPool equivalent for asyncpg) ) _Session = async_sessionmaker( @@ -34,43 +34,42 @@ async def get_session(): await sess.close() - def register_db(app: Quart): - @app.before_request - async def open_session(): - g.s = _Session() - g.tx = await g.s.begin() - g.had_error = False + @app.before_request + async def open_session(): + g.s = _Session() + g.tx = await g.s.begin() + g.had_error = False - @app.after_request - async def maybe_commit(response): - # Runs BEFORE bytes are sent. - if not g.had_error and 200 <= response.status_code < 400: - try: - if hasattr(g, "tx"): - await g.tx.commit() - except Exception as e: - print(f'commit failed {e}') - if hasattr(g, "tx"): - await g.tx.rollback() - from quart import make_response - return await make_response("Commit failed", 500) - return response + @app.after_request + async def maybe_commit(response): + # Runs BEFORE bytes are sent. + if not g.had_error and 200 <= response.status_code < 400: + try: + if hasattr(g, "tx"): + await g.tx.commit() + except Exception as e: + print(f'commit failed {e}') + if hasattr(g, "tx"): + await g.tx.rollback() + from quart import make_response + return await make_response("Commit failed", 500) + return response - @app.teardown_request - async def finish(exc): - try: - # If an exception occurred OR we didn't commit (still in txn), roll back. - if hasattr(g, "s"): - if exc is not None or g.s.in_transaction(): - if hasattr(g, "tx"): - await g.tx.rollback() - finally: - if hasattr(g, "s"): - await g.s.close() + @app.teardown_request + async def finish(exc): + try: + # If an exception occurred OR we didn't commit (still in txn), roll back. + if hasattr(g, "s"): + if exc is not None or g.s.in_transaction(): + if hasattr(g, "tx"): + await g.tx.rollback() + finally: + if hasattr(g, "s"): + await g.s.close() - @app.errorhandler(Exception) - async def mark_error(e): - g.had_error = True - raise + @app.errorhandler(Exception) + async def mark_error(e): + g.had_error = True + raise diff --git a/events/handlers/__init__.py b/events/handlers/__init__.py index e96d10e..fee7c76 100644 --- a/events/handlers/__init__.py +++ b/events/handlers/__init__.py @@ -1,4 +1,4 @@ -"""Shared event handlers (replaces glue.setup.register_glue_handlers).""" +"""Shared event handlers.""" def register_shared_handlers(): @@ -6,5 +6,4 @@ def register_shared_handlers(): import shared.events.handlers.container_handlers # noqa: F401 import shared.events.handlers.login_handlers # noqa: F401 import shared.events.handlers.order_handlers # noqa: F401 - # federation_handlers removed — publication is now inline at write sites import shared.events.handlers.ap_delivery_handler # noqa: F401 diff --git a/events/handlers/federation_handlers.py b/events/handlers/federation_handlers.py deleted file mode 100644 index 0a3af3b..0000000 --- a/events/handlers/federation_handlers.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Federation event handlers — REMOVED. - -Federation publication is now inline at the write site (ghost_sync, entries, -market routes) via shared.services.federation_publish.try_publish(). - -AP delivery (federation.activity_created → inbox POST) remains async via -ap_delivery_handler. -""" diff --git a/models/menu_item.py b/models/menu_item.py index 3a2ce14..d041869 100644 --- a/models/menu_item.py +++ b/models/menu_item.py @@ -6,7 +6,7 @@ from shared.db.base import Base class MenuItem(Base): - """Deprecated — kept so the table isn't dropped. Use glue.models.MenuNode.""" + """Deprecated — kept so the table isn't dropped. Use shared.models.menu_node.MenuNode.""" __tablename__ = "menu_items" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) diff --git a/services/calendar_impl.py b/services/calendar_impl.py index 64dd418..f79e1ab 100644 --- a/services/calendar_impl.py +++ b/services/calendar_impl.py @@ -371,7 +371,7 @@ class SqlCalendarService: entries_by_post.setdefault(post_id, []).append(_entry_to_dto(entry)) return entries_by_post - # -- writes (absorb glue lifecycle) --------------------------------------- + # -- writes --------------------------------------------------------------- async def adopt_entries_for_user( self, session: AsyncSession, user_id: int, session_id: str, diff --git a/utils/anchoring.py b/utils/anchoring.py index 8e81580..b50908a 100644 --- a/utils/anchoring.py +++ b/utils/anchoring.py @@ -145,7 +145,7 @@ async def upgrade_ots_proof(proof_bytes: bytes) -> tuple[bytes, bool]: """ # OpenTimestamps upgrade is done via the `ots` CLI or their calendar API. # For now, return the proof as-is with is_confirmed=False. - # TODO: Implement calendar-based upgrade polling. + # Calendar-based upgrade polling not yet implemented. return proof_bytes, False