A modern Python SDK for the Tango API by MakeGov, featuring dynamic response shaping and comprehensive type hints.
- Dynamic Response Shaping - Request only the fields you need, reducing payload sizes by 60-80%
- Full Type Safety - Runtime-generated TypedDict types with accurate type hints for IDE autocomplete
- Comprehensive API Coverage - All major Tango API endpoints (contracts, entities, forecasts, opportunities, notices, grants) [Note: the current version does NOT implement all endpoints, we will be adding them incrementally]
- Flexible Data Access - Dictionary-based response objects with validation
- Modern Python - Built for Python 3.12+ using modern async-ready patterns
- Production-Ready - Comprehensive test suite with VCR.py-based integration tests
Requirements: Python 3.12 or higher
pip install tango-pythonOr with uv:
uv pip install tango-pythonfrom tango import TangoClient, ShapeConfig
# Initialize the client
client = TangoClient(api_key="your-api-key")
# List agencies
agencies = client.list_agencies()
print(f"Found {agencies.count} agencies")
# Get specific agency
agency = client.get_agency("GSA")
print(f"Agency: {agency['name']}")
# Search contracts
contracts = client.list_contracts(
limit=10
)
## Authentication
Most endpoints require an API key. You can obtain one from the [Tango API portal](https://tango.makegov.com).
```python
# With API key
client = TangoClient(api_key="your-api-key")
# From environment variable (TANGO_API_KEY)
client = TangoClient()Response shaping is the most powerful feature of the Tango SDK. It lets you request only the fields you need, dramatically reducing payload sizes and improving performance.
from tango import TangoClient, ShapeConfig
client = TangoClient(api_key="your-api-key")
# Custom shape - only fields you need
contracts = client.list_contracts(
shape="key,piid,recipient(display_name,uei),total_contract_value",
limit=10
)
# Access fields using dictionary syntax OR as an attribute
for contract in contracts.results:
print(f"PIID: {contract['piid']}")
print(f"Recipient: {contract['recipient']['display_name']}")
for contract in contracts.results:
print(f"PIID: {contract.piid}")
print(f"Recipient: {contract.recipient.display_name}")# List all agencies
agencies = client.list_agencies(page=1, limit=25)
# Get specific agency by code
agency = client.get_agency("GSA")# List/search contracts with filtering
contracts = client.list_contracts(
page=1,
limit=25,
# Filter parameters
keyword="software",
awarding_agency="4700", # GSA agency code
award_date_gte="2023-01-01",
fiscal_year=2024,
naics_code="541511"
)
# Filter by specific agency
contracts = client.list_contracts(
awarding_agency="4700", # GSA
limit=50
)Available Filter Parameters:
Text Search:
keyword- Search contract descriptions (mapped to 'search' API param)
Date Filters:
award_date_gte,award_date_lte- Award date rangepop_start_date_gte,pop_start_date_lte- Period of performance start date rangepop_end_date_gte,pop_end_date_lte- Period of performance end date rangeexpiring_gte,expiring_lte- Contract expiration date range
Party Filters:
awarding_agency,funding_agency- Agency codesrecipient_name,recipient_uei- Vendor/recipient filters
Classification:
naics_code,psc_code- Industry/product codesset_aside_type- Set-aside type
Type Filters:
fiscal_year,fiscal_year_gte,fiscal_year_lte- Fiscal year filtersaward_type- Award type code
Identifiers:
piid- Procurement Instrument Identifiersolicitation_identifier- Solicitation ID
Sorting:
sort,order- Sort results (e.g.,sort="award_date",order="desc")
Response Options:
shape,flat,flat_lists- Response shaping options
# List entities
entities = client.list_entities(
page=1,
limit=25
)
# Get specific entity by UEI or CAGE code
entity = client.get_entity("ZQGGHJH74DW7")# List contract forecasts
forecasts = client.list_forecasts(
agency="GSA",
limit=25
)# List opportunities/solicitations
opportunities = client.list_opportunities(
agency="DOD",
limit=25
)# List contract notices
notices = client.list_notices(
agency="DOD",
limit=25
)# List grant opportunities
grants = client.list_grants(
agency_code="HHS",
limit=25
)# List business types
business_types = client.list_business_types()All list methods return a PaginatedResponse object with metadata:
response = client.list_contracts(limit=25)
print(f"Total results: {response.count}")
print(f"Next page URL: {response.next}")
print(f"Previous page URL: {response.previous}")
# Iterate through results
for contract in response.results:
print(contract['description'])
# Get next page
if response.next:
next_response = client.list_contracts(page=2, limit=25)The SDK provides specific exception types for different error scenarios:
from tango import (
TangoClient,
TangoAPIError,
TangoAuthError,
TangoNotFoundError,
TangoRateLimitError,
TangoValidationError
)
client = TangoClient(api_key="your-api-key")
try:
contracts = client.list_contracts(limit=10)
except TangoAuthError:
print("Invalid API key or authentication required")
except TangoNotFoundError:
print("Resource not found")
except TangoValidationError as e:
print(f"Invalid parameters: {e.message}")
print(f"Details: {e.details}")
except TangoRateLimitError:
print("Rate limit exceeded")
except TangoAPIError as e:
print(f"API error: {e.message}")Create custom shapes to request exactly the fields you need:
# Simple fields
contracts = client.list_contracts(
shape="key,piid,description,total_contract_value"
)
# Nested relationships
contracts = client.list_contracts(
shape="key,piid,recipient(display_name,uei),place_of_performance(*))"
)
# Wildcards for all fields
contracts = client.list_contracts(
shape="key,piid,recipient(*)"
)Enable flattening to get dot-notation field names:
contracts = client.list_contracts(
shape="key,piid,recipient(display_name,uei)",
flat=True
)
# Returns: {"key": "...", "piid": "...", "recipient.display_name": "...", "recipient.uei": "..."}
# Flatten arrays with indexed keys
contracts = client.list_contracts(
shape="key,transactions(*)",
flat=True,
flat_lists=True
)
# Returns: {"key": "...", "transactions.0.action_date": "...", "transactions.0.obligated": "..."}Import TypedDict types for IDE autocomplete:
from tango import TangoClient, ShapeConfig
from tango.shapes import ContractMinimalShaped
client = TangoClient(api_key="your-api-key")
contracts = client.list_contracts(shape=ShapeConfig.CONTRACTS_MINIMAL)
# Type hint enables IDE autocomplete
contract: ContractMinimalShaped = contracts.results[0]
print(contract["piid"]) # IDE knows this field exists
print(contract["recipient"]["display_name"]) # Nested fields tooThis project uses uv for dependency management and tooling.
# Clone the repository
git clone https://github.com/makegov/tango-python.git
cd tango-python
# Install dependencies with uv
uv sync --all-extras
# Or install dev dependencies only
uv sync --group devThe SDK includes a comprehensive test suite with:
- Unit tests - Fast tests for core functionality
- Integration tests - Real API validation using VCR.py cassettes
# Run all tests
uv run pytest
# Run only unit tests
uv run pytest tests/ -m "not integration"
# Run only integration tests
uv run pytest tests/integration/
# Run integration tests with live API (requires TANGO_API_KEY)
export TANGO_API_KEY=your-api-key
export TANGO_USE_LIVE_API=true
uv run pytest tests/integration/
# Refresh cassettes with fresh API responses
export TANGO_API_KEY=your-api-key
export TANGO_REFRESH_CASSETTES=true
uv run pytest tests/integration/See tests/integration/README.md for detailed testing documentation.
# Format code
uv run ruff format tango/
# Lint code
uv run ruff check tango/
# Type checking
uv run mypy tango/
# Run all checks
uv run ruff format tango/ && uv run ruff check tango/ && uv run mypy tango/tango-python/
├── tango/ # Main SDK package
│ ├── __init__.py # Public API exports
│ ├── client.py # TangoClient implementation
│ ├── models.py # Data models and shape configs
│ ├── exceptions.py # Exception classes
│ └── shapes/ # Dynamic model system
│ ├── __init__.py # Shapes package exports
│ ├── parser.py # Shape string parser
│ ├── generator.py # TypedDict generator
│ ├── factory.py # Instance factory
│ ├── schema.py # Schema registry
│ ├── explicit_schemas.py # Predefined schemas (Contract, Entity, Grant, etc.)
│ ├── models.py # Shape specification models
│ └── types.py # TypedDict exports
├── tests/ # Test suite
│ ├── __init__.py
│ ├── conftest.py # Pytest configuration
│ ├── test_client.py # Unit tests for client
│ ├── test_models.py # Model tests
│ ├── test_shapes.py # Shape system tests
│ ├── cassettes/ # VCR.py HTTP cassettes
│ └── integration/ # Integration tests
│ ├── __init__.py
│ ├── README.md # Integration test docs
│ ├── conftest.py # Integration test fixtures
│ ├── validation.py # Validation utilities
│ ├── test_agencies_integration.py
│ ├── test_contracts_integration.py
│ ├── test_entities_integration.py
│ ├── test_forecasts_integration.py
│ ├── test_grants_integration.py
│ ├── test_notices_integration.py
│ ├── test_opportunities_integration.py
│ ├── test_reference_data_integration.py
│ └── test_edge_cases_integration.py
├── docs/ # Documentation
│ ├── API_REFERENCE.md # Complete API reference
│ ├── DEVELOPERS.md # Developer guide
│ ├── SHAPES.md # Shape system guide
│ └── quick_start.ipynb # Interactive quick start
├── scripts/ # Utility scripts
│ ├── README.md
│ ├── fetch_api_schema.py
│ └── generate_schemas_from_api.py
├── pyproject.toml # Project configuration
├── uv.lock # Dependency lock file
├── LICENSE # MIT License
├── CHANGELOG.md # Version history
└── README.md # This file
- Shape System Guide - Comprehensive guide to response shaping
- API Reference - Detailed API documentation
- Developer Guide - Technical documentation for developers
- Quick Start Notebook - Interactive Jupyter notebook with examples
- Python 3.12 or higher
- httpx >= 0.27.0
MIT License - see LICENSE for details.
For questions, issues, or feature requests:
- Email: tango@makegov.com
- Issues: GitHub Issues
- Documentation: https://docs.makegov.com/tango-python
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Run tests (
uv run pytest) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request