Skip to content

feat(client): API parity — 30+ methods, ordering kwargs, webhook write fixes#25

Draft
makegov-hal[bot] wants to merge 5 commits into
mainfrom
feat/api-parity
Draft

feat(client): API parity — 30+ methods, ordering kwargs, webhook write fixes#25
makegov-hal[bot] wants to merge 5 commits into
mainfrom
feat/api-parity

Conversation

@makegov-hal
Copy link
Copy Markdown

@makegov-hal makegov-hal Bot commented May 12, 2026

Summary

Brings tango-python to full API parity with Tango: 30+ new methods, signature fixes for write paths, ordering kwargs on list endpoints that were silently missing them, and four new typed response models. 278 unit tests pass, 45/45 smoke checks pass against a running Tango instance, 80% line coverage across the package.

Branch: 5 commits on feat/api-parity off main.

What's new

Resolve / Validate utilities (typed wrappers)

Two new top-level methods replace the prior client._post("/api/resolve/", ...) workaround:

  • resolve(name, target_type, ...) — POST /api/resolve/. Returns a typed ResolveResult with ResolveCandidate entries (identifier, display_name, match_tier on Pro+). Optional context: state, city, context string. Backed by new dataclasses exported from the package root.
  • validate(identifier_type, value) — POST /api/validate/. Returns ValidateResult.

Webhook alerts CRUD parity

The convenience layer over /api/webhooks/alerts/ (filter subscriptions) was previously create-only. Full CRUD now:

  • list_webhook_alerts(...)
  • get_webhook_alert(id)
  • create_webhook_alert(name=, query_type=, filters=, frequency=, cron_expression=)
  • update_webhook_alert(id, ...)
  • delete_webhook_alert(id)

WebhookAlert dataclass exported from tango.

Webhook write-method signatures, completed

The SDK's write methods were missing fields that the Tango API has required since multi-endpoint support landed. Catching up:

  • create_webhook_endpoint now accepts name= (keyword-only). Required by the server; omitting it emits a DeprecationWarning and will become an error in a future major version.
  • create_webhook_subscription accepts endpoint=, subscription_type=, query_type=, filter_definition=, frequency=, cron_expression=, is_active= — covers both subject and filter subscription patterns through the canonical /api/webhooks/subscriptions/ API.
  • update_webhook_subscription accepts frequency=, cron_expression=, is_active=.
  • update_webhook_endpoint accepts name= for renames.

Reference data

list_departments, get_department, list_psc, get_psc, get_psc_metrics, get_naics, get_naics_metrics, get_business_type, list_assistance_listings, get_assistance_listing, list_mas_sins, get_mas_sin.

Entity sub-resources

list_entity_contracts, list_entity_idvs, list_entity_otas, list_entity_otidvs, list_entity_subawards, list_entity_lcats, get_entity_metrics. All shape-aware where the underlying endpoint supports shaping.

IDV + agency sub-resources

  • list_idv_lcats
  • list_agency_awarding_contracts, list_agency_funding_contracts

Misc

  • search_opportunity_attachments(q, top_k, include_extracted_text) for /api/opportunities/attachment-search/
  • get_version() for /api/version/
  • list_api_keys() for /api/api-keys/

ordering parameter where the API supports it

Seven list methods now accept ordering= and pass it through:

list_forecasts, list_grants, list_subawards, list_gsa_elibrary_contracts, list_opportunities, list_notices, list_protests. Prefix with - for descending. This closes parity gaps where the API documented ?ordering= but the SDK silently rejected the kwarg.

Fixed

  • TangoClient._post() and _patch() now accept both json_data= (positional, original) and json= (kwarg). Internal callers and docs examples that use json= no longer fail with TypeError.

