Skip to content

Commit b04d1eb

Browse files
v1.0.0
2 parents ee62982 + 132641f commit b04d1eb

30 files changed

Lines changed: 3639 additions & 810 deletions
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: Docs dispatch
2+
3+
# Fires on push to main when content that affects the published docs site
4+
# changes (docs/, README, or CHANGELOG). Notifies makegov/docs via
5+
# repository_dispatch so the docs site rebuilds without waiting for someone
6+
# to push to the composer.
7+
#
8+
# tango-python is a `coloc-source` repo: its docs/ folder is the authoritative
9+
# source for the Python SDK pages on docs.makegov.com (see makegov/docs#15).
10+
#
11+
# Required secrets:
12+
# DOCS_DISPATCH_TOKEN — GitHub token with contents:write on makegov/docs.
13+
#
14+
# Required variables (optional):
15+
# DOCS_TARGET_REPO — override the dispatch target (default: makegov/docs).
16+
17+
on:
18+
push:
19+
branches:
20+
- main
21+
paths:
22+
- "docs/**"
23+
- "README.md"
24+
- "CHANGELOG.md"
25+
workflow_dispatch:
26+
27+
jobs:
28+
dispatch:
29+
runs-on: ubuntu-latest
30+
permissions:
31+
contents: read
32+
steps:
33+
- uses: actions/checkout@v4
34+
with:
35+
fetch-depth: 2
36+
37+
- name: Detect changed paths
38+
id: changes
39+
run: |
40+
set -euo pipefail
41+
base=$(git rev-parse HEAD~1 2>/dev/null || git rev-parse HEAD)
42+
external=$(git diff --name-only "$base" HEAD -- 'docs' 'README.md' 'CHANGELOG.md' | paste -sd, -)
43+
{
44+
echo "external=$external"
45+
echo "has_external=$([ -n "$external" ] && echo true || echo false)"
46+
} >> "$GITHUB_OUTPUT"
47+
48+
- name: Dispatch to docs composer (makegov/docs)
49+
if: steps.changes.outputs.has_external == 'true'
50+
env:
51+
GH_TOKEN: ${{ secrets.DOCS_DISPATCH_TOKEN }}
52+
TARGET: ${{ vars.DOCS_TARGET_REPO || 'makegov/docs' }}
53+
run: |
54+
gh api "repos/$TARGET/dispatches" \
55+
-f event_type=external_updated \
56+
-f "client_payload[source_repo]=${{ github.repository }}" \
57+
-f "client_payload[source_ref]=${{ github.sha }}" \
58+
-f "client_payload[changed_paths]=${{ steps.changes.outputs.external }}"

