Skip to main content

Python SDK for the Tango API

Project description

Tango Python SDK

A modern Python SDK for the Tango API by MakeGov, featuring dynamic response shaping and comprehensive type hints.

Features

  • 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, webhooks) [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

Installation

Requirements: Python 3.12 or higher

pip install tango-python

Or with uv:

uv pip install tango-python

Quick Start

from 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()

Core Concepts

Dynamic Response Shaping

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}")

API Methods

Agencies

# List all agencies
agencies = client.list_agencies(page=1, limit=25)

# Get specific agency by code
agency = client.get_agency("GSA")

Contracts

# 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 range
  • pop_start_date_gte, pop_start_date_lte - Period of performance start date range
  • pop_end_date_gte, pop_end_date_lte - Period of performance end date range
  • expiring_gte, expiring_lte - Contract expiration date range

Party Filters:

  • awarding_agency, funding_agency - Agency codes
  • recipient_name, recipient_uei - Vendor/recipient filters

Classification:

  • naics_code, psc_code - Industry/product codes
  • set_aside_type - Set-aside type

Type Filters:

  • fiscal_year, fiscal_year_gte, fiscal_year_lte - Fiscal year filters
  • award_type - Award type code

Identifiers:

  • piid - Procurement Instrument Identifier
  • solicitation_identifier - Solicitation ID

Sorting:

  • sort, order - Sort results (e.g., sort="award_date", order="desc")

Response Options:

  • shape, flat, flat_lists - Response shaping options

Entities (Vendors/Recipients)

# List entities
entities = client.list_entities(
    page=1,
    limit=25
)

# Get specific entity by UEI or CAGE code
entity = client.get_entity("ZQGGHJH74DW7")

Forecasts

# List contract forecasts
forecasts = client.list_forecasts(
    agency="GSA",
    limit=25
)

Opportunities

# List opportunities/solicitations
opportunities = client.list_opportunities(
    agency="DOD",
    limit=25
)

Notices

# List contract notices
notices = client.list_notices(
    agency="DOD",
    limit=25
)

Grants

# List grant opportunities
grants = client.list_grants(
    agency_code="HHS",
    limit=25
)

Business Types

# List business types
business_types = client.list_business_types()

Pagination

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)

Error Handling

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}")

Advanced Features

Custom Shapes

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(*)"
)

Flattened Responses

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": "..."}

Type Hints with IDE Support

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 too

Development

This project uses uv for dependency management and tooling.

Setup

# 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 dev

Testing

The 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.

Code Quality

# 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/

Project Structure

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

Documentation

Requirements

  • Python 3.12 or higher
  • httpx >= 0.27.0

License

MIT License - see LICENSE for details.

Support

For questions, issues, or feature requests:

Contributing

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tango_python-0.3.0.tar.gz (358.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tango_python-0.3.0-py3-none-any.whl (52.8 kB view details)

Uploaded Python 3

File details

Details for the file tango_python-0.3.0.tar.gz.

File metadata

  • Download URL: tango_python-0.3.0.tar.gz
  • Upload date:
  • Size: 358.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for tango_python-0.3.0.tar.gz
Algorithm Hash digest
SHA256 ff2e18fee50036e8ff8806c33a7d31317348c1f7bce0c6568004ab23f19e05af
MD5 339ef8f3478db0ad0b885602960c6c33
BLAKE2b-256 ad26c3dc60c60ec8e9ea688402884f49c016d026c545ae0bb061c5748ac1169c

See more details on using hashes here.

File details

Details for the file tango_python-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: tango_python-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 52.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for tango_python-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ea428d7b12c777482f5710ac4e28f19b52a000b51e4e6bf7f30901d5a1ab4a80
MD5 6f2e90cf721a95633df0fd01d732895e
BLAKE2b-256 a85e95dbbf853d76ee7bc9ff8a21f9ce1e9a8f740d201fe0c8c4839a9c6aa458

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page