Replace inter-service _handlers dicts with declarative sx defquery/defaction
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m5s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m5s
The inter-service data layer (fetch_data/call_action) was the least structured part of the codebase — Python _handlers dicts with ad-hoc param extraction scattered across 16 route files. This replaces them with declarative .sx query/action definitions that make the entire inter-service protocol self-describing and greppable. Infrastructure: - defquery/defaction special forms in the sx evaluator - Query/action registry with load, lookup, and schema introspection - Query executor using async_eval with I/O primitives - Blueprint factories (create_data_blueprint/create_action_blueprint) with sx-first dispatch and Python fallback - /internal/schema endpoint on every service - parse-datetime and split-ids primitives for type coercion Service extractions: - LikesService (toggle, is_liked, liked_slugs, liked_ids) - PageConfigService (ensure, get_by_container, get_by_id, get_batch, update) - RelationsService (wraps module-level functions) - AccountDataService (user_by_email, newsletters) - CartItemsService, MarketDataService (raw SQLAlchemy lookups) 50 of 54 handlers converted to sx, 4 Python fallbacks remain (ghost-sync/push-member, clear-cart-for-order, create-order). Net: -1,383 lines Python, +251 lines modified. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
137
shared/services/page_config_impl.py
Normal file
137
shared/services/page_config_impl.py
Normal file
@@ -0,0 +1,137 @@
|
||||
"""SQL implementation of PageConfig service methods.
|
||||
|
||||
Extracted from blog/bp/data/routes.py and blog/bp/actions/routes.py
|
||||
to enable sx defquery/defaction conversion.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
|
||||
from shared.models.page_config import PageConfig
|
||||
|
||||
|
||||
def _to_dict(pc: PageConfig) -> dict[str, Any]:
|
||||
return {
|
||||
"id": pc.id,
|
||||
"container_type": pc.container_type,
|
||||
"container_id": pc.container_id,
|
||||
"features": pc.features or {},
|
||||
"sumup_merchant_code": pc.sumup_merchant_code,
|
||||
"sumup_api_key": pc.sumup_api_key,
|
||||
"sumup_checkout_prefix": pc.sumup_checkout_prefix,
|
||||
}
|
||||
|
||||
|
||||
class SqlPageConfigService:
|
||||
|
||||
async def ensure(
|
||||
self, session: AsyncSession, *,
|
||||
container_type: str = "page", container_id: int,
|
||||
) -> dict[str, Any]:
|
||||
"""Get or create a PageConfig. Returns minimal dict with id."""
|
||||
row = (await session.execute(
|
||||
select(PageConfig).where(
|
||||
PageConfig.container_type == container_type,
|
||||
PageConfig.container_id == container_id,
|
||||
)
|
||||
)).scalar_one_or_none()
|
||||
|
||||
if row is None:
|
||||
row = PageConfig(
|
||||
container_type=container_type,
|
||||
container_id=container_id,
|
||||
features={},
|
||||
)
|
||||
session.add(row)
|
||||
await session.flush()
|
||||
|
||||
return {
|
||||
"id": row.id,
|
||||
"container_type": row.container_type,
|
||||
"container_id": row.container_id,
|
||||
}
|
||||
|
||||
async def get_by_container(
|
||||
self, session: AsyncSession, *,
|
||||
container_type: str = "page", container_id: int,
|
||||
) -> dict[str, Any] | None:
|
||||
pc = (await session.execute(
|
||||
select(PageConfig).where(
|
||||
PageConfig.container_type == container_type,
|
||||
PageConfig.container_id == container_id,
|
||||
)
|
||||
)).scalar_one_or_none()
|
||||
return _to_dict(pc) if pc else None
|
||||
|
||||
async def get_by_id(
|
||||
self, session: AsyncSession, *, id: int,
|
||||
) -> dict[str, Any] | None:
|
||||
pc = await session.get(PageConfig, id)
|
||||
return _to_dict(pc) if pc else None
|
||||
|
||||
async def get_batch(
|
||||
self, session: AsyncSession, *,
|
||||
container_type: str = "page", ids: list[int],
|
||||
) -> list[dict[str, Any]]:
|
||||
if not ids:
|
||||
return []
|
||||
result = await session.execute(
|
||||
select(PageConfig).where(
|
||||
PageConfig.container_type == container_type,
|
||||
PageConfig.container_id.in_(ids),
|
||||
)
|
||||
)
|
||||
return [_to_dict(pc) for pc in result.scalars().all()]
|
||||
|
||||
async def update(
|
||||
self, session: AsyncSession, *,
|
||||
container_type: str = "page", container_id: int,
|
||||
features: dict | None = None,
|
||||
sumup_merchant_code: str | None = None,
|
||||
sumup_checkout_prefix: str | None = None,
|
||||
sumup_api_key: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
pc = (await session.execute(
|
||||
select(PageConfig).where(
|
||||
PageConfig.container_type == container_type,
|
||||
PageConfig.container_id == container_id,
|
||||
)
|
||||
)).scalar_one_or_none()
|
||||
|
||||
if pc is None:
|
||||
pc = PageConfig(
|
||||
container_type=container_type,
|
||||
container_id=container_id,
|
||||
features=features or {},
|
||||
)
|
||||
session.add(pc)
|
||||
await session.flush()
|
||||
|
||||
if features is not None:
|
||||
merged = dict(pc.features or {})
|
||||
for key, val in features.items():
|
||||
if isinstance(val, bool):
|
||||
merged[key] = val
|
||||
elif val in ("true", "1", "on"):
|
||||
merged[key] = True
|
||||
elif val in ("false", "0", "off", None):
|
||||
merged[key] = False
|
||||
pc.features = merged
|
||||
flag_modified(pc, "features")
|
||||
|
||||
if sumup_merchant_code is not None:
|
||||
pc.sumup_merchant_code = sumup_merchant_code or None
|
||||
if sumup_checkout_prefix is not None:
|
||||
pc.sumup_checkout_prefix = sumup_checkout_prefix or None
|
||||
if sumup_api_key is not None:
|
||||
pc.sumup_api_key = sumup_api_key or None
|
||||
|
||||
await session.flush()
|
||||
|
||||
result = _to_dict(pc)
|
||||
result["sumup_configured"] = bool(pc.sumup_api_key)
|
||||
return result
|
||||
Reference in New Issue
Block a user