Testing

  • pytest: 412 passed, 31 skipped — was 238 before this branch; +40 new tests in tests/test_api_parity.py.
  • 80% line coverage across tango/ (pytest --cov).
  • mypy clean on changed code; ruff clean.
  • Smoke (scripts/smoke_api_parity.py against http://localhost:8000): 45/45 pass. Three blocks SKIPped for environmental reasons (/attachment-search/ 404 locally, /funding-contracts/ 504 on the test agency, create_webhook_alert skipped when the test account has multiple endpoints — see makegov/tango#2256).
  • All smoke-test webhook resources cleaned up — zero leftover endpoints/subs/alerts.

Decisions worth flagging

  • Kept uuid= on vehicle methods (list_vehicle_awardees, list_vehicle_orders). Matches the existing get_vehicle(uuid) path-param convention. SDK convention: IDVs use key=, vehicles use uuid=.

Known issues uncovered during parity work (tracked separately on tango)

  • makegov/tango#2252 — Webhooks v2 inconsistency: /subscriptions/ POST uses field endpoint; /endpoints/test-delivery/ uses endpoint_id.
  • makegov/tango#2254 — OpenAPI advertises ordering on /notices/, /protests/, /subawards/ but viewsets reject most values.
  • makegov/tango#2256 — Multi-endpoint accounts can't use /api/webhooks/alerts/; backend needs to accept an explicit endpoint field on the alert create serializer before the SDKs can paper over the limitation.

Risks

  • No breaking changes to existing public methods. New kwargs are keyword-only with safe defaults; backward-compatible across the board.
  • DeprecationWarning on create_webhook_endpoint without name= may surface in user logs — intentional, since the API will eventually reject name-less requests outright.

Sibling work

Companion PR in tango-node covers the same parity work for the Node SDK. Docs are updated to reflect both SDKs' new surfaces in makegov/docs#9.

– Hal 🤖

infinityplusone and others added 5 commits May 11, 2026 20:02
Internal HTTP helpers previously accepted only positional `json_data`.
Docs examples and some user code (and a few SDK callers) pass `json=`,
which raised `TypeError`. Make both work — `json_data=` (positional or
keyword) and `json=` (keyword-only) both supply the request body. Empty
body defaults to `{}` for callers that POST/PATCH with no payload.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
These endpoints all accept `?ordering=` server-side (per OpenAPI), but
the SDK methods didn't expose it. Add explicit `ordering: str | None`
to:
- list_forecasts
- list_grants
- list_subawards
- list_gsa_elibrary_contracts
- list_opportunities
- list_notices
- list_protests

Pattern matches the existing `ordering` on list_otas / list_otidvs /
list_contracts / list_idvs / list_vehicles / list_vehicle_orders.
Prefix with `-` for descending.

list_entities, list_vehicle_awardees, and list_idv_transactions
intentionally NOT updated — those endpoints don't expose `ordering` in
their OpenAPI parameters. list_idv_awards / list_idv_child_idvs already
accept `**kwargs`, so `ordering=` flows through unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Tango webhooks API requires `name` on WebhookEndpoint create (the
server enforces unique(user, name)), and `endpoint` UUID on
WebhookSubscription create when a user owns more than one endpoint. The
SDK's create methods previously didn't accept either, forcing callers to
fall back to raw `_post` or use the `/api/webhooks/alerts/` convenience
API.

Changes:
- create_webhook_endpoint: add keyword-only `name=`. Currently optional
  for backward compatibility; emits DeprecationWarning when omitted and
  will become required in a future major version. Documents the
  server-side 400 callers see today when they skip it.
- create_webhook_subscription: add keyword-only `endpoint=`,
  `subscription_type=`, `query_type=`, `filter_definition=`,
  `frequency=`, `cron_expression=`, `is_active=`. Mirrors the full
  WebhookSubscriptionSerializer surface.
- update_webhook_subscription: add `frequency=`, `cron_expression=`,
  `is_active=` so filter subscriptions can be paused / re-frequencied
  via the canonical PATCH route.
- update_webhook_endpoint: add `name=` so endpoints can be renamed.

All additions are keyword-only and backward compatible — existing
call sites continue to work unchanged. The single in-tree test that
constructs a webhook endpoint is updated to pass `name="primary"` so
it doesn't trip the new DeprecationWarning.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Surveyed the OpenAPI schema and added the methods that were missing
from the SDK. Existing list_/get_ patterns preserved throughout —
shape-aware where the underlying endpoint supports shaping, plain
dict-returning where it doesn't.

Webhook alerts (convenience layer over filter subscriptions):
- list_webhook_alerts / get_webhook_alert / create_webhook_alert
  / update_webhook_alert / delete_webhook_alert
- New WebhookAlert dataclass

Resolve / validate (POST-based identity helpers):
- resolve(name, target_type, ...) — entity/org name disambiguation
- validate(identifier_type, value) — PIID/solicitation/UEI format check
- New ResolveCandidate, ResolveResult, ValidateResult dataclasses

Reference data:
- list_departments, get_department
- list_psc, get_psc, get_psc_metrics
- get_naics, get_naics_metrics
- get_business_type
- list_assistance_listings, get_assistance_listing
- list_mas_sins, get_mas_sin

Entity sub-resources (all under /api/entities/{uei}/...):
- list_entity_contracts, list_entity_idvs
- list_entity_otas, list_entity_otidvs
- list_entity_subawards, list_entity_lcats
- get_entity_metrics

IDV sub-resources:
- list_idv_lcats

Agency sub-resources:
- list_agency_awarding_contracts
- list_agency_funding_contracts

Misc:
- search_opportunity_attachments(q, top_k, include_extracted_text)
- get_version()
- list_api_keys()

Decisions:
- Kept `uuid=` for vehicle methods to match `get_vehicle(uuid)` and the
  existing list_vehicle_awardees / list_vehicle_orders surface — these
  match the API path param. IDVs use `key=`, vehicles use `uuid=`, and
  the SDK stays consistent.
- Stayed away from /api/accounts/usage/ admin endpoints, /api/events/,
  /api/news/, and /api/company/rag/ — niche internal surfaces. Easy to
  add when there's user demand.
- list_entities, list_vehicle_awardees, and list_idv_transactions are
  not getting `ordering` because their server-side parameters don't
  advertise it (verified against OpenAPI).

Backward compatible — all additions, no existing call sites changed.
Existing tests still pass (238/238).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two pieces:

1. tests/test_api_parity.py — 40 mock-driven unit tests covering:
   - `_post`/`_patch` json kwarg aliases
   - resolve / validate (POST endpoints) request shape + response parsing
   - WebhookAlert CRUD (create / update / list)
   - WebhookEndpoint create/update with name= (and DeprecationWarning
     when name= is omitted)
   - WebhookSubscription create with endpoint=, subscription_type=,
     filter fields
   - ordering= threading on the seven affected list_* methods
   - Reference-data and entity/agency sub-resource URL construction

2. scripts/smoke_api_parity.py — live smoke test that hits every method
   the branch added or changed against a running Tango. Skips
   gracefully when the local server can't service a particular
   endpoint (404 for attachment-search without the RAG index; 504 from
   funding-contracts aggregation; alerts can't auto-resolve endpoint
   when the test user owns multiple). Creates webhook endpoints +
   subscriptions, verifies them, and tears them down. Local run on
   2026-05-11: 45/45 PASS (with three SKIPs for the environmental
   reasons above).

Also includes a trivial ruff-format collapse in models.py
(VEHICLE_ORDERS_MINIMAL one-liner).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@infinityplusone infinityplusone requested review from Copilot and vdavez May 12, 2026 03:05
@infinityplusone infinityplusone changed the title feat\(client\): API parity — 30+ methods, ordering kwargs, webhook write fixes feat\client): API parity — 30+ methods, ordering kwargs, webhook write fixes May 12, 2026
@infinityplusone infinityplusone changed the title feat\client): API parity — 30+ methods, ordering kwargs, webhook write fixes feat\client: API parity — 30+ methods, ordering kwargs, webhook write fixes May 12, 2026
@infinityplusone infinityplusone changed the title feat\client: API parity — 30+ methods, ordering kwargs, webhook write fixes feat(client): API parity — 30+ methods, ordering kwargs, webhook write fixes May 12, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR expands tango-python toward full Tango API parity by adding many missing client methods and typed models, plus fixing webhook write signatures and request-body kwarg compatibility to match documented usage.

