Skip to content

Commit 4a2abbc

Browse files
anth-volkclaude
andcommitted
Pass the user list filters, row listings and sign-in identity as one value each
The PLR0913 findings in the code #3891 added: - The user-household and user-policy services, their route Protocols and routes take an optional UserHouseholdFilter or UserPolicyFilter (the v2 and v1 ids to narrow a listing to) instead of two keyword arguments, as the metadata layers take a ParameterValueFilter. The routes build the filter from the same query fields. - read_user_report_rows and read_user_simulation_rows take a UserReportListing or UserSimulationListing, as the household and policy row readers take their listings. - create_user_identity takes the UserPrincipal whose issuer, subject and email it stores, instead of the three fields. The statements, their parameters and the OpenAPI documents are unchanged. The route and service tests, and the concurrent sign-in test's stand-in, use the new arguments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent aa5766e commit 4a2abbc

19 files changed

Lines changed: 145 additions & 79 deletions

File tree

‎policyengine_api/fastapi_routes/dependencies.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,14 @@
6262
)
6363
from policyengine_api.services.v2.user_households.types import (
6464
UserHouseholdCreateFields,
65+
UserHouseholdFilter,
6566
UserHouseholdPage,
6667
UserHouseholdRead,
6768
UserHouseholdUpdateInput,
6869
)
6970
from policyengine_api.services.v2.user_policies.types import (
7071
UserPolicyCreateFields,
72+
UserPolicyFilter,
7173
UserPolicyPage,
7274
UserPolicyRead,
7375
UserPolicyUpdateInput,
@@ -408,8 +410,7 @@ def list_user_policies(
408410
*,
409411
principal: AccountPrincipal,
410412
country_id: str,
411-
policy_id: UUID | None = None,
412-
legacy_policy_id: int | None = None,
413+
policy_filter: UserPolicyFilter | None = None,
413414
offset: int = 0,
414415
limit: int = 100,
415416
) -> UserPolicyPage:
@@ -468,8 +469,7 @@ def list_user_households(
468469
*,
469470
principal: AccountPrincipal,
470471
country_id: str,
471-
household_id: UUID | None = None,
472-
legacy_household_id: int | None = None,
472+
household_filter: UserHouseholdFilter | None = None,
473473
offset: int = 0,
474474
limit: int = 100,
475475
) -> UserHouseholdPage:

‎policyengine_api/fastapi_routes/v2/user_households/routes.py‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
UserHouseholdUpdateQuery,
3939
)
4040
from policyengine_api.services.v2.identity.types import AccountPrincipal
41+
from policyengine_api.services.v2.user_households.types import UserHouseholdFilter
4142

4243
UserHouseholdCreateQueryDependency = Annotated[
4344
UserHouseholdCreateQuery, Depends(query_dependency(UserHouseholdCreateQuery))
@@ -160,8 +161,10 @@ def read() -> UserHouseholdPageResponse:
160161
page = service_factory().list_user_households(
161162
principal=principal,
162163
country_id=query.country_id,
163-
household_id=query.household_id,
164-
legacy_household_id=query.legacy_household_id,
164+
household_filter=UserHouseholdFilter(
165+
household_id=query.household_id,
166+
legacy_household_id=query.legacy_household_id,
167+
),
165168
offset=query.offset,
166169
limit=query.limit,
167170
)

‎policyengine_api/fastapi_routes/v2/user_policies/routes.py‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
UserPolicyCollectionQuery,
3636
)
3737
from policyengine_api.services.v2.identity.types import AccountPrincipal
38+
from policyengine_api.services.v2.user_policies.types import UserPolicyFilter
3839

