From 927d5c20c39a26d0b69b24312c249cab5e9d6564 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sat, 24 Oct 2026 21:40:42 +0000 Subject: [PATCH 1/2] feat(reporting-storage): let the Postgres service factory build a subclass postgres_reliable_reporting_service always constructed the base ReliableReportingService, while ReliableReportingService.memory builds cls. Hosts override the installed tool methods on a subclass, so the Embedded Sales Agent had to copy the factory body to keep its subclass. Add a service_class keyword, typed so the call returns the subclass. Fixes #1588 Co-Authored-By: Claude Opus 5.5 (1M context) --- .../src/adcp_reporting_storage/service.py | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/packages/adcp-reporting-storage/src/adcp_reporting_storage/service.py b/packages/adcp-reporting-storage/src/adcp_reporting_storage/service.py index a8fa46d38..8d202bfe4 100644 --- a/packages/adcp-reporting-storage/src/adcp_reporting_storage/service.py +++ b/packages/adcp-reporting-storage/src/adcp_reporting_storage/service.py @@ -4,7 +4,7 @@ from collections.abc import Callable from datetime import datetime -from typing import Any +from typing import Any, TypeVar, overload from adcp.reporting.service import ( ReliableReportingService, @@ -15,6 +15,34 @@ __all__ = ["postgres_reliable_reporting_service"] +_ServiceT = TypeVar("_ServiceT", bound=ReliableReportingService) + + +@overload +def postgres_reliable_reporting_service( + *, + pool: Any, + account_context: ReportingContextResolver, + caller_resolver: ReportingCallerResolver | None = ..., + clock: Callable[[], datetime] | None = ..., + production: ReportingProductionOptions | None = ..., + service_class: None = ..., + **kwargs: Any, +) -> ReliableReportingService: ... + + +@overload +def postgres_reliable_reporting_service( + *, + pool: Any, + account_context: ReportingContextResolver, + caller_resolver: ReportingCallerResolver | None = ..., + clock: Callable[[], datetime] | None = ..., + production: ReportingProductionOptions | None = ..., + service_class: type[_ServiceT], + **kwargs: Any, +) -> _ServiceT: ... + def postgres_reliable_reporting_service( *, @@ -23,6 +51,7 @@ def postgres_reliable_reporting_service( caller_resolver: ReportingCallerResolver | None = None, clock: Callable[[], datetime] | None = None, production: ReportingProductionOptions | None = None, + service_class: type[ReliableReportingService] | None = None, **kwargs: Any, ) -> ReliableReportingService: """Create durable ledger, staging and seal stores over a borrowed pool. @@ -32,6 +61,10 @@ def postgres_reliable_reporting_service( Startup installs all SDK-owned schemas. Explicit source-store overrides remain the adopter's responsibility. No pool is opened or closed here. + ``service_class`` builds an adopter's ``ReliableReportingService`` + subclass, as ``SubClass.memory(...)`` does, so a host can override the + installed tool methods without copying this composition. + This is what ``ReliableReportingService.postgres(...)`` was before the storage implementations moved to this distribution. """ @@ -45,7 +78,8 @@ def postgres_reliable_reporting_service( else PgReportingLedgerStore(pool=pool, clock=clock) ) staging = PgReportingStagingStore(pool=pool) - return ReliableReportingService( + cls = ReliableReportingService if service_class is None else service_class + return cls( store=store, account_context=account_context, caller_resolver=caller_resolver, From dd57324b0dbe93e24abd7b3c63410e56bb22d0f6 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sat, 24 Oct 2026 21:41:36 +0000 Subject: [PATCH 2/2] test(reporting-storage): cover the Postgres factory's service_class Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/test_reporting_storage_service_class.py | 35 +++++++++++++++++++ .../reporting_storage_service_class.py | 21 +++++++++++ 2 files changed, 56 insertions(+) create mode 100644 tests/test_reporting_storage_service_class.py create mode 100644 tests/type_checks/reporting_storage_service_class.py diff --git a/tests/test_reporting_storage_service_class.py b/tests/test_reporting_storage_service_class.py new file mode 100644 index 000000000..1e79b502a --- /dev/null +++ b/tests/test_reporting_storage_service_class.py @@ -0,0 +1,35 @@ +"""``postgres_reliable_reporting_service`` builds an adopter's service subclass (#1588).""" + +from __future__ import annotations + +import pytest + +pytest.importorskip("psycopg_pool") + +from adcp_reporting_storage import postgres_reliable_reporting_service # noqa: E402 +from psycopg_pool import AsyncConnectionPool # noqa: E402 + +from adcp.reporting import ReliableReportingService # noqa: E402 +from tests.test_reliable_reporting_service import _account_context # noqa: E402 + + +class HostReportingService(ReliableReportingService): + """A host subclass overriding an installed tool method.""" + + +def _pool() -> AsyncConnectionPool: + return AsyncConnectionPool("postgresql://unused", open=False) + + +def test_default_builds_the_base_service() -> None: + service = postgres_reliable_reporting_service(pool=_pool(), account_context=_account_context) + assert type(service) is ReliableReportingService + + +def test_service_class_builds_the_subclass_like_memory() -> None: + pg = postgres_reliable_reporting_service( + pool=_pool(), account_context=_account_context, service_class=HostReportingService + ) + memory = HostReportingService.memory(account_context=_account_context) + assert type(pg) is HostReportingService + assert type(memory) is HostReportingService diff --git a/tests/type_checks/reporting_storage_service_class.py b/tests/type_checks/reporting_storage_service_class.py new file mode 100644 index 000000000..03ee37f82 --- /dev/null +++ b/tests/type_checks/reporting_storage_service_class.py @@ -0,0 +1,21 @@ +"""Adopter-facing type contract: the Postgres factory returns the requested subclass.""" + +from typing import Any + +from adcp_reporting_storage import postgres_reliable_reporting_service + +from adcp.reporting import ReliableReportingService + + +class HostReportingService(ReliableReportingService): + pass + + +def build(pool: Any, account_context: Any) -> HostReportingService: + return postgres_reliable_reporting_service( + pool=pool, account_context=account_context, service_class=HostReportingService + ) + + +def build_default(pool: Any, account_context: Any) -> ReliableReportingService: + return postgres_reliable_reporting_service(pool=pool, account_context=account_context)