Changes:

  • Added Resolve/Validate POST helpers with typed response models (ResolveResult, ValidateResult) and exported them at the package root.
  • Implemented webhook alerts CRUD plus webhook endpoint/subscription write-signature parity (including name= on endpoint creation and additional subscription fields).
  • Added ordering= passthrough on several list endpoints and introduced a comprehensive unit-test suite + smoke script for the parity surface.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/test_client.py Updates webhook endpoint CRUD test to include name= and assert it is sent in the request body.
tests/test_api_parity.py Adds extensive mock-driven tests covering new parity methods, ordering passthrough, and _post/_patch json kwarg aliasing.
tango/models.py Introduces new dataclasses (WebhookAlert, ResolveCandidate/Result, ValidateResult) and minor shape string formatting change.
tango/client.py Adds _post/_patch json= alias support, ordering= kwargs, webhook write fixes, webhook alerts CRUD, resolve/validate endpoints, and multiple reference/sub-resource list/get methods.
tango/init.py Exports new parity models from the package root.
scripts/smoke_api_parity.py Adds an end-to-end smoke script to exercise parity methods against a running Tango instance with cleanup.
CHANGELOG.md Documents newly added methods/kwargs and the _post/_patch compatibility fix.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tango/client.py
Comment on lines +209 to +224
def _post(
self,
endpoint: str,
json_data: dict[str, Any] | None = None,
*,
json: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Make a POST request.

Accepts either ``json_data`` (positional) or ``json=`` (keyword) for
backward compatibility with internal callers and docs examples.
"""
body = json_data if json_data is not None else json
if body is None:
body = {}
return self._request("POST", endpoint, json_data=body)
Comment thread tango/client.py
Args:
code: PSC code.
months: Window size in months (e.g. 6, 12, 24, 36).
period_grouping: ``monthly``, ``quarterly``, etc.
Comment thread CHANGELOG.md
Comment on lines +10 to +12
### Added
- `ordering` parameter on `list_forecasts`, `list_grants`, `list_subawards`, `list_gsa_elibrary_contracts`, `list_opportunities`, `list_notices`, and `list_protests`. Prefix with `-` for descending. Closes a parity gap with the API surface (these endpoints all accept `?ordering=` server-side).
- `create_webhook_endpoint` accepts `name=` (keyword-only). Required by the Tango API since multi-endpoint support landed; omitting it now emits a `DeprecationWarning` and will become an error in a future major version.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants