Skip to content
Merged
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
9 changes: 9 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ jobs:

steps:
- uses: actions/checkout@v4

- name: Checkout tango API repo (manifest source)
uses: actions/checkout@v4
with:
repository: makegov/tango
path: tango-api

- name: Install uv
uses: astral-sh/setup-uv@v4
Expand All @@ -29,3 +35,6 @@ jobs:

- name: Lint with ruff
run: uv run ruff check tango/

- name: Check SDK filter/shape conformance
run: uv run python scripts/check_filter_shape_conformance.py --manifest tango-api/contracts/filter_shape_contract.json
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.3.0] - 2026-02-12

### Added
- Vehicles endpoints: `list_vehicles`, `get_vehicle`, and `list_vehicle_awardees` (supports shaping + flattening). (refs `makegov/tango#1328`)
- IDV endpoints: `list_idvs`, `get_idv`, `list_idv_awards`, `list_idv_child_idvs`, `list_idv_transactions`, `get_idv_summary`, `list_idv_summary_awards`. (refs `makegov/tango#1328`)
Expand Down
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -432,8 +432,10 @@ tango-python/
│ └── quick_start.ipynb # Interactive quick start
├── scripts/ # Utility scripts
│ ├── README.md
│ ├── check_filter_shape_conformance.py # Filter + shape conformance (CI)
│ ├── fetch_api_schema.py
│ └── generate_schemas_from_api.py
│ ├── generate_schemas_from_api.py
│ └── pr_review.py # PR validation (lint, types, tests, conformance)
├── pyproject.toml # Project configuration
├── uv.lock # Dependency lock file
├── LICENSE # MIT License
Expand Down Expand Up @@ -471,7 +473,12 @@ Contributions are welcome! Please feel free to submit a Pull Request.

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Run tests (`uv run pytest`)
4. Commit your changes (`git commit -m 'Add amazing feature'`)
5. Push to the branch (`git push origin feature/amazing-feature`)
6. Open a Pull Request
3. Run lint and format: `uv run ruff format tango/ && uv run ruff check tango/`
4. Run type checking: `uv run mypy tango/`
5. Run tests: `uv run pytest`
6. (Optional) Run [filter and shape conformance](scripts/README.md#filter-and-shape-conformance) if you have the tango API manifest; CI will run it on push/PR
7. Commit your changes (`git commit -m 'Add amazing feature'`)
8. Push to the branch (`git push origin feature/amazing-feature`)
9. Open a Pull Request

For a single command that runs formatting, linting, type checking, and tests (and conformance when the manifest is present), use: `uv run python scripts/pr_review.py --mode full`
9 changes: 1 addition & 8 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,7 @@

## Now

- [ ] Align existing API to the SDK
- [ ] Add in existing IDV endpoint
- [ ] Add in existing OTA endpoint
- [ ] Add in existing OTIDV endpoint
- [ ] Add in existing financial assistance endpoint
- [ ] Add in account usage endpoint
- [ ] Add in webhooks endpoint
- [ ] Add in subawards endpoint
- [X] Align existing API to the SDK
- [ ] Better Filter DX
- [ ] Dataclasses for search validation and typing
- [ ] Docs improvements
Expand Down
46 changes: 45 additions & 1 deletion docs/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Complete reference for all Tango Python SDK methods and functionality.
- [Business Types](#business-types)
- [Webhooks](#webhooks)
- [Response Objects](#response-objects)
- [ShapeConfig (predefined shapes)](#shapeconfig-predefined-shapes)
- [Error Handling](#error-handling)

## Client Initialization
Expand Down Expand Up @@ -904,6 +905,48 @@ print(f"Total collected: {len(all_results)} results")

---

## ShapeConfig (predefined shapes)

The SDK provides predefined shape strings as constants on `ShapeConfig`. Use them as the `shape` argument for list/get methods when you want a consistent, validated set of fields without building a custom shape string.

```python
from tango import TangoClient, ShapeConfig

client = TangoClient()

# List methods default to the minimal shape when shape is omitted
contracts = client.list_contracts(limit=10) # uses CONTRACTS_MINIMAL

# Or pass the constant explicitly
contracts = client.list_contracts(shape=ShapeConfig.CONTRACTS_MINIMAL, limit=10)
entity = client.get_entity("UEI_KEY", shape=ShapeConfig.ENTITIES_COMPREHENSIVE)
```

**Available constants (by resource):**

| Constant | Used by | Description |
|----------|---------|-------------|
| `CONTRACTS_MINIMAL` | `list_contracts`, `search_contracts` | key, piid, award_date, recipient(display_name), description, total_contract_value |
| `ENTITIES_MINIMAL` | `list_entities` | uei, legal_business_name, cage_code, business_types |
| `ENTITIES_COMPREHENSIVE` | `get_entity` | Full entity profile (addresses, naics, psc, obligations, etc.) |
| `FORECASTS_MINIMAL` | `list_forecasts` | id, title, anticipated_award_date, fiscal_year, naics_code, status |
| `OPPORTUNITIES_MINIMAL` | `list_opportunities` | opportunity_id, title, solicitation_number, response_deadline, active |
| `NOTICES_MINIMAL` | `list_notices` | notice_id, title, solicitation_number, posted_date |
| `GRANTS_MINIMAL` | `list_grants` | grant_id, opportunity_number, title, status(*), agency_code |
| `IDVS_MINIMAL` | `list_idvs`, `list_vehicle_awardees` | key, piid, award_date, recipient(display_name,uei), description, total_contract_value, obligated, idv_type |
| `IDVS_COMPREHENSIVE` | `get_idv` | Full IDV with offices, place_of_performance, competition, transactions, etc. |
| `VEHICLES_MINIMAL` | `list_vehicles` | uuid, solicitation_identifier, organization_id, awardee_count, order_count, vehicle_obligations, vehicle_contracts_value, solicitation_title, solicitation_date |
| `VEHICLES_COMPREHENSIVE` | `get_vehicle` | Full vehicle with competition_details, fiscal_year, set_aside, etc. |
| `VEHICLE_AWARDEES_MINIMAL` | `list_vehicle_awardees` | uuid, key, piid, award_date, title, order_count, idv_obligations, idv_contracts_value, recipient(display_name,uei) |
| `ORGANIZATIONS_MINIMAL` | `list_organizations`, `list_organization_offices` | key, fh_key, name, level, type, short_name |
| `OTAS_MINIMAL` | `list_otas` | key, piid, award_date, recipient(display_name,uei), description, total_contract_value, obligated |
| `OTIDVS_MINIMAL` | `list_otidvs` | key, piid, award_date, recipient(display_name,uei), description, total_contract_value, obligated, idv_type |
| `SUBAWARDS_MINIMAL` | `list_subawards` | award_key, prime_recipient(uei,display_name), subaward_recipient(uei,display_name) |

All predefined shapes are validated at SDK release time (see [Developer Guide](DEVELOPERS.md#sdk-conformance-maintainers)). For custom shapes, see the [Shaping Guide](SHAPES.md).

---

## Error Handling

The SDK provides specific exception types for different error scenarios.
Expand Down Expand Up @@ -1094,7 +1137,8 @@ client = TangoClient()

## Additional Resources

- [Shaping Guide](SHAPES.md) - Comprehensive guide to response shaping
- [Shaping Guide](SHAPES.md) - Response shaping syntax, examples, and field reference
- [Developer Guide](DEVELOPERS.md) - Dynamic models, predefined shapes, and SDK conformance (maintainers)
- [Quick Start](quick_start.ipynb) - Interactive notebook with examples
- [GitHub Repository](https://github.com/makegov/tango-python) - Source code and examples
- [Tango API Documentation](https://tango.makegov.com/docs) - Full API documentation
27 changes: 27 additions & 0 deletions docs/DEVELOPERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The Tango SDK uses dynamic models that generate runtime types matching the exact
- [Type Hints and IDE Support](#type-hints-and-ide-support)
- [Performance Considerations](#performance-considerations)
- [Troubleshooting](#troubleshooting)
- [SDK conformance (maintainers)](#sdk-conformance-maintainers)

## Overview

Expand Down Expand Up @@ -623,6 +624,32 @@ If you encounter issues:
3. See [examples/](../examples/) for working code samples
4. Contact support at [tango@makegov.com](mailto:tango@makegov.com)

## SDK conformance (maintainers)

The SDK is kept in sync with the Tango API and its own shape schemas via two conformance checks. Both run in CI on every push and PR (see [Lint workflow](../.github/workflows/lint.yml)) and can be run locally with [scripts/check_filter_shape_conformance.py](../scripts/check_filter_shape_conformance.py).

### Filter conformance

- **What it checks:** Every list/get endpoint in the canonical manifest (from the [tango](https://github.com/makegov/tango) API repo) has a matching SDK method that exposes the manifest’s filter parameters—either as explicit arguments or via the method’s `api_param_mapping`.
- **Why it matters:** Ensures the SDK supports all query filters the API exposes for each resource, so users can filter without relying on undocumented `**kwargs`.
- **Warnings:** Methods that take filters only via `**kwargs` are reported as warnings (filter names cannot be verified against the manifest).

### Shape conformance

- **What it checks:** Every predefined shape in `ShapeConfig` (e.g. `CONTRACTS_MINIMAL`, `IDVS_MINIMAL`, `GRANTS_MINIMAL`) is parsed and validated against the SDK’s explicit schemas in `tango/shapes/explicit_schemas.py`. Each shape must only reference fields that exist for that model (including nested fields).
- **Why it matters:** Ensures default shapes never reference invalid or renamed fields, so default list/get behavior stays valid after schema or API changes.
- **Errors:** Parse failures or invalid field names in any `ShapeConfig` constant are reported as errors and fail the check.

### Running the conformance check

- **In CI:** The [Lint workflow](../.github/workflows/lint.yml) runs the full check automatically (it has access to the manifest). No setup needed for push/PR.
- **Locally:** You need the manifest file to run the script. If you have it (e.g. a path to `filter_shape_contract.json` from the [tango](https://github.com/makegov/tango) repo—wherever you keep that repo—or from a colleague), run:
```bash
uv run python scripts/check_filter_shape_conformance.py --manifest /path/to/filter_shape_contract.json
```
If you don’t have the manifest, CI will still run the full check on your branch; shape conformance is included whenever the script runs.
- **Output:** JSON with `errors` and `warnings`. Exit code 1 if there are any errors. See [scripts/README.md](../scripts/README.md#filter-and-shape-conformance) for full usage and `--list-missing`.

## Next Steps

- Read the [API Reference](API_REFERENCE.md) for detailed class and method documentation
Expand Down
32 changes: 30 additions & 2 deletions docs/SHAPES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Response shaping lets you control which fields the API returns, making your requests faster and more efficient. Instead of receiving hundreds of fields you don't need, you specify exactly what you want.

**See also:** [API Reference](API_REFERENCE.md) for method parameters and [ShapeConfig (predefined shapes)](API_REFERENCE.md#shapeconfig-predefined-shapes); [Developer Guide](DEVELOPERS.md) for dynamic models and maintainer conformance.

## Why Use Response Shaping?

**Performance Benefits:**
Expand All @@ -27,11 +29,36 @@ contracts = client.list_contracts(
shape="key,piid,recipient(display_name),total_contract_value"
)

# Or use a predefined shape constant
from tango import ShapeConfig
contracts = client.list_contracts(shape=ShapeConfig.CONTRACTS_MINIMAL, limit=10)

# Access the data
for contract in contracts.results:
print(f"{contract['piid']}: {contract['recipient']['display_name']}")
```

## Predefined shapes (ShapeConfig)

Instead of writing shape strings by hand, you can use the SDK’s predefined constants. Each list/get method has a default minimal shape when you omit `shape`; you can also pass a constant explicitly.

```python
from tango import TangoClient, ShapeConfig

client = TangoClient()

# These are equivalent (list_contracts defaults to CONTRACTS_MINIMAL)
contracts = client.list_contracts(limit=10)
contracts = client.list_contracts(shape=ShapeConfig.CONTRACTS_MINIMAL, limit=10)

# Other resources
entities = client.list_entities(shape=ShapeConfig.ENTITIES_MINIMAL)
idvs = client.list_idvs(shape=ShapeConfig.IDVS_MINIMAL)
grants = client.list_grants(shape=ShapeConfig.GRANTS_MINIMAL)
```

**Available constants:** Contracts (`CONTRACTS_MINIMAL`), Entities (`ENTITIES_MINIMAL`, `ENTITIES_COMPREHENSIVE`), Forecasts, Opportunities, Notices, Grants, IDVs, Vehicles, Organizations, OTAs, OTIDVs, Subawards. See [API Reference – ShapeConfig](API_REFERENCE.md#shapeconfig-predefined-shapes) for the full table and which method uses which constant.

## Basic Shaping

### Simple Fields
Expand Down Expand Up @@ -455,5 +482,6 @@ display_name = contract.get('recipient', {}).get('display_name', 'Unknown')
- **Define patterns** - Create reusable shapes for your common queries

For more help, see:
- [Quick Start Guide](quick_start.ipynb) - Interactive examples
- [API Reference](API_REFERENCE.md) - Complete field listings
- [API Reference](API_REFERENCE.md) - Method parameters, [ShapeConfig table](API_REFERENCE.md#shapeconfig-predefined-shapes), and field context
- [Developer Guide](DEVELOPERS.md) - Dynamic models, predefined shapes in depth, and SDK conformance (for maintainers)
- [Quick Start Guide](quick_start.ipynb) - Interactive examples
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "tango-python"
version = "0.2.0"
version = "0.3.0"
description = "Python SDK for the Tango API"
readme = "README.md"
requires-python = ">=3.12"
Expand Down
30 changes: 27 additions & 3 deletions scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ This directory contains utility scripts used during development and maintenance
### Testing and Validation

- **`test_production.py`** - Runs production API smoke tests against the live API
- **`pr_review.py`** - Runs configurable validation checks for PR review (linting, type checking, tests)
- **`pr_review.py`** - Runs configurable validation checks for PR review (linting, type checking, tests, conformance)
- **`check_filter_shape_conformance.py`** - Validates SDK filter and shape conformance (see [Filter and shape conformance](#filter-and-shape-conformance))

## Usage

Expand Down Expand Up @@ -56,8 +57,10 @@ uv run python scripts/pr_review.py --mode production --changed-files-only
**Validation Modes:**
- `smoke` - Production API smoke tests only
- `quick` - Linting + type checking (no tests)
- `full` - All checks (linting + type checking + all tests)
- `production` - Production API smoke tests + linting + type checking (default)
- `full` - All checks (linting + type checking + filter/shape conformance + all tests)
- `production` - Linting + type checking + filter/shape conformance + production API smoke tests (default)

When the conformance manifest is present (`tango-api/contracts/filter_shape_contract.json` or `TANGO_CONTRACT_MANIFEST`), both `full` and `production` run the [filter and shape conformance](#filter-and-shape-conformance) check. If the manifest is missing, that step is skipped with a warning.

**PR Detection:**
The script automatically detects PR information from:
Expand All @@ -70,6 +73,27 @@ When a PR is detected, the script displays PR information and automatically:
- Checks only changed files
- Shows PR URL in summary

### Filter and shape conformance

The SDK is validated against a canonical manifest (from the [tango](https://github.com/makegov/tango) API repo) for **filter conformance** and against its own schemas for **shape conformance**:

1. **Filter conformance** – Ensures every list/get method exposes the filter parameters defined in the manifest (e.g. `award_date`, `naics_code`, `agency`). Methods that use `**kwargs` for filters are reported as warnings because their filter names cannot be verified.
2. **Shape conformance** – Ensures every `ShapeConfig` constant (e.g. `CONTRACTS_MINIMAL`, `IDVS_MINIMAL`) parses correctly and only references fields that exist in the SDK’s explicit schemas for that model. Invalid or unknown fields in default shapes are reported as errors.

This script runs in CI on every push/PR (see [Lint workflow](.github/workflows/lint.yml)). The manifest file is produced by the tango API repo and must be available for the full check.

```bash
# Full check (filter + shape). Requires manifest from tango API repo.
uv run python scripts/check_filter_shape_conformance.py --manifest tango-api/contracts/filter_shape_contract.json

# List resources that have no matching SDK method (for implementation checklist)
uv run python scripts/check_filter_shape_conformance.py --manifest tango-api/contracts/filter_shape_contract.json --list-missing
```

**Output:** JSON with `manifest`, `errors`, and `warnings`. Exit code 1 if there are any errors (missing filters, invalid shapes, or missing SDK methods for manifest resources).

**Local runs:** To run the full check locally, you need a copy of `filter_shape_contract.json` (from the [tango](https://github.com/makegov/tango) repo’s `contracts/` directory—wherever you keep that repo). Pass it with `--manifest` or set `TANGO_CONTRACT_MANIFEST`. The script runs both filter and shape conformance; shape conformance validates all `ShapeConfig` defaults against `tango/shapes/explicit_schemas.py`.

## Requirements

- `TANGO_API_KEY` environment variable (required for production API tests)
Expand Down
Loading
Loading