feat(decisioning): createOAuthPassthroughResolver — Shape B account resolver factory#472
Merged
Merged
Conversation
bokelley
added a commit
that referenced
this pull request
May 3, 2026
… AccountStore-shaped object Address PR #472 review: - BLOCKER 1: change resolver signature to (ref, auth_info=None) to match AccountStore.resolve Protocol. Adopters can now plug the result directly into DecisioningPlatform.accounts. Internally synthesise a ResolveContext for to_account / get_auth_context callbacks so adopter callbacks stay uniform with the rest of the AccountStore surface. - BLOCKER 2: return an AccountStore-shaped class (resolution='explicit', resolve method) instead of a bare callable. Module docstring + example updated to show wiring under DecisioningPlatform.accounts. - Layering: tests now import AccountReferenceById from adcp.types instead of reaching into generated_poc/. - Pagination (option b): documented limitation in module docstring + resolver docstring. Async-iterator support deferred to follow-up to keep fix-pack scope tight. - Soften platform names in module docstring; replace brand placeholders in tests with generic "Globex". - Move _default_extract_rows / _default_auth_context above the public factory for read-order; add direct unit tests for _default_extract_rows edge cases (None body, {}, {"data": null}, {"data": "not_a_list"}, flat list, data envelope).
b2245a1 to
1910046
Compare
…esolver factory Port of @adcp/sdk@6.7's `createOAuthPassthroughResolver` (issue #458). Standardises the canonical "Shape B" account-resolution pattern: an adapter wraps a vendor OAuth + ad-account API (Snap, Meta, TikTok-shaped) and resolves the buyer's `AccountReference` by hitting the upstream's `/me/adaccounts`-shaped listing endpoint with the buyer's bearer. Behaviour: - Only the `{account_id}` discriminated-union arm is handled; natural-key refs and `None` return `None` without calling upstream. - Bearer pass-through via `DynamicBearer` on the upstream client; the factory forwards `ctx.auth_info` to `get_token` by default. - Upstream errors propagate verbatim — the upstream client already projects non-2xx to spec-conformant `AdcpError` codes. - `extract_rows` is a callable (Python diverges from JS's `rowsPath` string) defaulting to flat-list-or-`{"data": []}`. Adopters with deeper-nested shapes pass their own callback. Refs #458, parent #452.
…untStore-shaped object Address code-review BLOCKERS: returned resolver had a (ref, ctx) signature that wouldn't match the framework dispatcher (which calls resolve with auth_info kwarg), and the docstring example referenced a phantom ExplicitAccounts(resolve=) pattern that doesn't exist. - Factory returns _OAuthPassthroughAccountStore: a class whose resolve(ref, auth_info=None) matches the Protocol. Adopters wire the returned object directly into DecisioningPlatform.accounts. - ResolveContext is synthesized inside resolve so adopter callbacks (to_account, get_auth_context) keep the ctx-based API uniform with upsert/list/sync_governance. - Tests now import AccountReferenceById from adcp.types instead of reaching into generated_poc/ (CLAUDE.md layering rule). - Test brand placeholders changed to Globex. - Pagination limitation documented in module + factory docstrings: single GET, paginated upstreams must aggregate inside extract_rows or compose their own resolver. Async-iterator support is a follow-up. - Six unit tests added for _default_extract_rows edge cases.
1910046 to
a260021
Compare
This was referenced May 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Port of
@adcp/sdk@6.7'screateOAuthPassthroughResolver(Shape Baccount-resolver factory). Standardises the canonical pattern where an
adapter wraps a vendor OAuth +
/me/adaccounts-shaped API (Snap, Meta,TikTok-shaped) and resolves the buyer's
AccountReferenceby hittingthe upstream's listing endpoint with the buyer's bearer.
Without this factory, every Shape B adopter copies the same ~30 LOC:
extract bearer from
ctx.auth_info, GET/me/adaccounts, match by id,return the mapped
Account.Design decisions / divergences from JS
extract_rowscallback instead of JS'srowsPathstring. JSships
rowsPath: 'data' | string | null(single-segment only) anddocuments that deeper-nested shapes — TikTok's
data.list— need acustom fetch wrapper. Python takes a callable defaulting to flat-list
or
{"data": [...]}, so deeper nests are a one-liner.optional LRU+TTL listing cache. The Python issue spec (feat(server): createOAuthPassthroughResolver — Shape B account resolver factory #458) doesn't
call for it; deferring to a follow-up keeps this PR scoped to the
resolve primitive. Cache composition can layer on cleanly via
compose_methodonce we need it.DynamicBearer. Upstream client alreadyhandles auth injection; the factory just forwards
ctx.auth_info(overridable via
get_auth_context) toget_token.non-2xx → spec-conformant
AdcpErrorcodes (AUTH_REQUIRED,SERVICE_UNAVAILABLE, etc.). 401 surfaces asAUTH_REQUIRED; 404 onthe listing endpoint becomes
None(the client'streat_404_as_nonedefault), which the factory treats as no rows.Tests cover
AccountNoneNoneref → returnsNonewithout calling upstreamAdcpError(AUTH_REQUIRED)surfacesAdcpError(SERVICE_UNAVAILABLE)surfacesNone(no rows)id_field(e.g.account_uuid)extract_rowsreceives the raw parsed body (TikTok-shapedata.list)extract_rowshandles flat-list-shaped APIsto_accountboth workget_auth_contextforwardsauth_infoverbatimget_auth_contextthreads through toDynamicBearer.get_tokenTest plan
ruff checkon changed filesmypy src/adcp/decisioning/oauth_passthrough.pypytest tests/test_oauth_passthrough.py -v(15/15 pass)pytest tests/ -xregression (3375 passed, 0 failed)Refs #458, parent #452.
🤖 Generated with Claude Code