CHANGELOG.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,67 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
## [1.0.0] - 2026-05-13
11+
12+
> First stable release. `tango-python` is now at full API parity with the
13+
> Tango HTTP surface, the legacy subject-based webhook subscription
14+
> mechanism has been removed in favor of filter alerts, the shape parser
15+
> agrees byte-for-byte with the server's expand-alias handling, and the
16+
> SDK's docs are auto-published to `docs.makegov.com/sdks/python/` via the
17+
> composer pipeline (makegov/docs#15 / makegov/docs#16). From `1.x` on,
18+
> we'll only do breaking changes on a major bump.
19+
>
20+
> Originally tracked as: API parity (PR #25), subject-based webhook
21+
> removal (PR #27 / issue #2275), shape-validator alias support (PR #28 /
22+
> issue #2266), and the docs-only content port (makegov/docs#16).
23+
24+
### Added
25+
- `ordering` parameter on `list_forecasts`, `list_grants`, `list_subawards`, `list_gsa_elibrary_contracts`, and `list_opportunities`. Prefix with `-` for descending. Closes a parity gap with the API surface (these endpoints all accept `?ordering=` server-side).
26+
- `create_webhook_endpoint` accepts `name=` (keyword-only) and now **requires** it. The Tango API enforces unique `(user, name)` on endpoints; omitting `name` returns a 400 server-side, so the SDK raises `TangoValidationError` client-side instead of round-tripping. (0.7.0 — never publicly released — emitted a `DeprecationWarning` instead.)
27+
- `update_webhook_endpoint` accepts `name=` for renaming an endpoint.
28+
- Webhook alerts (filter subscriptions): `list_webhook_alerts`, `get_webhook_alert`, `create_webhook_alert`, `update_webhook_alert`, `delete_webhook_alert` — the canonical write surface over `/api/webhooks/alerts/`. New `WebhookAlert` dataclass exported from the top-level package.
29+
- `resolve(name, target_type, ...)` — POST `/api/resolve/` to rank entity / organization candidates from a free-text name. Returns `ResolveResult` with `ResolveCandidate` entries (both exported).
30+
- `validate(identifier_type, value)` — POST `/api/validate/` to validate the format of a PIID, solicitation number, or UEI. Returns `ValidateResult` (exported).
31+
- 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`.
32+
- 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.
33+
- IDV sub-resources: `list_idv_lcats`.
34+
- Agency sub-resources: `list_agency_awarding_contracts`, `list_agency_funding_contracts`.
35+
- 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/`.
36+
37+
### Changed
38+
- `create_webhook_alert` accepts `endpoint=` (keyword-only). Required for accounts with multiple webhook endpoints; auto-resolves for single-endpoint accounts. Closes the multi-endpoint smoke-test gap (tango#2256).
39+
- `test_webhook_delivery` now sends the canonical `endpoint` body key instead of the deprecated `endpoint_id` alias (tango#2252). The Python kwarg name stays `endpoint_id=` for backwards compatibility; the wire payload is what changed.
40+
- **`generate_signature(body, secret)` now returns the full wire form `"sha256=<hex>"`** instead of bare hex. Callers can assign the return value directly to the `X-Tango-Signature` header without wrapping in a format string. This is a breaking change for code that relied on the bare-hex return; pass it through `parse_signature_header()` to recover the previous form. `verify_signature` accepts both prefixed and bare-hex inputs (unchanged), so receivers continue to work either way.
41+
42+
### Removed
43+
- **Subject-based webhook subscription surface** (tango#2275). Migrate to `create_webhook_alert(...)` and the alerts API.
44+
- Methods: `list_webhook_subscriptions`, `get_webhook_subscription`, `create_webhook_subscription`, `update_webhook_subscription`, `delete_webhook_subscription`.
45+
- Dataclasses: `WebhookSubscription`, `WebhookSubjectTypeDefinition`. Both are no longer exported from the top-level `tango` package — importing them raises `ImportError`.
46+
- Fields: `default_subject_type` removed from `WebhookEventType`; `subject_types` and `subject_type_definitions` removed from `WebhookEventTypesResponse`. The server's `/api/webhooks/event-types/` response no longer carries these.
47+
- CLI: the entire `tango webhooks subscriptions` Click subgroup (`list` / `get` / `create` / `delete`). Use the SDK's `client.create_webhook_alert(...)` etc. directly — there is no CLI subgroup for alerts.
48+
- `ordering` kwarg from `list_notices` and `list_protests`. The notices and protests viewsets reject every `?ordering=` value at runtime (tango#2254); the kwarg silently sent unsupported values. Other five list methods retain `ordering`.
49+
50+
### Fixed
51+
- `TangoClient._post()` and `_patch()` accept both `json_data=` (positional) and `json=` (keyword) for backward compatibility. Internal callers and docs examples that use `json=` no longer fail with `TypeError`. Passing **both** now raises `TangoValidationError` rather than silently preferring one — that ambiguity would hide caller bugs.
52+
- `get_psc_metrics` / `get_naics_metrics` / `get_entity_metrics` docstrings — `period_grouping` values are `"month"` / `"quarter"` / `"year"` (the path-segment values the API accepts), not `"monthly"` / `"quarterly"`.
53+
- `docs/API_REFERENCE.md#get_agency` — example uses `client.get_agency("GSA")` consistently and notes the parameter accepts CGAC / FPDS / short code / abbreviation / canonical name.
54+
- `README.md` Quick Start — `get_agency()` returns an `Agency` dataclass, so the example uses attribute access (`agency.name`) instead of `agency['name']` which would `TypeError`.
55+
- `scripts/smoke_api_parity.py``list_business_types(limit=1)` is now wrapped in the `run(...)` helper so a failure on that call records FAIL instead of aborting the smoke run.
56+
- `tango webhooks endpoints create` CLI now accepts and requires `--name` (passed through to `create_webhook_endpoint(name=...)`). Previously the option was absent, meaning the CLI could never set a custom endpoint name and every call would 400 server-side (the server enforces `unique(user, name)`).
57+
- `WebhookAlert.query_type` and `WebhookAlert.filters` tightened from `Optional` to non-optional (`str` and `dict[str, Any]` respectively). Legacy nullable rows were purged by the tango#2275 migration; the server model and serializer guarantee non-null values for all current data. `WebhookAlert.status` narrowed from `str` to `Literal["active", "paused"]` — the server serializer produces exactly those two values.
58+
- **Shape validator agrees with server on `naics(...)` / `psc(...)` expansions.** The client-side `ShapeParser.validate()` previously rejected the canonical `shape=naics(code,description)` form (which the server has always accepted) and also rejected the alias `shape=naics_code(code,description)`. The parser now mirrors the server's `_EXPAND_ALIASES` (introduced in Tango PR makegov/tango#2259) and rewrites `naics_code(...)` / `psc_code(...)` to their canonical `naics(...)` / `psc(...)` form at parse time. Bare scalar leaves (`shape=naics_code` / `shape=psc_code`) are left untouched and still return the raw column value, matching the server. Schemas for `Contract`, `Forecast`, `Opportunity`, `Notice`, and `Vehicle` gained explicit `naics` / `psc` expand entries backed by the existing `CodeDescription` nested model. Fixes makegov/tango#2266.
59+
- **`Subaward` schema matches the server's `SubawardSerializer`.** The previous `SUBAWARD_SCHEMA` declared two fields the server has never exposed (`id`, `amount`) and was missing every real field on the resource — including `piid`, `key`, `awarding_office` / `funding_office` / `place_of_performance` / `subaward_details` / `fsrs_details` / `highly_compensated_officers` / `usaspending_permalink`, and the denormalized `prime_awardee_*` / `recipient_*` lookup columns. Shape strings that referenced any real field (e.g. `shape="piid"`) would fail client-side validation with `unknown_field`, and conversely the SDK happily passed `shape="id"` / `shape="amount"` through to the server, where they were rejected. `SUBAWARD_SCHEMA` is now derived directly from `awards.serializers.subawards.SubawardSerializer` and the resource's runtime `available_fields`. The `Subaward` dataclass in `tango/models.py` was updated to match. New nested schemas `SubawardDetails`, `FsrsDetails`, `SubawardPlaceOfPerformance`, and `HighlyCompensatedOfficer` are registered so the corresponding shape expansions validate end-to-end.
60+
61+
### Documentation
62+
- New `docs/ERRORS.md` — full exception hierarchy, recovery patterns, and the shape-error classes (`ShapeValidationError`, `ShapeParseError`, `TypeGenerationError`, `ModelInstantiationError`). Ported from `docs.makegov.com/sdks/python/errors.md` ahead of the docs-site auto-pull cutover (makegov/docs#15 / makegov/docs#16).
63+
- New `docs/PAGINATION.md` — page-based vs cursor-based strategies, iteration patterns, and the `PaginatedResponse` field reference. Ported from `docs.makegov.com/sdks/python/pagination.md`.
64+
- New `docs/CLIENT.md``TangoClient` constructor reference, `rate_limit_info` / `last_response_headers` properties, and retry-semantics note (the SDK has no built-in retry). Ported from `docs.makegov.com/sdks/python/client.md`.
65+
66+
### CI
67+
- New `.github/workflows/docs-dispatch.yml` — fires on push to `main` when `docs/**`, `README.md`, or `CHANGELOG.md` changes and dispatches `external_updated` at `makegov/docs` so the public docs site rebuilds with the latest SDK content. Required for the makegov/docs#15 auto-pull pipeline.
68+
869
## [0.6.0] - 2026-05-07
970

1071
### Added

README.md

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,14 @@ print(f"Found {agencies.count} agencies")
3939

4040
# Get specific agency
4141
agency = client.get_agency("GSA")
42-
print(f"Agency: {agency['name']}")
42+
print(f"Agency: {agency.name}")
4343

4444
# Search contracts
4545
contracts = client.list_contracts(
4646
limit=10
4747
)
48+
```
49+
4850
## Authentication
4951

5052
Most endpoints require an API key. You can obtain one from the [Tango API portal](https://tango.makegov.com).
@@ -204,13 +206,13 @@ opportunities = client.list_opportunities(agency="DOD", active=True, limit=25)
204206
### Notices
205207

206208
```python
207-
notices = client.list_notices(agency="DOD", notice_type="award", limit=25)
209+
notices = client.list_notices(agency="DOD", notice_type="Presolicitation", limit=25)
208210
```
209211

210212
### Grants
211213

212214
```python
213-
grants = client.list_grants(agency="HHS", status="forecasted", limit=25)
215+
grants = client.list_grants(agency="HHS", status="F", limit=25) # F = Forecasted
214216
```
215217

216218
### Protests
@@ -230,12 +232,45 @@ contract = client.get_gsa_elibrary_contract("UUID")
230232
### Reference Data
231233

232234
```python
233-
# Offices, organizations, NAICS, subawards, business types
235+
# Offices, organizations, NAICS, PSC, subawards, business types
234236
offices = client.list_offices(search="acquisitions")
235237
organizations = client.list_organizations(level=1)
236238
naics = client.list_naics(search="software")
239+
get_naics = client.get_naics("541511")
240+
psc = client.list_psc()
237241
subawards = client.list_subawards(prime_uei="UEI123")
238242
business_types = client.list_business_types()
243+
mas_sins = client.list_mas_sins()
244+
assistance = client.list_assistance_listings()
245+
departments = client.list_departments()
246+
```
247+
248+
### Resolve / Validate
249+
250+
```python
251+
# Resolve a name to entity/org candidates
252+
result = client.resolve(name="Lockheed Martin", target_type="entity")
253+
for c in result.candidates:
254+
print(c.identifier, c.display_name)
255+
256+
# Validate an identifier
257+
result = client.validate(identifier_type="uei", value="ABCDEF123456")
258+
```
259+
260+
### IT Dashboard
261+
262+
```python
263+
investments = client.list_itdashboard_investments(search="cloud", limit=25)
264+
investment = client.get_itdashboard_investment("023-000001234")
265+
```
266+
267+
### Entity Sub-resources
268+
269+
```python
270+
contracts = client.list_entity_contracts("ABCDEF123456", limit=25)
271+
idvs = client.list_entity_idvs("ABCDEF123456")
272+
otas = client.list_entity_otas("ABCDEF123456")
273+
metrics = client.get_entity_metrics("ABCDEF123456", months=12, period_grouping="month")
239274
```
240275

241276
## Pagination
@@ -253,9 +288,9 @@ print(f"Previous page URL: {response.previous}")
253288
for contract in response.results:
254289
print(contract['description'])
255290

256-
# Get next page
291+
# Get next page (contracts use keyset/cursor pagination)
257292
if response.next:
258-
next_response = client.list_contracts(page=2, limit=25)
293+
next_response = client.list_contracts(cursor=response.cursor, limit=25)
259294
```
260295

261296
## Error Handling
@@ -282,7 +317,7 @@ except TangoNotFoundError:
282317
print("Resource not found")
283318
except TangoValidationError as e:
284319
print(f"Invalid parameters: {e.message}")
285-
print(f"Details: {e.details}")
320+
print(f"Details: {e.response_data}")
286321
except TangoRateLimitError:
287322
print("Rate limit exceeded")
288323
except TangoAPIError as e:
@@ -314,22 +349,18 @@ contracts = client.list_contracts(
314349

315350
### Flattened Responses
316351

317-
Enable flattening to get dot-notation field names:
352+
The `flat=True` parameter is passed to the API, which returns dot-notation keys in the raw response. The SDK still wraps the result in a `ShapedModel` — access nested fields via attribute or dict syntax, not dot-notation string keys:
318353

319354
```python
320355
contracts = client.list_contracts(
321356
shape="key,piid,recipient(display_name,uei)",
322357
flat=True
323358
)
324-
# Returns: {"key": "...", "piid": "...", "recipient.display_name": "...", "recipient.uei": "..."}
325-
326-
# Flatten arrays with indexed keys
327-
contracts = client.list_contracts(
328-
shape="key,transactions(*)",
329-
flat=True,
330-
flat_lists=True
331-
)
332-
# Returns: {"key": "...", "transactions.0.action_date": "...", "transactions.0.obligated": "..."}
359+
for contract in contracts.results:
360+
# Attribute access
361+
print(contract.recipient.display_name)
362+
# Dict access (nested, not flat string keys)
363+
print(contract['recipient']['display_name'])
333364
```
334365

335366
### Webhook Tooling
@@ -353,9 +384,8 @@ tango webhooks simulate --secret $SECRET --event-type entities.updated # sign +
353384
tango webhooks simulate --secret $SECRET --event-type entities.updated \
354385
--to http://127.0.0.1:8011/tango/webhooks # also POST
355386

356-
# Manage real subscriptions and endpoints
357-
tango webhooks endpoints create|list|get|delete
358-
tango webhooks subscriptions create|list|get|delete
387+
# Manage delivery endpoints
388+
tango webhooks endpoints create|list|get|delete
359389

360390
# Force a real test delivery from Tango
361391
tango webhooks trigger

0 commit comments

Comments
 (0)