Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- `PaginatedResponse.meta` now carries the response-level `meta` block the API returns, with three accessors over it: `unresolved_agency_tokens` (agency tokens that matched nothing, per filter), `resolved_agencies` (the organization each token *did* match), and `agency_warnings` (the API's human-readable notes). Agency values resolve fuzzily, so a token can match an organization the caller did not intend and quietly scope the query to that subtree — and a token that resolves to nothing was previously dropped with no signal at all. Both cases were indistinguishable from "no such records exist". The API now reports both; until now the SDK read the envelope key-by-key and dropped `meta` on the floor, so SDK users were the one group that could not see it. `resolved_agencies` is the accessor that matters for the wrong-organization case: nothing is dropped there, so an unresolved-token check cannot detect it. All accessors return empty rather than raising when `meta` is absent (most responses) or malformed.
- A fully-unresolvable agency filter now raises `TangoValidationError` naming the offending value instead of returning an empty page. This needed no SDK change — the existing 400 handler already reads the API's `error` key — but it is new behavior for callers of `list_subawards()`, `list_opportunities()`, `list_notices()`, and `list_vehicles()`, which previously returned an empty result set. Contracts, IDVs, OTAs, and OTIDVs already behaved this way.

### Notes
- `PaginatedResponse.page_metadata` is documented as always `None`: the API has never emitted a `page_metadata` key, so the field has only ever read a value that does not exist. It is retained so existing attribute access keeps working. Use `meta`.

## [1.4.0] - 2026-07-20

### Added
Expand Down
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,37 @@ contracts = client.list_contracts(
- `expiring_gte`, `expiring_lte` - Contract expiration date range

**Party Filters:**
- `awarding_agency`, `funding_agency` - Agency codes
- `awarding_agency`, `funding_agency` - Agency codes, names, abbreviations, or organization UUIDs. Multi-value OR via `|`.
- `recipient_name`, `recipient_uei` - Vendor/recipient filters

### Checking how agency filters resolved

Agency values are resolved fuzzily, so a token can match an organization you did not
intend — which silently scopes the query to that organization's subtree. A short result
set is then indistinguishable from "no such records exist". Responses expose what
actually happened:

```python
response = client.list_contracts(awarding_agency="HUD|HUDD")

# Tokens that matched nothing and were ignored.
if response.unresolved_agency_tokens:
raise SystemExit(f"dropped: {response.unresolved_agency_tokens}")
# {'awarding_agency': ['HUDD']}

# What the tokens that DID match resolved to — the only way to catch a
# plausible-but-wrong match, where nothing was dropped at all.
for org in response.resolved_agencies.get("awarding_agency", []):
print(org["name"], org["cgac"])
# Department of Housing and Urban Development 086

for warning in response.agency_warnings:
print(warning)
```

If *every* token for a filter fails to resolve, the API returns `400` and the SDK raises
`TangoValidationError` naming the offending value, rather than an empty page.

**Classification:**
- `naics_code`, `psc_code` - Industry/product codes
- `set_aside_type` - Set-aside type
Expand Down
Loading
Loading