tango-python 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tango_python-0.2.0 → tango_python-0.3.0}/.github/workflows/publish.yml +2 -1
- {tango_python-0.2.0 → tango_python-0.3.0}/.gitignore +3 -1
- tango_python-0.3.0/CHANGELOG.md +27 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/PKG-INFO +2 -2
- {tango_python-0.2.0 → tango_python-0.3.0}/README.md +1 -1
- {tango_python-0.2.0 → tango_python-0.3.0}/docs/API_REFERENCE.md +245 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/pyproject.toml +2 -1
- tango_python-0.3.0/scripts/README.md +76 -0
- tango_python-0.3.0/scripts/pr_review.py +473 -0
- tango_python-0.3.0/scripts/test_production.py +86 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tango/__init__.py +13 -1
- {tango_python-0.2.0 → tango_python-0.3.0}/tango/client.py +681 -14
- {tango_python-0.2.0 → tango_python-0.3.0}/tango/models.py +119 -1
- {tango_python-0.2.0 → tango_python-0.3.0}/tango/shapes/explicit_schemas.py +302 -0
- tango_python-0.3.0/tests/cassettes/TestContractsIntegration.test_contract_cursor_pagination +169 -0
- tango_python-0.3.0/tests/cassettes/TestIDVsIntegration.test_get_idv_summary +252 -0
- tango_python-0.3.0/tests/cassettes/TestIDVsIntegration.test_get_idv_uses_default_shape +494 -0
- tango_python-0.3.0/tests/cassettes/TestIDVsIntegration.test_list_idv_awards_uses_default_shape +302 -0
- tango_python-0.3.0/tests/cassettes/TestIDVsIntegration.test_list_idv_child_idvs_uses_default_shape +302 -0
- tango_python-0.3.0/tests/cassettes/TestIDVsIntegration.test_list_idv_summary_awards +256 -0
- tango_python-0.3.0/tests/cassettes/TestIDVsIntegration.test_list_idv_transactions +304 -0
- tango_python-0.3.0/tests/cassettes/TestIDVsIntegration.test_list_idvs_uses_default_shape_and_keyset_params +284 -0
- tango_python-0.3.0/tests/cassettes/TestProductionSmokeWithCassettes.test_contract_cursor_pagination +169 -0
- tango_python-0.3.0/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists +1352 -0
- tango_python-0.3.0/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape +1505 -0
- tango_python-0.3.0/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search +812 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/test_contracts_integration.py +65 -14
- tango_python-0.3.0/tests/integration/test_vehicles_idvs_integration.py +467 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/validation.py +2 -0
- tango_python-0.3.0/tests/production/__init__.py +5 -0
- tango_python-0.3.0/tests/production/conftest.py +79 -0
- tango_python-0.3.0/tests/production/test_production_smoke.py +314 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/test_client.py +254 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/uv.lock +1 -1
- tango_python-0.2.0/CHANGELOG.md +0 -10
- tango_python-0.2.0/scripts/README.md +0 -13
- {tango_python-0.2.0 → tango_python-0.3.0}/.env.example +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/.github/workflows/lint.yml +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/.github/workflows/test.yml +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/LICENSE +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/ROADMAP.md +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/docs/DEVELOPERS.md +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/docs/SHAPES.md +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/docs/quick_start.ipynb +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/scripts/fetch_api_schema.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/scripts/generate_schemas_from_api.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tango/exceptions.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tango/shapes/__init__.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tango/shapes/factory.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tango/shapes/generator.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tango/shapes/models.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tango/shapes/parser.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tango/shapes/schema.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tango/shapes/types.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/__init__.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestAgenciesIntegration.test_get_agency +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestAgenciesIntegration.test_list_agencies +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_field_type_validation +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_parsing_consistency +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestBusinessTypesIntegration.test_list_business_types +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_combined_filters_work_together +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_contract_data_object_parsing +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_contract_field_types +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[keyword-software] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[psc_code-R425] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_awarding_agency_filter +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_date_range_filter +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_flat +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_naics_code_filter +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[custom-key,piid,recipient(display_name),total_contract_value,award_date] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[default-None] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[detailed-key,piid,award_date,description,total_contract_value,obligated,fiscal_year,set_aside,recipient(display_name,uei),awarding_office(-),place_of_performa...ce114a3c47e2037aaa3c15d00b7031bd +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[minimal-key,piid,award_date,recipient(display_name),description,total_contract_value] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_new_expiring_filters +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_new_fiscal_year_range_filters +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_new_identifier_filters +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_search_contracts_with_filters +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_search_filters_object_with_new_parameters +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[asc-] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[desc--] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_contracts +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_entities +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_date_field_parsing_edge_cases +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_decimal_field_parsing_edge_cases +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_empty_list_responses +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_entity_parsing_with_various_address_formats +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_flattened_responses_with_flat_lists +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_list_field_parsing_consistency +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_nested_objects_with_missing_data +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_null_missing_fields_in_contracts +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_with_minimal_shape_sparse_data +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEntitiesIntegration.test_entity_field_types +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEntitiesIntegration.test_entity_location_parsing +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEntitiesIntegration.test_entity_parsing_with_business_types +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEntitiesIntegration.test_entity_with_various_identifiers +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_by_uei +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_flat +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_search +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[comprehensive-uei,legal_business_name,dba_name,cage_code,business_types,primary_naics,naics_codes,psc_codes,email_address,entity_url,description,capabilities,ke...95fcff7efcf320ecc846393dd484321d +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[custom-uei,legal_business_name,cage_code] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[with_address-uei,legal_business_name,cage_code,business_types,physical_address] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestForecastsIntegration.test_forecast_field_types +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[custom-id,title,anticipated_award_date] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[default-None] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[detailed-id,source_system,external_id,title,description,anticipated_award_date,fiscal_year,naics_code,status,is_active] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[minimal-id,title,anticipated_award_date,fiscal_year,naics_code,status] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestGrantsIntegration.test_grant_field_types +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestGrantsIntegration.test_grant_pagination +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[custom-grant_id,title,opportunity_number] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[default-None] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[detailed-grant_id,opportunity_number,title,status(-),agency_code,description,last_updated,cfda_numbers(number,title),applicant_types(-),funding_categories(-)] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[minimal-grant_id,opportunity_number,title,status(-),agency_code] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[custom-notice_id,title,solicitation_number] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[default-None] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[detailed-notice_id,title,description,solicitation_number,posted_date,naics_code,set_aside,office(-),place_of_performance(-)] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestNoticesIntegration.test_notice_field_types +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestNoticesIntegration.test_notice_pagination +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestNoticesIntegration.test_notice_with_meta_fields +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[custom-opportunity_id,title,solicitation_number] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[default-None] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[detailed-opportunity_id,title,description,solicitation_number,response_deadline,first_notice_date,last_notice_date,active,naics_code,psc_code,set_asid...23b6b4502ddd665b7184afcff6c6d8d9 +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestOpportunitiesIntegration.test_opportunity_field_types +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[custom-key,piid,description] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[minimal-key,piid,award_date,recipient(display_name),description,total_contract_value] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[ultra_minimal-key,piid,recipient(display_name),total_contract_value] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[with_address-uei,legal_business_name,cage_code,business_types,physical_address] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestTypeHintsIntegration.test_notices_dict_access[detailed-notice_id,title,description,solicitation_number,posted_date,naics_code,set_aside,office(-),place_of_performance(-)] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestTypeHintsIntegration.test_notices_dict_access[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestTypeHintsIntegration.test_opportunities_dict_access[detailed-opportunity_id,title,description,solicitation_number,response_deadline,first_notice_date,last_notice_date,active,naics_code,psc_code,set_aside,sam_url,office(-),place_of_performance(-)] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestTypeHintsIntegration.test_opportunities_dict_access[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/conftest.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/README.md +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/__init__.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/conftest.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/test_agencies_integration.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/test_edge_cases_integration.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/test_entities_integration.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/test_forecasts_integration.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/test_grants_integration.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/test_notices_integration.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/test_opportunities_integration.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/test_reference_data_integration.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/test_models.py +0 -0
- {tango_python-0.2.0 → tango_python-0.3.0}/tests/test_shapes.py +0 -0
|
@@ -3,6 +3,7 @@ name: Publish to PyPI
|
|
|
3
3
|
on:
|
|
4
4
|
release:
|
|
5
5
|
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
6
7
|
|
|
7
8
|
jobs:
|
|
8
9
|
build-and-publish:
|
|
@@ -24,5 +25,5 @@ jobs:
|
|
|
24
25
|
|
|
25
26
|
- name: Publish to PyPI
|
|
26
27
|
env:
|
|
27
|
-
UV_PUBLISH_TOKEN: ${{ secrets.
|
|
28
|
+
UV_PUBLISH_TOKEN: ${{ secrets.TANGO_PYPI_TOKEN }}
|
|
28
29
|
run: uv publish
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
## [0.3.0] - 2026-02-09
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- Vehicles endpoints: `list_vehicles`, `get_vehicle`, and `list_vehicle_awardees` (supports shaping + flattening). (refs `makegov/tango#1328`)
|
|
18
|
+
- 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`)
|
|
19
|
+
- Webhooks v2 client support: event type discovery, subscription CRUD, endpoint management, test delivery, and sample payload helpers. (refs `makegov/tango#1274`)
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Expanded explicit schemas to support common IDV shaping expansions (award offices, officers, period of performance, etc.).
|
|
23
|
+
- HTTP client now supports PATCH/DELETE helpers for webhook management endpoints.
|
|
24
|
+
|
|
25
|
+
## [0.2.0] - 2025-11-16
|
|
26
|
+
|
|
27
|
+
- Entirely refactored SDK
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tango-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Python SDK for the Tango API
|
|
5
5
|
Project-URL: Homepage, https://github.com/makegov/tango-python
|
|
6
6
|
Project-URL: Documentation, https://docs.makegov.com/tango-python
|
|
@@ -61,7 +61,7 @@ A modern Python SDK for the [Tango API](https://tango.makegov.com) by MakeGov, f
|
|
|
61
61
|
|
|
62
62
|
- **Dynamic Response Shaping** - Request only the fields you need, reducing payload sizes by 60-80%
|
|
63
63
|
- **Full Type Safety** - Runtime-generated TypedDict types with accurate type hints for IDE autocomplete
|
|
64
|
-
- **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]
|
|
64
|
+
- **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]
|
|
65
65
|
- **Flexible Data Access** - Dictionary-based response objects with validation
|
|
66
66
|
- **Modern Python** - Built for Python 3.12+ using modern async-ready patterns
|
|
67
67
|
- **Production-Ready** - Comprehensive test suite with VCR.py-based integration tests
|
|
@@ -6,7 +6,7 @@ A modern Python SDK for the [Tango API](https://tango.makegov.com) by MakeGov, f
|
|
|
6
6
|
|
|
7
7
|
- **Dynamic Response Shaping** - Request only the fields you need, reducing payload sizes by 60-80%
|
|
8
8
|
- **Full Type Safety** - Runtime-generated TypedDict types with accurate type hints for IDE autocomplete
|
|
9
|
-
- **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]
|
|
9
|
+
- **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]
|
|
10
10
|
- **Flexible Data Access** - Dictionary-based response objects with validation
|
|
11
11
|
- **Modern Python** - Built for Python 3.12+ using modern async-ready patterns
|
|
12
12
|
- **Production-Ready** - Comprehensive test suite with VCR.py-based integration tests
|
|
@@ -7,12 +7,15 @@ Complete reference for all Tango Python SDK methods and functionality.
|
|
|
7
7
|
- [Client Initialization](#client-initialization)
|
|
8
8
|
- [Agencies](#agencies)
|
|
9
9
|
- [Contracts](#contracts)
|
|
10
|
+
- [IDVs](#idvs)
|
|
11
|
+
- [Vehicles](#vehicles)
|
|
10
12
|
- [Entities](#entities)
|
|
11
13
|
- [Forecasts](#forecasts)
|
|
12
14
|
- [Opportunities](#opportunities)
|
|
13
15
|
- [Notices](#notices)
|
|
14
16
|
- [Grants](#grants)
|
|
15
17
|
- [Business Types](#business-types)
|
|
18
|
+
- [Webhooks](#webhooks)
|
|
16
19
|
- [Response Objects](#response-objects)
|
|
17
20
|
- [Error Handling](#error-handling)
|
|
18
21
|
|
|
@@ -271,6 +274,119 @@ contracts = client.list_contracts(
|
|
|
271
274
|
|
|
272
275
|
---
|
|
273
276
|
|
|
277
|
+
## Vehicles
|
|
278
|
+
|
|
279
|
+
Vehicles provide a solicitation-centric way to discover groups of related IDVs and (optionally) expand into the underlying awards via shaping.
|
|
280
|
+
|
|
281
|
+
### list_vehicles()
|
|
282
|
+
|
|
283
|
+
List vehicles with optional vehicle-level full-text search.
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
vehicles = client.list_vehicles(
|
|
287
|
+
page=1,
|
|
288
|
+
limit=25,
|
|
289
|
+
search="GSA schedule",
|
|
290
|
+
shape=ShapeConfig.VEHICLES_MINIMAL,
|
|
291
|
+
flat=False,
|
|
292
|
+
flat_lists=False,
|
|
293
|
+
)
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Parameters:**
|
|
297
|
+
- `page` (int): Page number (default: 1)
|
|
298
|
+
- `limit` (int): Results per page (default: 25, max: 100)
|
|
299
|
+
- `search` (str, optional): Vehicle-level search term
|
|
300
|
+
- `shape` (str, optional): Shape string (defaults to `ShapeConfig.VEHICLES_MINIMAL`)
|
|
301
|
+
- `flat` (bool): Flatten nested objects in shaped response
|
|
302
|
+
- `flat_lists` (bool): Flatten arrays using indexed keys
|
|
303
|
+
- `joiner` (str): Joiner used when `flat=True` (default: `"."`)
|
|
304
|
+
|
|
305
|
+
**Returns:** [PaginatedResponse](#paginatedresponse) with vehicle dictionaries
|
|
306
|
+
|
|
307
|
+
### get_vehicle()
|
|
308
|
+
|
|
309
|
+
Get a single vehicle by UUID.
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
vehicle = client.get_vehicle(
|
|
313
|
+
uuid="00000000-0000-0000-0000-000000000001",
|
|
314
|
+
shape=ShapeConfig.VEHICLES_COMPREHENSIVE,
|
|
315
|
+
)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
**Notes:**
|
|
319
|
+
- On the vehicle detail endpoint, `search` filters **expanded awardees** when your `shape` includes `awardees(...)` (it does not filter the vehicle itself).
|
|
320
|
+
|
|
321
|
+
### list_vehicle_awardees()
|
|
322
|
+
|
|
323
|
+
List the IDV awardees for a vehicle.
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
awardees = client.list_vehicle_awardees(
|
|
327
|
+
uuid="00000000-0000-0000-0000-000000000001",
|
|
328
|
+
shape=ShapeConfig.VEHICLE_AWARDEES_MINIMAL,
|
|
329
|
+
)
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## IDVs
|
|
335
|
+
|
|
336
|
+
IDVs (indefinite delivery vehicles) are the parent “vehicle award” records that can have child awards/orders under them.
|
|
337
|
+
|
|
338
|
+
### list_idvs()
|
|
339
|
+
|
|
340
|
+
```python
|
|
341
|
+
idvs = client.list_idvs(
|
|
342
|
+
limit=25,
|
|
343
|
+
cursor=None,
|
|
344
|
+
shape=ShapeConfig.IDVS_MINIMAL,
|
|
345
|
+
awarding_agency="4700",
|
|
346
|
+
)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Notes:
|
|
350
|
+
|
|
351
|
+
- This endpoint uses **keyset pagination** (`cursor` + `limit`) rather than page numbers.
|
|
352
|
+
|
|
353
|
+
### get_idv()
|
|
354
|
+
|
|
355
|
+
```python
|
|
356
|
+
idv = client.get_idv("SOME_IDV_KEY", shape=ShapeConfig.IDVS_COMPREHENSIVE)
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### list_idv_awards()
|
|
360
|
+
|
|
361
|
+
Lists child awards (contracts) under an IDV.
|
|
362
|
+
|
|
363
|
+
```python
|
|
364
|
+
awards = client.list_idv_awards("SOME_IDV_KEY", limit=25)
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### list_idv_child_idvs()
|
|
368
|
+
|
|
369
|
+
Lists child IDVs under an IDV.
|
|
370
|
+
|
|
371
|
+
```python
|
|
372
|
+
children = client.list_idv_child_idvs("SOME_IDV_KEY", limit=25)
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### list_idv_transactions()
|
|
376
|
+
|
|
377
|
+
```python
|
|
378
|
+
tx = client.list_idv_transactions("SOME_IDV_KEY", limit=100)
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### get_idv_summary() / list_idv_summary_awards()
|
|
382
|
+
|
|
383
|
+
```python
|
|
384
|
+
summary = client.get_idv_summary("SOLICITATION_IDENTIFIER")
|
|
385
|
+
awards = client.list_idv_summary_awards("SOLICITATION_IDENTIFIER", limit=25)
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
274
390
|
## Entities
|
|
275
391
|
|
|
276
392
|
Vendors, recipients, and organizations doing business with the government.
|
|
@@ -606,6 +722,135 @@ for biz_type in business_types.results:
|
|
|
606
722
|
|
|
607
723
|
---
|
|
608
724
|
|
|
725
|
+
## Webhooks
|
|
726
|
+
|
|
727
|
+
Webhook APIs let **Large / Enterprise** users manage subscription filters for outbound Tango webhooks.
|
|
728
|
+
|
|
729
|
+
### list_webhook_event_types()
|
|
730
|
+
|
|
731
|
+
Discover supported `event_type` values and subject types.
|
|
732
|
+
|
|
733
|
+
```python
|
|
734
|
+
info = client.list_webhook_event_types()
|
|
735
|
+
print(info.event_types[0].event_type)
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
### list_webhook_subscriptions()
|
|
739
|
+
|
|
740
|
+
```python
|
|
741
|
+
subs = client.list_webhook_subscriptions(page=1, page_size=25)
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
Notes:
|
|
745
|
+
|
|
746
|
+
- This endpoint uses `page` + `page_size` (tier-capped) rather than `limit`.
|
|
747
|
+
|
|
748
|
+
### create_webhook_subscription()
|
|
749
|
+
|
|
750
|
+
```python
|
|
751
|
+
sub = client.create_webhook_subscription(
|
|
752
|
+
"Track specific vendors",
|
|
753
|
+
{
|
|
754
|
+
"records": [
|
|
755
|
+
{"event_type": "awards.new_award", "subject_type": "entity", "subject_ids": ["UEI123ABC"]},
|
|
756
|
+
{"event_type": "awards.new_transaction", "subject_type": "entity", "subject_ids": ["UEI123ABC"]},
|
|
757
|
+
]
|
|
758
|
+
},
|
|
759
|
+
)
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
Notes:
|
|
763
|
+
|
|
764
|
+
- Prefer v2 fields: `subject_type` + `subject_ids`.
|
|
765
|
+
- Legacy compatibility: `resource_ids` is accepted as an alias for `subject_ids` (don’t send both).
|
|
766
|
+
- Catch-all: `subject_ids: []` means “all subjects” for that record and is **Enterprise-only**. Large tier users must list specific IDs.
|
|
767
|
+
|
|
768
|
+
### update_webhook_subscription()
|
|
769
|
+
|
|
770
|
+
```python
|
|
771
|
+
sub = client.update_webhook_subscription("SUBSCRIPTION_UUID", subscription_name="Updated name")
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
### delete_webhook_subscription()
|
|
775
|
+
|
|
776
|
+
```python
|
|
777
|
+
client.delete_webhook_subscription("SUBSCRIPTION_UUID")
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
### list_webhook_endpoints()
|
|
781
|
+
|
|
782
|
+
List your webhook endpoint(s).
|
|
783
|
+
|
|
784
|
+
```python
|
|
785
|
+
endpoints = client.list_webhook_endpoints(page=1, limit=25)
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
### get_webhook_endpoint()
|
|
789
|
+
|
|
790
|
+
```python
|
|
791
|
+
endpoint = client.get_webhook_endpoint("ENDPOINT_UUID")
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
### create_webhook_endpoint() / update_webhook_endpoint() / delete_webhook_endpoint()
|
|
795
|
+
|
|
796
|
+
In production, MakeGov provisions the initial endpoint for you. These are most useful for dev/self-service.
|
|
797
|
+
|
|
798
|
+
```python
|
|
799
|
+
endpoint = client.create_webhook_endpoint("https://example.com/tango/webhooks")
|
|
800
|
+
endpoint = client.update_webhook_endpoint(endpoint.id, is_active=False)
|
|
801
|
+
client.delete_webhook_endpoint(endpoint.id)
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
### test_webhook_delivery()
|
|
805
|
+
|
|
806
|
+
Send an immediate test webhook to your configured endpoint.
|
|
807
|
+
|
|
808
|
+
```python
|
|
809
|
+
result = client.test_webhook_delivery()
|
|
810
|
+
print(result.success, result.status_code)
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
### get_webhook_sample_payload()
|
|
814
|
+
|
|
815
|
+
Fetch Tango-shaped sample deliveries (and sample subscription request bodies).
|
|
816
|
+
|
|
817
|
+
```python
|
|
818
|
+
sample = client.get_webhook_sample_payload(event_type="awards.new_award")
|
|
819
|
+
print(sample["event_type"])
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
### Deliveries / redelivery
|
|
823
|
+
|
|
824
|
+
The API does not currently expose a public `/api/webhooks/deliveries/` or redelivery endpoint. Use:
|
|
825
|
+
|
|
826
|
+
- `test_webhook_delivery()` for connectivity checks
|
|
827
|
+
- `get_webhook_sample_payload()` for building handlers + subscription payloads
|
|
828
|
+
|
|
829
|
+
### Receiving webhooks (signature verification)
|
|
830
|
+
|
|
831
|
+
Every delivery includes an HMAC signature header:
|
|
832
|
+
|
|
833
|
+
- `X-Tango-Signature: sha256=<hex digest>`
|
|
834
|
+
|
|
835
|
+
Compute the digest over the **raw request body bytes** using your shared secret.
|
|
836
|
+
|
|
837
|
+
```python
|
|
838
|
+
import hashlib
|
|
839
|
+
import hmac
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
def verify_tango_webhook_signature(secret: str, raw_body: bytes, signature_header: str | None) -> bool:
|
|
843
|
+
if not signature_header:
|
|
844
|
+
return False
|
|
845
|
+
sig = signature_header.strip()
|
|
846
|
+
if sig.startswith("sha256="):
|
|
847
|
+
sig = sig[len("sha256=") :]
|
|
848
|
+
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
|
|
849
|
+
return hmac.compare_digest(expected, sig)
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
---
|
|
853
|
+
|
|
609
854
|
## Response Objects
|
|
610
855
|
|
|
611
856
|
### PaginatedResponse
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tango-python"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Python SDK for the Tango API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -97,6 +97,7 @@ markers = [
|
|
|
97
97
|
"live: Tests that always use live API (skip cassettes)",
|
|
98
98
|
"cached: Tests that only run with cached responses",
|
|
99
99
|
"slow: Tests that are slow to execute",
|
|
100
|
+
"production: Production API smoke tests that run against live API",
|
|
100
101
|
]
|
|
101
102
|
|
|
102
103
|
[tool.coverage.run]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Development Scripts
|
|
2
|
+
|
|
3
|
+
This directory contains utility scripts used during development and maintenance of the tango-python library.
|
|
4
|
+
|
|
5
|
+
## Scripts
|
|
6
|
+
|
|
7
|
+
### Schema and API Tools
|
|
8
|
+
|
|
9
|
+
- **`fetch_api_schema.py`** - Fetches the OpenAPI schema from the Tango API and saves it locally
|
|
10
|
+
- **`generate_schemas_from_api.py`** - Generates schema definitions from the API reference (outputs to stdout)
|
|
11
|
+
|
|
12
|
+
### Testing and Validation
|
|
13
|
+
|
|
14
|
+
- **`test_production.py`** - Runs production API smoke tests against the live API
|
|
15
|
+
- **`pr_review.py`** - Runs configurable validation checks for PR review (linting, type checking, tests)
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
These scripts are primarily for maintainers and are not part of the public API.
|
|
20
|
+
|
|
21
|
+
### Production API Testing
|
|
22
|
+
|
|
23
|
+
Run smoke tests against the production API:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Requires TANGO_API_KEY environment variable
|
|
27
|
+
uv run python scripts/test_production.py
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
This runs fast smoke tests (~30-60 seconds) that validate core SDK functionality against the live production API.
|
|
31
|
+
|
|
32
|
+
### PR Review Validation
|
|
33
|
+
|
|
34
|
+
Run validation checks for PR review. **Automatically detects PR context** from GitHub Actions, GitHub CLI, or git branch.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Auto-detect PR and run validation (default: production mode)
|
|
38
|
+
uv run python scripts/pr_review.py
|
|
39
|
+
|
|
40
|
+
# Review a specific PR number
|
|
41
|
+
uv run python scripts/pr_review.py --pr-number 123
|
|
42
|
+
|
|
43
|
+
# Smoke tests only
|
|
44
|
+
uv run python scripts/pr_review.py --mode smoke
|
|
45
|
+
|
|
46
|
+
# Quick validation (linting + type checking, no tests)
|
|
47
|
+
uv run python scripts/pr_review.py --mode quick
|
|
48
|
+
|
|
49
|
+
# Full validation (all checks including all tests)
|
|
50
|
+
uv run python scripts/pr_review.py --mode full
|
|
51
|
+
|
|
52
|
+
# Check only changed files (auto-enabled when PR is detected)
|
|
53
|
+
uv run python scripts/pr_review.py --mode production --changed-files-only
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Validation Modes:**
|
|
57
|
+
- `smoke` - Production API smoke tests only
|
|
58
|
+
- `quick` - Linting + type checking (no tests)
|
|
59
|
+
- `full` - All checks (linting + type checking + all tests)
|
|
60
|
+
- `production` - Production API smoke tests + linting + type checking (default)
|
|
61
|
+
|
|
62
|
+
**PR Detection:**
|
|
63
|
+
The script automatically detects PR information from:
|
|
64
|
+
- GitHub Actions environment variables (`GITHUB_EVENT_PATH`, `GITHUB_PR_NUMBER`)
|
|
65
|
+
- GitHub CLI (`gh pr view` - requires `gh auth login`)
|
|
66
|
+
- Current git branch
|
|
67
|
+
|
|
68
|
+
When a PR is detected, the script displays PR information and automatically:
|
|
69
|
+
- Detects the base branch from the PR
|
|
70
|
+
- Checks only changed files
|
|
71
|
+
- Shows PR URL in summary
|
|
72
|
+
|
|
73
|
+
## Requirements
|
|
74
|
+
|
|
75
|
+
- `TANGO_API_KEY` environment variable (required for production API tests)
|
|
76
|
+
- All dependencies installed: `uv sync --all-extras`
|