Skip to content

feat: tango-python 1.0.0 — API parity, webhook overhaul, docs auto-pull, packaging guardrails#25

Merged
infinityplusone merged 21 commits into
mainfrom
feat/api-parity
May 13, 2026
Merged

feat: tango-python 1.0.0 — API parity, webhook overhaul, docs auto-pull, packaging guardrails#25
infinityplusone merged 21 commits into
mainfrom
feat/api-parity

Conversation

@makegov-hal
Copy link
Copy Markdown

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

Status (2026-05-13): This PR now ships as tango-python 1.0.0. The scope grew significantly past the original "API parity" framing — full release notes below. Outstanding Copilot threads on this PR have all been addressed or replied to as stale; the Windows CI flake on test_receiver_404s_on_unknown_path is fixed.

1.0.0 release notes (additions since this PR was opened)

This PR was originally scoped to the API parity work captured below. Over the last week it absorbed three additional changesets and a packaging hardening pass, which collectively justify the major-version bump from 0.6.x → 1.0.0:

  • Webhook signature scheme change (224878e, tango#2252) — generate_signature(body, secret) now returns the canonical "sha256=<hex>" wire form instead of bare hex. Breaking for receivers that consumed the bare hex directly. verify_signature still accepts both forms.
  • Subject-based webhook subscription surface removed (7ede733, tango#2275) — list_webhook_subscriptions / get_webhook_subscription / create_webhook_subscription / update_webhook_subscription / delete_webhook_subscription and the WebhookSubscription / WebhookSubjectTypeDefinition dataclasses are gone. Subject filtering migrates to create_webhook_alert(...) and the alerts API.
  • Shape validator agrees with server on naics(...) / psc(...) expansions (7b233c5, tango#2266) — ShapeParser.validate() now mirrors the server's _EXPAND_ALIASES table; naics_code(...) and psc_code(...) aliases are rewritten to the canonical form at parse time. Closes the parser-vs-server divergence flagged in tango#2266.
  • create_webhook_endpoint(name=...) is now required, not deprecated (132641f, addresses Copilot review). 0.7.0 was going to emit a DeprecationWarning; since 0.7.0 never publicly released, 1.0.0 makes it a hard TangoValidationError instead. The server enforces unique(user, name) so the warn-then-400 path was strictly worse DX.
  • _post / _patch raise on json_data=+json= collision (132641f) — previously silently preferred json_data. Now raises TangoValidationError to surface caller bugs.
  • Doc port + auto-pull (42d760d, makegov/docs#16) — new docs/ERRORS.md, docs/PAGINATION.md, docs/CLIENT.md ported from docs.makegov.com/sdks/python/ so the docs-site auto-pull (makegov/docs#15) can replace the hand-maintained pages.
  • docs-dispatch CI workflow (401207e, makegov/docs#15) — fires on push to main for docs/**, README.md, CHANGELOG.md. Triggers a rebuild of docs.makegov.com when the SDK content changes.
  • Packaging guardrails (06e7feb) — explicit [tool.hatch.build.targets.sdist] whitelist. Even from a developer laptop, uv publish can no longer bundle .mg-tools/, .claude/, CLAUDE.md, ROADMAP.md, work diaries, scratch reports, or .coverage to PyPI. CI publishing was already clean; this closes the local-publish foot-gun.
  • Windows webhook-receiver test fix (132641f) — do_POST now drains the request body before returning a 404, fixing WinError 10053 on windows-latest runners.

CHANGELOG [1.0.0] section reflects everything above. [Unreleased] is empty.


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

This comment was marked as resolved.

infinityplusone and others added 2 commits May 12, 2026 09:23
…inst actual surface

Audit pass on the parity branch's internal docs vs `tango/client.py`, `tango/models.py`, and `tango/webhooks/`.

README.md:
- Fixed unclosed code block in Quick Start (was causing `## Authentication` to render inside the code block).
- Added examples for PSC, `get_naics`, MAS SINs, assistance listings, departments, resolve/validate, IT Dashboard, and entity sub-resources.

docs/API_REFERENCE.md:
- Added sections for previously undocumented methods: PSC, MAS SINs, Assistance Listings, Departments, Business Types (by code), IT Dashboard, Entity Sub-resources, IDV LCATs, Agency Sub-resources, Resolve/Validate, Opportunity Attachments, Webhook Alerts, Utility, `get_naics`/`get_naics_metrics`.
- Removed two stale phantom methods from the ShapeConfig table: `search_contracts` and `list_organization_offices` (neither exists in source).
- Added `VEHICLE_ORDERS_MINIMAL` and `ITDASHBOARD_INVESTMENTS_MINIMAL` rows to the ShapeConfig table.
- Fixed `list_agencies` Parameters list (missing `search` param).
- Fixed `search_opportunity_attachments` signature (had nonexistent `limit`; actual interface is `(q, top_k=None, include_extracted_text=None)`).
- `VEHICLES_MINIMAL` ShapeConfig row updated to match the 17-field constant in `models.py` (was a stale 8-field list).
- Added missing `ITDASHBOARD_INVESTMENTS_COMPREHENSIVE` ShapeConfig row.

docs/DEVELOPERS.md:
- Removed phantom `use_dynamic=True` parameter from all 7 code examples (the param doesn't exist on any client method).
- Removed phantom ShapeConfig constants from examples: `CONTRACTS_SUMMARY`, `CONTRACTS_COMPREHENSIVE`, `CONTRACTS_FOR_ANALYSIS`, `ENTITIES_STANDARD`. Replaced with constants that actually exist.
- Corrected `ENTITIES_MINIMAL` field list (`display_name` -> `legal_business_name`) and `ENTITIES_COMPREHENSIVE` (removed ~9 fields not in the actual constant).

docs/SHAPES.md / docs/WEBHOOKS.md:
- Updated `WebhookReceiver` import paths — it's exported from the top-level `tango` package, not `tango.webhooks`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors `tango-node/docs/DYNAMIC_MODELS.md` so the two SDK repos have matching internal-docs structure. Translated entirely to Python idioms.

Sections:
- Overview
- Components — ShapeParser / SchemaRegistry / TypeGenerator / ModelFactory
- Full Shaping Pipeline (manual)
- Attribute Access — Python-specific addition covering `__getattr__` and its helpful error messages
- Type Safety
- Caching
- Nested Models
- Predefined Shape Constants

Key Python divergences from the Node version (corrected on this side rather than copied):
- `SchemaRegistry.get_schema(ModelClass)` returns a full dict (not Node's `getField()`).
- TypeGenerator uses LRU eviction, not the FIFO the Node doc claims.
- No `ShapedListModel` — that class doesn't exist in the Python codebase.

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

This comment was marked as resolved.

infinityplusone and others added 13 commits May 12, 2026 17:13
Folds in the work from PR #27 (issue-2275-drop-subjects) plus three
cleanup-batch carry-overs from tango's `dogpile/api-cleanup-batch`:

- #2256: add `endpoint` kwarg to create_webhook_alert (multi-endpoint
  accounts can now use the convenience wrapper)
- #2254: remove `ordering` kwarg from list_notices and list_protests
  (server rejects every value)
- #2252: switch test_webhook_delivery to canonical `endpoint` body key

Closes makegov/tango#2275 — part of makegov/tango#2267.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Receivers copy-pasting `generate_signature(...)` into their handler used
to produce a bare hex digest like `"abc123..."` and had to remember to
wrap it as `f"sha256={sig}"` to match the dispatcher's wire format.
Forgetting the wrap silently produced malformed signatures that failed
verification on the other side.

`generate_signature(body, secret)` now returns the full
`"sha256=<lowercase hex>"` form, ready to be assigned to the
`X-Tango-Signature` header directly. `verify_signature` continues to
accept both the prefixed wire form and the bare-hex form so callers
that pre-strip the prefix keep working — added an explicit regression
test for that.

Audited and updated:
- `tango/webhooks/simulate.py` — `sign()` builds the header from the
  prefixed form directly; `SignedRequest.signature` field stays bare hex
- `tango/webhooks/cli.py` — uses `SIGNATURE_PREFIX` for the printed
  signature value (via the SimulationResult.signature bare-hex field)
- `tests/test_webhooks_signing.py` — vectors updated to prefixed form;
  added explicit dual-form acceptance test
- `tests/test_webhooks_receiver.py` — `_post_signed` no longer
  double-wraps the header
- `docs/WEBHOOKS.md` and `docs/API_REFERENCE.md` — receiver examples
  drop the manual `f"sha256={...}"` wrap

Breaking change for direct callers of `generate_signature` who relied
on bare-hex output — pass through `parse_signature_header()` to recover
the previous form. Folded into the v0.7.0 release.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aligns examples with the post-dogpile event-type taxonomy
(only alerts.{opportunity,contract,entity,grant,forecast}.match
emit now).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CLI was missing the `--name` option entirely. Since tango#2267,
the server enforces `unique(user, name)` on webhook endpoints and
`name` is a required API field — omitting it causes a 400 from the
server. Pass `--name` through to `client.create_webhook_endpoint(name=...)`.

Test: updated `test_cli_endpoints_create_returns_secret` to supply
`--name default`; verified `--help` output includes the option.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After tango#2275 (migration 0019_drop_subject_webhooks), all legacy
subject-based rows with null query_type / filter_definition were
deleted. The model fields are now non-nullable at the DB level.

- query_type: str | None → str
- filters: dict | None → dict[str, Any]
- status: str → Literal["active", "paused"]
  (server serializer maps is_active=True→"active", False→"paused")

Adds docstring note explaining the migration context and referencing
the tango migration file. Aligns Python with Node's WebhookAlert
interface in tango-node/src/models/Webhooks.ts.

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

Parity with `tango-node`'s `WebhookSamplePayloadResponse` discriminated union.
The endpoint can return one of two response shapes depending on whether
`event_type` is supplied:

- Single-event: `{event_type, sample_delivery, signature_header, note}`
- All-events:   `{samples, usage, signature_header, note}`

Added `WebhookSampleDelivery`, `WebhookSamplePayloadSingleResponse`,
`WebhookSamplePayloadAllResponse`, and the union alias
`WebhookSamplePayloadResponse` as TypedDict classes (structural, no runtime
conversion). Exported from the top-level `tango` package and used as the
return annotation on `client.get_webhook_sample_payload()`. No behavior
change — just better static typing.

Verified against the live API:
- single (`alerts.contract.match`): returns 4 keys matching the Single shape
- all: returns 4 keys matching the All shape

All 285 unit tests still pass.

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

After the `endpoints create --name` CLI fix, three examples still showed the
old `tango webhooks endpoints create --url URL` shape. Without `--name` the
CLI now errors out (Click marks it required). Updated all three call sites
(lines 123, 243, 373) to include `--name`.

Also fixed the stale "one endpoint per user" wording in the troubleshooting
section. Endpoint uniqueness is on `(user, name)` post-tango#2256 — users can
have multiple endpoints with distinct names. The 400 case is name collision,
not a user-level cap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tango server (makegov/tango#2259) now accepts both spellings as
expand aliases:

- `naics(code,description)` — canonical
- `naics_code(code,description)` — alias rewritten to `naics` at
  parse time
- Same pair for `psc(...)` / `psc_code(...)`.

Pre-fix the SDK's ShapeParser rejected the canonical server form
and demanded the `_code` alias — exactly opposite to the server.
Mirror the server's `_EXPAND_ALIASES` map locally so both spellings
are accepted client-side when used as expansions (with parens or
wildcards). Bare scalar leaves `naics_code` / `psc_code` are
untouched, matching server semantics.

Also added explicit `naics` / `psc` expand entries to Contract,
Forecast, Opportunity, Notice, and Vehicle schemas in
`tango/shapes/explicit_schemas.py` so the canonical form validates
locally too (IDV already had them).

Tests: 11 new cases in `TestShapeParserExpandAliases`. 261/261 unit
tests pass on Python 3.12 and 3.13. (Python 3.14 in the worktree's
default venv has pre-existing typing-related failures unrelated to
this change.)

Refs: makegov/tango#2266, makegov/tango#2259

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous SUBAWARD_SCHEMA declared `id` and `amount` (rejected by the
server with `unknown_field`) and was missing every real field on the
resource. Replace it with a schema derived from
`awards.serializers.subawards.SubawardSerializer` plus the runtime
`available_fields` payload: `key` / `award_key` / `piid` /
`usaspending_permalink`, the denormalized `prime_awardee_*` /
`recipient_*` lookup columns, and expandable objects for
`awarding_office`, `funding_office`, `prime_recipient`,
`subaward_recipient`, `place_of_performance`, `subaward_details`,
`fsrs_details`, and `highly_compensated_officers`. New nested schemas
`SubawardDetails`, `FsrsDetails`, `SubawardPlaceOfPerformance`, and
`HighlyCompensatedOfficer` back the expansions. Also update the
`Subaward` dataclass in `tango/models.py` to match.

Conformance and unit tests pass.
Adds three SDK-doc files that previously lived only in makegov/docs
under docs/sdks/python/. This is the pre-cutover content port for the
auto-pull pipeline (makegov/docs#15), so the docs-site versions can be
deleted at cutover without losing content.

- docs/ERRORS.md — exception hierarchy, recovery patterns, and shape-error
  classes (ShapeValidationError, ShapeParseError, TypeGenerationError,
  ModelInstantiationError) that have no dedicated entry in API_REFERENCE.md
- docs/PAGINATION.md — page-based vs cursor-based strategies, iteration
  patterns, PaginatedResponse field reference
- docs/CLIENT.md — TangoClient constructor reference, rate_limit_info and
  last_response_headers properties, retry-semantics note

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gov/docs#15)

Fires on push to main when docs/, README.md, or CHANGELOG.md changes and
dispatches `external_updated` to makegov/docs so the public docs site
rebuilds. Required so the auto-pull pipeline picks up SDK doc changes
without waiting for someone to push to the docs composer.

Requires `DOCS_DISPATCH_TOKEN` secret (contents:write on makegov/docs).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First stable release. The previously-pending 0.7.0 changeset (API
parity, subject-based webhook removal, shape-validator alias support)
plus the docs-only content port (makegov/docs#16) and the docs-dispatch
CI wiring for the makegov/docs#15 auto-pull pipeline now ship together
as 1.0.0. From here on, breaking changes will require a major bump.

- pyproject.toml: 0.7.0 → 1.0.0
- tango/__init__.py: __version__ 0.7.0 → 1.0.0
- CHANGELOG: rename [Unreleased] → [1.0.0] - 2026-05-13; rewrite intro
  framing; add CI section for the new docs-dispatch workflow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hatchling's default sdist behavior packs everything in the project root,
which is fine in CI (clean checkout) but ships per-developer state from
a laptop: .mg-tools/, .claude/, .claude.bak.*/, CLAUDE.md, ROADMAP.md,
work diaries, scratch reports, .coverage, etc. None of those have ever
been on PyPI because every release has shipped via the publish.yml
workflow's clean GitHub Actions checkout — but a single accidental
`uv publish` from a dev machine would have leaked all of them.

This adds an explicit `[tool.hatch.build.targets.sdist]` include list
plus a small exclude list for the internal docs that live inside
included dirs (docs/DEVELOPERS.md, docs/quick_start.ipynb). Even from a
developer's laptop, only the listed paths can now end up on PyPI.

Verified locally: `uv build` produces a sdist whose top-level entries
are .github, .gitignore, CHANGELOG.md, docs, LICENSE, PKG-INFO,
pyproject.toml, README.md, scripts, tango, tests. No more .mg-tools,
.claude, CLAUDE.md, .coverage, DEVELOPERS.md, or quick_start.ipynb.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI fix
- `tests/test_webhooks_receiver.py::test_receiver_404s_on_unknown_path`
  was failing on windows-latest with `httpx.ReadError WinError 10053`.
  The receiver's do_POST returned a 404 before draining the request body
  on unknown paths. On Linux/macOS that's absorbed silently; on Windows
  the abrupt socket close abort the still-pending request. do_POST now
  drains the body first, then branches on path.

Behavior changes
- `_post` / `_patch` now raise `TangoValidationError` when both
  `json_data=` and `json=` are passed instead of silently preferring
  one. Single-kwarg usage is unchanged.
- `create_webhook_endpoint(name=...)` is now **required** (was a
  `DeprecationWarning` in 0.7.0 — never publicly released). The server
  enforces unique(user, name), so omitting `name` returned a 400
  anyway. Raising client-side gives a clear error and avoids the
  wasted round-trip. Updated `test_create_endpoint_without_name_warns`
  → `..._raises`.

Docs/example fixes
- `get_psc_metrics` / `get_naics_metrics` / `get_entity_metrics`
  docstrings — `period_grouping` values are "month"/"quarter"/"year"
  (path-segment values), not "monthly"/"quarterly".
- `docs/API_REFERENCE.md#get_agency` — consistent `get_agency("GSA")`
  example + note that the parameter accepts CGAC / FPDS / short code /
  abbreviation / canonical name.
- `README.md` Quick Start — `agency.name` instead of `agency['name']`;
  `get_agency()` returns an `Agency` dataclass.
- `scripts/smoke_api_parity.py` — `list_business_types(limit=1)` is
  now wrapped in the `run(...)` helper so a failure there records FAIL
  instead of crashing the whole script.

CHANGELOG updated to reflect the behavior changes.

Closes the open Copilot comments on PR #25.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@infinityplusone infinityplusone changed the title feat(client): API parity — 30+ methods, ordering kwargs, webhook write fixes feat: tango-python 1.0.0 — API parity, webhook overhaul, docs auto-pull, packaging guardrails May 13, 2026
@infinityplusone infinityplusone marked this pull request as ready for review May 13, 2026 15:13
@infinityplusone infinityplusone merged commit b04d1eb into main May 13, 2026
10 checks passed
@infinityplusone infinityplusone deleted the feat/api-parity branch May 13, 2026 16:13
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