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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.4.1] - 2026-03-03

### Added
- GSA eLibrary contracts: `list_gsa_elibrary_contracts`, `get_gsa_elibrary_contract` with shaping and filter params (`contract_number`, `key`, `piid`, `schedule`, `search`, `sin`, `uei`).

### Changed
- Conformance: replaced `**kwargs`/`**filters` with explicit filter parameters on `list_contracts`, `list_idvs`, `list_entities`, `list_forecasts`, `list_grants`, `list_notices`, `list_opportunities` for full filter/shape conformance. Backward compatibility preserved for `list_contracts(filters=SearchFilters(...))`.

## [0.4.0] - 2026-02-24

### Added
Expand Down
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.4.0"
version = "0.4.1"
description = "Python SDK for the Tango API"
readme = "README.md"
requires-python = ">=3.12"
Expand Down
43 changes: 24 additions & 19 deletions scripts/check_filter_shape_conformance.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import ast
import json
from pathlib import Path
from typing import Any, Type
from typing import Any

REPO_ROOT = Path(__file__).resolve().parents[1]
CLIENT_PATH = REPO_ROOT / "tango" / "client.py"
Expand Down Expand Up @@ -49,32 +49,34 @@
"entities": "list_entities",
"agencies": "list_agencies",
"naics": "list_naics",
"gsa_elibrary_contracts": "list_gsa_elibrary_contracts",
# Resources not yet implemented in SDK
"assistance": None,
"offices": None,
}


def get_shape_config_entries() -> list[tuple[str, str, Type[Any]]]:
def get_shape_config_entries() -> list[tuple[str, str, type[Any]]]:
"""Return (shape_name, shape_string, model_class) for every ShapeConfig constant."""
from tango.models import (
IDV,
OTA,
OTIDV,
Contract,
Entity,
Forecast,
Grant,
IDV,
GsaElibraryContract,
Notice,
OTA,
Organization,
Opportunity,
OTIDV,
Organization,
ShapeConfig,
Subaward,
Vehicle,
)

# ShapeConfig constant name -> (shape string, model class for validation)
entries: list[tuple[str, str, Type[Any]]] = []
entries: list[tuple[str, str, type[Any]]] = []
configs = [
("CONTRACTS_MINIMAL", ShapeConfig.CONTRACTS_MINIMAL, Contract),
("ENTITIES_MINIMAL", ShapeConfig.ENTITIES_MINIMAL, Entity),
Expand All @@ -92,6 +94,11 @@ def get_shape_config_entries() -> list[tuple[str, str, Type[Any]]]:
("OTAS_MINIMAL", ShapeConfig.OTAS_MINIMAL, OTA),
("OTIDVS_MINIMAL", ShapeConfig.OTIDVS_MINIMAL, OTIDV),
("SUBAWARDS_MINIMAL", ShapeConfig.SUBAWARDS_MINIMAL, Subaward),
(
"GSA_ELIBRARY_CONTRACTS_MINIMAL",
ShapeConfig.GSA_ELIBRARY_CONTRACTS_MINIMAL,
GsaElibraryContract,
),
]
for name, shape_str, model_cls in configs:
entries.append((name, shape_str, model_cls))
Expand Down Expand Up @@ -166,16 +173,12 @@ def run_check(manifest_path: Path) -> tuple[list[str], list[str]]:
# Explicitly marked as not implemented
runtime_filters = payload.get("runtime", {}).get("filter_params", [])
if runtime_filters:
warnings.append(
f"{resource_name}: no SDK method implemented for this resource"
)
warnings.append(f"{resource_name}: no SDK method implemented for this resource")
continue

if sdk_method not in methods:
# Method mapped but not found in client.py
errors.append(
f"{resource_name}: mapped method `{sdk_method}` not found in SDK client"
)
errors.append(f"{resource_name}: mapped method `{sdk_method}` not found in SDK client")
continue

runtime_filters = set(payload.get("runtime", {}).get("filter_params", []))
Expand Down Expand Up @@ -212,12 +215,14 @@ def get_unmapped_resources(manifest_path: Path) -> list[dict[str, Any]]:
# Check if unmapped or method doesn't exist
if sdk_method is None or sdk_method not in methods:
runtime = payload.get("runtime", {}) or {}
unmapped.append({
"resource": resource_name,
"expected_method": sdk_method,
"filter_params": runtime.get("filter_params", []),
"pagination_class": (runtime.get("pagination") or {}).get("class", ""),
})
unmapped.append(
{
"resource": resource_name,
"expected_method": sdk_method,
"filter_params": runtime.get("filter_params", []),
"pagination_class": (runtime.get("pagination") or {}).get("class", ""),
}
)
return unmapped


Expand Down
4 changes: 3 additions & 1 deletion tango/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
TangoValidationError,
)
from .models import (
GsaElibraryContract,
PaginatedResponse,
SearchFilters,
ShapeConfig,
Expand All @@ -26,14 +27,15 @@
TypeGenerator,
)

__version__ = "0.4.0"
__version__ = "0.4.1"
__all__ = [
"TangoClient",
"TangoAPIError",
"TangoAuthError",
"TangoNotFoundError",
"TangoValidationError",
"TangoRateLimitError",
"GsaElibraryContract",
"PaginatedResponse",
"SearchFilters",
"ShapeConfig",
Expand Down
Loading
Loading