3940
CountryQueryDependency = Annotated[
4041
CountryQuery, Depends(query_dependency(CountryQuery))
@@ -151,8 +152,10 @@ def read() -> UserPolicyPageResponse:
151152
page = service_factory().list_user_policies(
152153
principal=principal,
153154
country_id=query.country_id,
154-
policy_id=query.policy_id,
155-
legacy_policy_id=query.legacy_policy_id,
155+
policy_filter=UserPolicyFilter(
156+
policy_id=query.policy_id,
157+
legacy_policy_id=query.legacy_policy_id,
158+
),
156159
offset=query.offset,
157160
limit=query.limit,
158161
)

‎policyengine_api/services/v2/identity/database_connectors/creates.py‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
from datetime import datetime
1414
from uuid import UUID
1515

16+
from policyengine_api.services.v2.identity.types import UserPrincipal
17+
1618

1719
def create_provisioned_user(session: Session, *, primary_country: str) -> User:
1820
"""Insert a standard user for a new sign-in identity and return it with its ID."""
@@ -42,19 +44,17 @@ def create_user_identity(
4244
session: Session,
4345
*,
4446
user_id: UUID,
45-
issuer: str,
46-
subject: str,
47-
email: str | None,
47+
principal: UserPrincipal,
4848
seen_at: datetime,
4949
) -> UUID | None:
50-
"""Insert one identity, returning its user unless another request won."""
50+
"""Insert the principal's identity; return its user unless another request won."""
5151
return session.execute(
5252
insert(UserIdentity)
5353
.values(
5454
user_id=user_id,
55-
issuer=issuer,
56-
subject=subject,
57-
email=email,
55+
issuer=principal.issuer,
56+
subject=principal.subject,
57+
email=principal.email,
5858
last_seen_at=seen_at,
5959
)
6060
.on_conflict_do_nothing(constraint="uq_user_identities_issuer_subject")

‎policyengine_api/services/v2/identity/services.py‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,7 @@ def resolve_in_session(
141141
created_user_id = create_user_identity(
142142
session,
143143
user_id=user.id,
144-
issuer=principal.issuer,
145-
subject=principal.subject,
146-
email=principal.email,
144+
principal=principal,
147145
seen_at=seen_at,
148146
)
149147
if created_user_id is None:
@@ -339,9 +337,7 @@ def convert_unauthenticated_user_in_session(
339337
and create_user_identity(
340338
session,
341339
user_id=unauthenticated_user.id,
342-
issuer=principal.issuer,
343-
subject=principal.subject,
344-
email=principal.email,
340+
principal=principal,
345341
seen_at=seen_at,
346342
)
347343
is not None

‎policyengine_api/services/v2/user_households/services.py‎

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@
3939
user_household_page,
4040
user_household_read,
4141
)
42-
from policyengine_api.services.v2.user_households.types import UserHouseholdListing
42+
from policyengine_api.services.v2.user_households.types import (
43+
UserHouseholdFilter,
44+
UserHouseholdListing,
45+
)
4346
from policyengine_api.services.v2.user_households.validators import (
4447
require_user_household,
4548
resolve_association_household_id,
@@ -223,16 +226,18 @@ def list_user_households(
223226
*,
224227
principal: AccountPrincipal,
225228
country_id: str,
226-
household_id: UUID | None = None,
227-
legacy_household_id: int | None = None,
229+
household_filter: UserHouseholdFilter | None = None,
228230
offset: int = 0,
229231
limit: int = 100,
230232
) -> UserHouseholdPage:
231233
"""Return a page of the principal's associations in this country.
232234
233-
``household_id`` and ``legacy_household_id`` narrow it to one household. A
234-
signed-out browser that has not saved anything yet gets an empty page.
235+
``household_filter`` narrows it to one household. A signed-out browser
236+
that has not saved anything yet gets an empty page.
235237
"""
238+
selected = (
239+
household_filter if household_filter is not None else UserHouseholdFilter()
240+
)
236241
with self._database_session.transaction() as session:
237242
owner = resolve_principal_user_for_read_in_session(
238243
session, principal, country_id=country_id
@@ -244,8 +249,8 @@ def list_user_households(
244249
UserHouseholdListing(
245250
country_id=country_id,
246251
user_id=owner.user_id,
247-
household_id=household_id,
248-
legacy_household_id=legacy_household_id,
252+
household_id=selected.household_id,
253+
legacy_household_id=selected.legacy_household_id,
249254
offset=offset,
250255
limit=limit,
251256
),

‎policyengine_api/services/v2/user_households/types.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,18 @@ class UserHouseholdRead:
104104
updated_at: datetime
105105

106106

107+
@dataclass(frozen=True)
108+
class UserHouseholdFilter:
109+
"""Which of the caller's saved households a listing keeps.
110+
111+
``household_id`` keeps links to one v2 household and ``legacy_household_id``
112+
links to one v1 household; with neither, every saved household is kept.
113+
"""
114+
115+
household_id: UUID | None = None
116+
legacy_household_id: int | None = None
117+
118+
107119
@dataclass(frozen=True)
108120
class UserHouseholdListing:
109121
"""One page of a user's associations to read, optionally for one household.

‎policyengine_api/services/v2/user_policies/services.py‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
LegacyUserPolicySnapshot,
6161
UserPolicyCreateFields,
6262
UserPolicyCreationInput,
63+
UserPolicyFilter,
6364
UserPolicyListing,
6465
UserPolicyPage,
6566
UserPolicyRead,
@@ -460,16 +461,16 @@ def list_user_policies(
460461
*,
461462
principal: AccountPrincipal,
462463
country_id: str,
463-
policy_id: UUID | None = None,
464-
legacy_policy_id: int | None = None,
464+
policy_filter: UserPolicyFilter | None = None,
465465
offset: int = 0,
466466
limit: int = 100,
467467
) -> UserPolicyPage:
468468
"""Return a page of the principal's associations in this country.
469469
470-
``policy_id`` and ``legacy_policy_id`` narrow it to one policy. A signed-out
471-
browser that has not saved anything yet gets an empty page.
470+
``policy_filter`` narrows it to one policy. A signed-out browser that has
471+
not saved anything yet gets an empty page.
472472
"""
473+
selected = policy_filter if policy_filter is not None else UserPolicyFilter()
473474
with self._database_session.transaction() as session:
474475
owner = resolve_principal_user_for_read_in_session(
475476
session, principal, country_id=country_id
@@ -481,10 +482,10 @@ def list_user_policies(
481482
UserPolicyListing(
482483
country_id=country_id,
483484
user_id=owner.user_id,
484-
policy_id=policy_id,
485+
policy_id=selected.policy_id,
485486
offset=offset,
486487
limit=limit,
487-
legacy_policy_id=legacy_policy_id,
488+
legacy_policy_id=selected.legacy_policy_id,
488489
),
489490
)
490491

‎policyengine_api/services/v2/user_policies/types.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,18 @@ class UserPolicyRead:
163163
updated_at: datetime
164164

165165

166+
@dataclass(frozen=True)
167+
class UserPolicyFilter:
168+
"""Which of the caller's saved policies a listing keeps.
169+
170+
``policy_id`` keeps links to one v2 policy and ``legacy_policy_id`` links to
171+
one v1 policy; with neither, every saved policy is kept.
172+
"""
173+
174+
policy_id: UUID | None = None
175+
legacy_policy_id: int | None = None
176+
177+
166178
@dataclass(frozen=True)
167179
class UserPolicyListing:
168180
"""One page of a user's associations to read, optionally for one policy.

‎policyengine_api/services/v2/user_reports/database_connectors/reads.py‎

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
if TYPE_CHECKING:
1212
from uuid import UUID
1313

14+
from policyengine_api.services.v2.user_reports.types import UserReportListing
15+
1416

1517
def read_owned_user_report_row(
1618
session: Session,
@@ -46,34 +48,30 @@ def read_user_report_row(
4648

4749
def read_user_report_rows(
4850
session: Session,
49-
*,
50-
country_id: str,
51-
user_id: UUID,
52-
legacy_report_id: int | None,
53-
offset: int,
54-
limit: int,
51+
listing: UserReportListing,
5552
) -> list[UserReportAssociation]:
5653
"""Return up to ``limit + 1`` of a user's associations in creation order.
5754
58-
With ``legacy_report_id``, only associations to that v1 report output are
59-
returned. The extra row lets the caller report whether another page exists.
55+
With the listing's ``legacy_report_id``, only associations to that v1 report
56+
output are returned. The extra row lets the caller report whether another
57+
page exists.
6058
"""
6159
statement = select(UserReportAssociation).where(
62-
UserReportAssociation.country_id == country_id,
63-
UserReportAssociation.user_id == user_id,
60+
UserReportAssociation.country_id == listing.country_id,
61+
UserReportAssociation.user_id == listing.user_id,
6462
)
65-
if legacy_report_id is not None:
63+
if listing.legacy_report_id is not None:
6664
statement = statement.where(
67-
UserReportAssociation.legacy_report_id == legacy_report_id
65+
UserReportAssociation.legacy_report_id == listing.legacy_report_id
6866
)
6967
return list(
7068
session.exec(
7169
statement.order_by(
7270
col(UserReportAssociation.created_at),
7371
col(UserReportAssociation.id),
7472
)
75-
.offset(offset)
76-
.limit(limit + 1)
73+
.offset(listing.offset)
74+
.limit(listing.limit + 1)
7775
).all()
7876
)
7977

0 commit comments

Comments
 (0)