tango-python 0.2.0__tar.gz → 0.4.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.4.0}/.github/workflows/lint.yml +9 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/.github/workflows/publish.yml +2 -1
- {tango_python-0.2.0 → tango_python-0.4.0}/.gitignore +5 -1
- tango_python-0.4.0/CHANGELOG.md +32 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/PKG-INFO +14 -7
- {tango_python-0.2.0 → tango_python-0.4.0}/README.md +13 -6
- tango_python-0.4.0/ROADMAP.md +22 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/docs/API_REFERENCE.md +290 -1
- {tango_python-0.2.0 → tango_python-0.4.0}/docs/DEVELOPERS.md +27 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/docs/SHAPES.md +30 -2
- {tango_python-0.2.0 → tango_python-0.4.0}/pyproject.toml +2 -1
- tango_python-0.4.0/scripts/README.md +100 -0
- tango_python-0.4.0/scripts/check_filter_shape_conformance.py +267 -0
- tango_python-0.4.0/scripts/pr_review.py +495 -0
- tango_python-0.4.0/scripts/test_production.py +86 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tango/__init__.py +13 -1
- tango_python-0.4.0/tango/client.py +1964 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tango/models.py +180 -1
- {tango_python-0.2.0 → tango_python-0.4.0}/tango/shapes/explicit_schemas.py +377 -0
- tango_python-0.4.0/tests/cassettes/TestAssistanceIntegration.test_list_assistance +121 -0
- tango_python-0.4.0/tests/cassettes/TestContractsIntegration.test_contract_cursor_pagination +169 -0
- tango_python-0.4.0/tests/cassettes/TestIDVsIntegration.test_get_idv_summary +252 -0
- tango_python-0.4.0/tests/cassettes/TestIDVsIntegration.test_get_idv_uses_default_shape +494 -0
- tango_python-0.4.0/tests/cassettes/TestIDVsIntegration.test_list_idv_awards_uses_default_shape +302 -0
- tango_python-0.4.0/tests/cassettes/TestIDVsIntegration.test_list_idv_child_idvs_uses_default_shape +302 -0
- tango_python-0.4.0/tests/cassettes/TestIDVsIntegration.test_list_idv_summary_awards +256 -0
- tango_python-0.4.0/tests/cassettes/TestIDVsIntegration.test_list_idv_transactions +304 -0
- tango_python-0.4.0/tests/cassettes/TestIDVsIntegration.test_list_idvs_uses_default_shape_and_keyset_params +284 -0
- tango_python-0.4.0/tests/cassettes/TestNaicsIntegration.test_list_naics +82 -0
- tango_python-0.4.0/tests/cassettes/TestOTAsIntegration.test_get_ota +306 -0
- tango_python-0.4.0/tests/cassettes/TestOTAsIntegration.test_list_otas +170 -0
- tango_python-0.4.0/tests/cassettes/TestOTIDVsIntegration.test_get_otidv +310 -0
- tango_python-0.4.0/tests/cassettes/TestOTIDVsIntegration.test_list_otidvs +176 -0
- tango_python-0.4.0/tests/cassettes/TestOfficesIntegration.test_get_office +78 -0
- tango_python-0.4.0/tests/cassettes/TestOfficesIntegration.test_list_offices +96 -0
- tango_python-0.4.0/tests/cassettes/TestOrganizationsIntegration.test_get_organization +152 -0
- tango_python-0.4.0/tests/cassettes/TestOrganizationsIntegration.test_list_organizations +83 -0
- tango_python-0.4.0/tests/cassettes/TestProductionSmokeWithCassettes.test_contract_cursor_pagination +169 -0
- tango_python-0.4.0/tests/cassettes/TestSubawardsIntegration.test_list_subawards +95 -0
- tango_python-0.4.0/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists +1352 -0
- tango_python-0.4.0/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape +1505 -0
- tango_python-0.4.0/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search +812 -0
- tango_python-0.4.0/tests/integration/test_assistance_integration.py +46 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/integration/test_contracts_integration.py +65 -14
- tango_python-0.4.0/tests/integration/test_naics_integration.py +36 -0
- tango_python-0.4.0/tests/integration/test_offices_integration.py +60 -0
- tango_python-0.4.0/tests/integration/test_organizations_integration.py +57 -0
- tango_python-0.4.0/tests/integration/test_otas_otidvs_integration.py +100 -0
- tango_python-0.4.0/tests/integration/test_subawards_integration.py +43 -0
- tango_python-0.4.0/tests/integration/test_vehicles_idvs_integration.py +467 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/integration/validation.py +2 -0
- tango_python-0.4.0/tests/production/__init__.py +5 -0
- tango_python-0.4.0/tests/production/conftest.py +79 -0
- tango_python-0.4.0/tests/production/test_production_smoke.py +719 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/test_client.py +484 -8
- {tango_python-0.2.0 → tango_python-0.4.0}/uv.lock +1 -1
- tango_python-0.2.0/CHANGELOG.md +0 -10
- tango_python-0.2.0/ROADMAP.md +0 -29
- tango_python-0.2.0/scripts/README.md +0 -13
- tango_python-0.2.0/tango/client.py +0 -860
- {tango_python-0.2.0 → tango_python-0.4.0}/.env.example +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/.github/workflows/test.yml +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/LICENSE +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/docs/quick_start.ipynb +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/scripts/fetch_api_schema.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/scripts/generate_schemas_from_api.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tango/exceptions.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tango/shapes/__init__.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tango/shapes/factory.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tango/shapes/generator.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tango/shapes/models.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tango/shapes/parser.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tango/shapes/schema.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tango/shapes/types.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/__init__.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestAgenciesIntegration.test_get_agency +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestAgenciesIntegration.test_list_agencies +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_field_type_validation +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_parsing_consistency +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestBusinessTypesIntegration.test_list_business_types +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestContractsIntegration.test_combined_filters_work_together +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestContractsIntegration.test_contract_data_object_parsing +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestContractsIntegration.test_contract_field_types +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[keyword-software] +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[psc_code-R425] +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_awarding_agency_filter +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_date_range_filter +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_flat +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_naics_code_filter +0 -0
- {tango_python-0.2.0 → tango_python-0.4.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.4.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[default-None] +0 -0
- {tango_python-0.2.0 → tango_python-0.4.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.4.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.4.0}/tests/cassettes/TestContractsIntegration.test_new_expiring_filters +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestContractsIntegration.test_new_fiscal_year_range_filters +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestContractsIntegration.test_new_identifier_filters +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestContractsIntegration.test_search_contracts_with_filters +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestContractsIntegration.test_search_filters_object_with_new_parameters +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[asc-] +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[desc--] +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_contracts +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_entities +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_date_field_parsing_edge_cases +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_decimal_field_parsing_edge_cases +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_empty_list_responses +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_entity_parsing_with_various_address_formats +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_flattened_responses_with_flat_lists +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_list_field_parsing_consistency +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_nested_objects_with_missing_data +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_null_missing_fields_in_contracts +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_with_minimal_shape_sparse_data +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestEntitiesIntegration.test_entity_field_types +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestEntitiesIntegration.test_entity_location_parsing +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestEntitiesIntegration.test_entity_parsing_with_business_types +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestEntitiesIntegration.test_entity_with_various_identifiers +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_by_uei +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_flat +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_search +0 -0
- {tango_python-0.2.0 → tango_python-0.4.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.4.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.4.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.4.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.4.0}/tests/cassettes/TestForecastsIntegration.test_forecast_field_types +0 -0
- {tango_python-0.2.0 → tango_python-0.4.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.4.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[default-None] +0 -0
- {tango_python-0.2.0 → tango_python-0.4.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.4.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.4.0}/tests/cassettes/TestGrantsIntegration.test_grant_field_types +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestGrantsIntegration.test_grant_pagination +0 -0
- {tango_python-0.2.0 → tango_python-0.4.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.4.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[default-None] +0 -0
- {tango_python-0.2.0 → tango_python-0.4.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.4.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.4.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.4.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[default-None] +0 -0
- {tango_python-0.2.0 → tango_python-0.4.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.4.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.4.0}/tests/cassettes/TestNoticesIntegration.test_notice_field_types +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestNoticesIntegration.test_notice_pagination +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestNoticesIntegration.test_notice_with_meta_fields +0 -0
- {tango_python-0.2.0 → tango_python-0.4.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.4.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[default-None] +0 -0
- {tango_python-0.2.0 → tango_python-0.4.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.4.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.4.0}/tests/cassettes/TestOpportunitiesIntegration.test_opportunity_field_types +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[custom-key,piid,description] +0 -0
- {tango_python-0.2.0 → tango_python-0.4.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.4.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.4.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.4.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.4.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.4.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.4.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.4.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.4.0}/tests/conftest.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/integration/README.md +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/integration/__init__.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/integration/conftest.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/integration/test_agencies_integration.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/integration/test_edge_cases_integration.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/integration/test_entities_integration.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/integration/test_forecasts_integration.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/integration/test_grants_integration.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/integration/test_notices_integration.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/integration/test_opportunities_integration.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/integration/test_reference_data_integration.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/test_models.py +0 -0
- {tango_python-0.2.0 → tango_python-0.4.0}/tests/test_shapes.py +0 -0
|
@@ -12,6 +12,12 @@ jobs:
|
|
|
12
12
|
|
|
13
13
|
steps:
|
|
14
14
|
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Checkout tango API repo (manifest source)
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
repository: makegov/tango
|
|
20
|
+
path: tango-api
|
|
15
21
|
|
|
16
22
|
- name: Install uv
|
|
17
23
|
uses: astral-sh/setup-uv@v4
|
|
@@ -29,3 +35,6 @@ jobs:
|
|
|
29
35
|
|
|
30
36
|
- name: Lint with ruff
|
|
31
37
|
run: uv run ruff check tango/
|
|
38
|
+
|
|
39
|
+
- name: Check SDK filter/shape conformance
|
|
40
|
+
run: uv run python scripts/check_filter_shape_conformance.py --manifest tango-api/contracts/filter_shape_contract.json
|
|
@@ -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,32 @@
|
|
|
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
|
+
## [0.4.0] - 2026-02-24
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Offices, Organizations, OTAs, OTIDVs, Subawards, NAICS, and Assistance endpoints.
|
|
14
|
+
- Filter/shape conformance tooling and documentation.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- CI lint workflow runs filter/shape conformance when the manifest is available.
|
|
18
|
+
|
|
19
|
+
## [0.3.0] - 2026-02-09
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- Vehicles endpoints: `list_vehicles`, `get_vehicle`, and `list_vehicle_awardees` (supports shaping + flattening). (refs `makegov/tango#1328`)
|
|
23
|
+
- 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`)
|
|
24
|
+
- Webhooks v2 client support: event type discovery, subscription CRUD, endpoint management, test delivery, and sample payload helpers. (refs `makegov/tango#1274`)
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- Expanded explicit schemas to support common IDV shaping expansions (award offices, officers, period of performance, etc.).
|
|
28
|
+
- HTTP client now supports PATCH/DELETE helpers for webhook management endpoints.
|
|
29
|
+
|
|
30
|
+
## [0.2.0] - 2025-11-16
|
|
31
|
+
|
|
32
|
+
- Entirely refactored SDK
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tango-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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
|
|
@@ -487,8 +487,10 @@ tango-python/
|
|
|
487
487
|
│ └── quick_start.ipynb # Interactive quick start
|
|
488
488
|
├── scripts/ # Utility scripts
|
|
489
489
|
│ ├── README.md
|
|
490
|
+
│ ├── check_filter_shape_conformance.py # Filter + shape conformance (CI)
|
|
490
491
|
│ ├── fetch_api_schema.py
|
|
491
|
-
│
|
|
492
|
+
│ ├── generate_schemas_from_api.py
|
|
493
|
+
│ └── pr_review.py # PR validation (lint, types, tests, conformance)
|
|
492
494
|
├── pyproject.toml # Project configuration
|
|
493
495
|
├── uv.lock # Dependency lock file
|
|
494
496
|
├── LICENSE # MIT License
|
|
@@ -526,7 +528,12 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
|
526
528
|
|
|
527
529
|
1. Fork the repository
|
|
528
530
|
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
529
|
-
3. Run
|
|
530
|
-
4.
|
|
531
|
-
5.
|
|
532
|
-
6.
|
|
531
|
+
3. Run lint and format: `uv run ruff format tango/ && uv run ruff check tango/`
|
|
532
|
+
4. Run type checking: `uv run mypy tango/`
|
|
533
|
+
5. Run tests: `uv run pytest`
|
|
534
|
+
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
|
|
535
|
+
7. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
536
|
+
8. Push to the branch (`git push origin feature/amazing-feature`)
|
|
537
|
+
9. Open a Pull Request
|
|
538
|
+
|
|
539
|
+
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`
|
|
@@ -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
|
|
@@ -432,8 +432,10 @@ tango-python/
|
|
|
432
432
|
│ └── quick_start.ipynb # Interactive quick start
|
|
433
433
|
├── scripts/ # Utility scripts
|
|
434
434
|
│ ├── README.md
|
|
435
|
+
│ ├── check_filter_shape_conformance.py # Filter + shape conformance (CI)
|
|
435
436
|
│ ├── fetch_api_schema.py
|
|
436
|
-
│
|
|
437
|
+
│ ├── generate_schemas_from_api.py
|
|
438
|
+
│ └── pr_review.py # PR validation (lint, types, tests, conformance)
|
|
437
439
|
├── pyproject.toml # Project configuration
|
|
438
440
|
├── uv.lock # Dependency lock file
|
|
439
441
|
├── LICENSE # MIT License
|
|
@@ -471,7 +473,12 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
|
471
473
|
|
|
472
474
|
1. Fork the repository
|
|
473
475
|
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
474
|
-
3. Run
|
|
475
|
-
4.
|
|
476
|
-
5.
|
|
477
|
-
6.
|
|
476
|
+
3. Run lint and format: `uv run ruff format tango/ && uv run ruff check tango/`
|
|
477
|
+
4. Run type checking: `uv run mypy tango/`
|
|
478
|
+
5. Run tests: `uv run pytest`
|
|
479
|
+
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
|
|
480
|
+
7. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
481
|
+
8. Push to the branch (`git push origin feature/amazing-feature`)
|
|
482
|
+
9. Open a Pull Request
|
|
483
|
+
|
|
484
|
+
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`
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# ROADMAP
|
|
2
|
+
|
|
3
|
+
## Now
|
|
4
|
+
|
|
5
|
+
- [X] Align existing API to the SDK
|
|
6
|
+
- [ ] Better Filter DX
|
|
7
|
+
- [ ] Dataclasses for search validation and typing
|
|
8
|
+
- [ ] Docs improvements
|
|
9
|
+
- [ ] Document each endpoint separately to allow for easier information
|
|
10
|
+
- [ ] Some more targeted examples
|
|
11
|
+
|
|
12
|
+
## Next
|
|
13
|
+
|
|
14
|
+
- Better support for MCPs
|
|
15
|
+
|
|
16
|
+
## Later
|
|
17
|
+
|
|
18
|
+
-
|
|
19
|
+
|
|
20
|
+
## Maybe Someday
|
|
21
|
+
|
|
22
|
+
- CLI support
|
|
@@ -7,13 +7,17 @@ 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)
|
|
20
|
+
- [ShapeConfig (predefined shapes)](#shapeconfig-predefined-shapes)
|
|
17
21
|
- [Error Handling](#error-handling)
|
|
18
22
|
|
|
19
23
|
## Client Initialization
|
|
@@ -271,6 +275,119 @@ contracts = client.list_contracts(
|
|
|
271
275
|
|
|
272
276
|
---
|
|
273
277
|
|
|
278
|
+
## Vehicles
|
|
279
|
+
|
|
280
|
+
Vehicles provide a solicitation-centric way to discover groups of related IDVs and (optionally) expand into the underlying awards via shaping.
|
|
281
|
+
|
|
282
|
+
### list_vehicles()
|
|
283
|
+
|
|
284
|
+
List vehicles with optional vehicle-level full-text search.
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
vehicles = client.list_vehicles(
|
|
288
|
+
page=1,
|
|
289
|
+
limit=25,
|
|
290
|
+
search="GSA schedule",
|
|
291
|
+
shape=ShapeConfig.VEHICLES_MINIMAL,
|
|
292
|
+
flat=False,
|
|
293
|
+
flat_lists=False,
|
|
294
|
+
)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**Parameters:**
|
|
298
|
+
- `page` (int): Page number (default: 1)
|
|
299
|
+
- `limit` (int): Results per page (default: 25, max: 100)
|
|
300
|
+
- `search` (str, optional): Vehicle-level search term
|
|
301
|
+
- `shape` (str, optional): Shape string (defaults to `ShapeConfig.VEHICLES_MINIMAL`)
|
|
302
|
+
- `flat` (bool): Flatten nested objects in shaped response
|
|
303
|
+
- `flat_lists` (bool): Flatten arrays using indexed keys
|
|
304
|
+
- `joiner` (str): Joiner used when `flat=True` (default: `"."`)
|
|
305
|
+
|
|
306
|
+
**Returns:** [PaginatedResponse](#paginatedresponse) with vehicle dictionaries
|
|
307
|
+
|
|
308
|
+
### get_vehicle()
|
|
309
|
+
|
|
310
|
+
Get a single vehicle by UUID.
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
vehicle = client.get_vehicle(
|
|
314
|
+
uuid="00000000-0000-0000-0000-000000000001",
|
|
315
|
+
shape=ShapeConfig.VEHICLES_COMPREHENSIVE,
|
|
316
|
+
)
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Notes:**
|
|
320
|
+
- On the vehicle detail endpoint, `search` filters **expanded awardees** when your `shape` includes `awardees(...)` (it does not filter the vehicle itself).
|
|
321
|
+
|
|
322
|
+
### list_vehicle_awardees()
|
|
323
|
+
|
|
324
|
+
List the IDV awardees for a vehicle.
|
|
325
|
+
|
|
326
|
+
```python
|
|
327
|
+
awardees = client.list_vehicle_awardees(
|
|
328
|
+
uuid="00000000-0000-0000-0000-000000000001",
|
|
329
|
+
shape=ShapeConfig.VEHICLE_AWARDEES_MINIMAL,
|
|
330
|
+
)
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## IDVs
|
|
336
|
+
|
|
337
|
+
IDVs (indefinite delivery vehicles) are the parent “vehicle award” records that can have child awards/orders under them.
|
|
338
|
+
|
|
339
|
+
### list_idvs()
|
|
340
|
+
|
|
341
|
+
```python
|
|
342
|
+
idvs = client.list_idvs(
|
|
343
|
+
limit=25,
|
|
344
|
+
cursor=None,
|
|
345
|
+
shape=ShapeConfig.IDVS_MINIMAL,
|
|
346
|
+
awarding_agency="4700",
|
|
347
|
+
)
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Notes:
|
|
351
|
+
|
|
352
|
+
- This endpoint uses **keyset pagination** (`cursor` + `limit`) rather than page numbers.
|
|
353
|
+
|
|
354
|
+
### get_idv()
|
|
355
|
+
|
|
356
|
+
```python
|
|
357
|
+
idv = client.get_idv("SOME_IDV_KEY", shape=ShapeConfig.IDVS_COMPREHENSIVE)
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### list_idv_awards()
|
|
361
|
+
|
|
362
|
+
Lists child awards (contracts) under an IDV.
|
|
363
|
+
|
|
364
|
+
```python
|
|
365
|
+
awards = client.list_idv_awards("SOME_IDV_KEY", limit=25)
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### list_idv_child_idvs()
|
|
369
|
+
|
|
370
|
+
Lists child IDVs under an IDV.
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
children = client.list_idv_child_idvs("SOME_IDV_KEY", limit=25)
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### list_idv_transactions()
|
|
377
|
+
|
|
378
|
+
```python
|
|
379
|
+
tx = client.list_idv_transactions("SOME_IDV_KEY", limit=100)
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### get_idv_summary() / list_idv_summary_awards()
|
|
383
|
+
|
|
384
|
+
```python
|
|
385
|
+
summary = client.get_idv_summary("SOLICITATION_IDENTIFIER")
|
|
386
|
+
awards = client.list_idv_summary_awards("SOLICITATION_IDENTIFIER", limit=25)
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
274
391
|
## Entities
|
|
275
392
|
|
|
276
393
|
Vendors, recipients, and organizations doing business with the government.
|
|
@@ -606,6 +723,135 @@ for biz_type in business_types.results:
|
|
|
606
723
|
|
|
607
724
|
---
|
|
608
725
|
|
|
726
|
+
## Webhooks
|
|
727
|
+
|
|
728
|
+
Webhook APIs let **Large / Enterprise** users manage subscription filters for outbound Tango webhooks.
|
|
729
|
+
|
|
730
|
+
### list_webhook_event_types()
|
|
731
|
+
|
|
732
|
+
Discover supported `event_type` values and subject types.
|
|
733
|
+
|
|
734
|
+
```python
|
|
735
|
+
info = client.list_webhook_event_types()
|
|
736
|
+
print(info.event_types[0].event_type)
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
### list_webhook_subscriptions()
|
|
740
|
+
|
|
741
|
+
```python
|
|
742
|
+
subs = client.list_webhook_subscriptions(page=1, page_size=25)
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
Notes:
|
|
746
|
+
|
|
747
|
+
- This endpoint uses `page` + `page_size` (tier-capped) rather than `limit`.
|
|
748
|
+
|
|
749
|
+
### create_webhook_subscription()
|
|
750
|
+
|
|
751
|
+
```python
|
|
752
|
+
sub = client.create_webhook_subscription(
|
|
753
|
+
"Track specific vendors",
|
|
754
|
+
{
|
|
755
|
+
"records": [
|
|
756
|
+
{"event_type": "awards.new_award", "subject_type": "entity", "subject_ids": ["UEI123ABC"]},
|
|
757
|
+
{"event_type": "awards.new_transaction", "subject_type": "entity", "subject_ids": ["UEI123ABC"]},
|
|
758
|
+
]
|
|
759
|
+
},
|
|
760
|
+
)
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
Notes:
|
|
764
|
+
|
|
765
|
+
- Prefer v2 fields: `subject_type` + `subject_ids`.
|
|
766
|
+
- Legacy compatibility: `resource_ids` is accepted as an alias for `subject_ids` (don’t send both).
|
|
767
|
+
- Catch-all: `subject_ids: []` means “all subjects” for that record and is **Enterprise-only**. Large tier users must list specific IDs.
|
|
768
|
+
|
|
769
|
+
### update_webhook_subscription()
|
|
770
|
+
|
|
771
|
+
```python
|
|
772
|
+
sub = client.update_webhook_subscription("SUBSCRIPTION_UUID", subscription_name="Updated name")
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
### delete_webhook_subscription()
|
|
776
|
+
|
|
777
|
+
```python
|
|
778
|
+
client.delete_webhook_subscription("SUBSCRIPTION_UUID")
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
### list_webhook_endpoints()
|
|
782
|
+
|
|
783
|
+
List your webhook endpoint(s).
|
|
784
|
+
|
|
785
|
+
```python
|
|
786
|
+
endpoints = client.list_webhook_endpoints(page=1, limit=25)
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
### get_webhook_endpoint()
|
|
790
|
+
|
|
791
|
+
```python
|
|
792
|
+
endpoint = client.get_webhook_endpoint("ENDPOINT_UUID")
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
### create_webhook_endpoint() / update_webhook_endpoint() / delete_webhook_endpoint()
|
|
796
|
+
|
|
797
|
+
In production, MakeGov provisions the initial endpoint for you. These are most useful for dev/self-service.
|
|
798
|
+
|
|
799
|
+
```python
|
|
800
|
+
endpoint = client.create_webhook_endpoint("https://example.com/tango/webhooks")
|
|
801
|
+
endpoint = client.update_webhook_endpoint(endpoint.id, is_active=False)
|
|
802
|
+
client.delete_webhook_endpoint(endpoint.id)
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
### test_webhook_delivery()
|
|
806
|
+
|
|
807
|
+
Send an immediate test webhook to your configured endpoint.
|
|
808
|
+
|
|
809
|
+
```python
|
|
810
|
+
result = client.test_webhook_delivery()
|
|
811
|
+
print(result.success, result.status_code)
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
### get_webhook_sample_payload()
|
|
815
|
+
|
|
816
|
+
Fetch Tango-shaped sample deliveries (and sample subscription request bodies).
|
|
817
|
+
|
|
818
|
+
```python
|
|
819
|
+
sample = client.get_webhook_sample_payload(event_type="awards.new_award")
|
|
820
|
+
print(sample["event_type"])
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
### Deliveries / redelivery
|
|
824
|
+
|
|
825
|
+
The API does not currently expose a public `/api/webhooks/deliveries/` or redelivery endpoint. Use:
|
|
826
|
+
|
|
827
|
+
- `test_webhook_delivery()` for connectivity checks
|
|
828
|
+
- `get_webhook_sample_payload()` for building handlers + subscription payloads
|
|
829
|
+
|
|
830
|
+
### Receiving webhooks (signature verification)
|
|
831
|
+
|
|
832
|
+
Every delivery includes an HMAC signature header:
|
|
833
|
+
|
|
834
|
+
- `X-Tango-Signature: sha256=<hex digest>`
|
|
835
|
+
|
|
836
|
+
Compute the digest over the **raw request body bytes** using your shared secret.
|
|
837
|
+
|
|
838
|
+
```python
|
|
839
|
+
import hashlib
|
|
840
|
+
import hmac
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
def verify_tango_webhook_signature(secret: str, raw_body: bytes, signature_header: str | None) -> bool:
|
|
844
|
+
if not signature_header:
|
|
845
|
+
return False
|
|
846
|
+
sig = signature_header.strip()
|
|
847
|
+
if sig.startswith("sha256="):
|
|
848
|
+
sig = sig[len("sha256=") :]
|
|
849
|
+
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
|
|
850
|
+
return hmac.compare_digest(expected, sig)
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
---
|
|
854
|
+
|
|
609
855
|
## Response Objects
|
|
610
856
|
|
|
611
857
|
### PaginatedResponse
|
|
@@ -659,6 +905,48 @@ print(f"Total collected: {len(all_results)} results")
|
|
|
659
905
|
|
|
660
906
|
---
|
|
661
907
|
|
|
908
|
+
## ShapeConfig (predefined shapes)
|
|
909
|
+
|
|
910
|
+
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.
|
|
911
|
+
|
|
912
|
+
```python
|
|
913
|
+
from tango import TangoClient, ShapeConfig
|
|
914
|
+
|
|
915
|
+
client = TangoClient()
|
|
916
|
+
|
|
917
|
+
# List methods default to the minimal shape when shape is omitted
|
|
918
|
+
contracts = client.list_contracts(limit=10) # uses CONTRACTS_MINIMAL
|
|
919
|
+
|
|
920
|
+
# Or pass the constant explicitly
|
|
921
|
+
contracts = client.list_contracts(shape=ShapeConfig.CONTRACTS_MINIMAL, limit=10)
|
|
922
|
+
entity = client.get_entity("UEI_KEY", shape=ShapeConfig.ENTITIES_COMPREHENSIVE)
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
**Available constants (by resource):**
|
|
926
|
+
|
|
927
|
+
| Constant | Used by | Description |
|
|
928
|
+
|----------|---------|-------------|
|
|
929
|
+
| `CONTRACTS_MINIMAL` | `list_contracts`, `search_contracts` | key, piid, award_date, recipient(display_name), description, total_contract_value |
|
|
930
|
+
| `ENTITIES_MINIMAL` | `list_entities` | uei, legal_business_name, cage_code, business_types |
|
|
931
|
+
| `ENTITIES_COMPREHENSIVE` | `get_entity` | Full entity profile (addresses, naics, psc, obligations, etc.) |
|
|
932
|
+
| `FORECASTS_MINIMAL` | `list_forecasts` | id, title, anticipated_award_date, fiscal_year, naics_code, status |
|
|
933
|
+
| `OPPORTUNITIES_MINIMAL` | `list_opportunities` | opportunity_id, title, solicitation_number, response_deadline, active |
|
|
934
|
+
| `NOTICES_MINIMAL` | `list_notices` | notice_id, title, solicitation_number, posted_date |
|
|
935
|
+
| `GRANTS_MINIMAL` | `list_grants` | grant_id, opportunity_number, title, status(*), agency_code |
|
|
936
|
+
| `IDVS_MINIMAL` | `list_idvs`, `list_vehicle_awardees` | key, piid, award_date, recipient(display_name,uei), description, total_contract_value, obligated, idv_type |
|
|
937
|
+
| `IDVS_COMPREHENSIVE` | `get_idv` | Full IDV with offices, place_of_performance, competition, transactions, etc. |
|
|
938
|
+
| `VEHICLES_MINIMAL` | `list_vehicles` | uuid, solicitation_identifier, organization_id, awardee_count, order_count, vehicle_obligations, vehicle_contracts_value, solicitation_title, solicitation_date |
|
|
939
|
+
| `VEHICLES_COMPREHENSIVE` | `get_vehicle` | Full vehicle with competition_details, fiscal_year, set_aside, etc. |
|
|
940
|
+
| `VEHICLE_AWARDEES_MINIMAL` | `list_vehicle_awardees` | uuid, key, piid, award_date, title, order_count, idv_obligations, idv_contracts_value, recipient(display_name,uei) |
|
|
941
|
+
| `ORGANIZATIONS_MINIMAL` | `list_organizations`, `list_organization_offices` | key, fh_key, name, level, type, short_name |
|
|
942
|
+
| `OTAS_MINIMAL` | `list_otas` | key, piid, award_date, recipient(display_name,uei), description, total_contract_value, obligated |
|
|
943
|
+
| `OTIDVS_MINIMAL` | `list_otidvs` | key, piid, award_date, recipient(display_name,uei), description, total_contract_value, obligated, idv_type |
|
|
944
|
+
| `SUBAWARDS_MINIMAL` | `list_subawards` | award_key, prime_recipient(uei,display_name), subaward_recipient(uei,display_name) |
|
|
945
|
+
|
|
946
|
+
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).
|
|
947
|
+
|
|
948
|
+
---
|
|
949
|
+
|
|
662
950
|
## Error Handling
|
|
663
951
|
|
|
664
952
|
The SDK provides specific exception types for different error scenarios.
|
|
@@ -849,7 +1137,8 @@ client = TangoClient()
|
|
|
849
1137
|
|
|
850
1138
|
## Additional Resources
|
|
851
1139
|
|
|
852
|
-
- [Shaping Guide](SHAPES.md) -
|
|
1140
|
+
- [Shaping Guide](SHAPES.md) - Response shaping syntax, examples, and field reference
|
|
1141
|
+
- [Developer Guide](DEVELOPERS.md) - Dynamic models, predefined shapes, and SDK conformance (maintainers)
|
|
853
1142
|
- [Quick Start](quick_start.ipynb) - Interactive notebook with examples
|
|
854
1143
|
- [GitHub Repository](https://github.com/makegov/tango-python) - Source code and examples
|
|
855
1144
|
- [Tango API Documentation](https://tango.makegov.com/docs) - Full API documentation
|
|
@@ -12,6 +12,7 @@ The Tango SDK uses dynamic models that generate runtime types matching the exact
|
|
|
12
12
|
- [Type Hints and IDE Support](#type-hints-and-ide-support)
|
|
13
13
|
- [Performance Considerations](#performance-considerations)
|
|
14
14
|
- [Troubleshooting](#troubleshooting)
|
|
15
|
+
- [SDK conformance (maintainers)](#sdk-conformance-maintainers)
|
|
15
16
|
|
|
16
17
|
## Overview
|
|
17
18
|
|
|
@@ -623,6 +624,32 @@ If you encounter issues:
|
|
|
623
624
|
3. See [examples/](../examples/) for working code samples
|
|
624
625
|
4. Contact support at [tango@makegov.com](mailto:tango@makegov.com)
|
|
625
626
|
|
|
627
|
+
## SDK conformance (maintainers)
|
|
628
|
+
|
|
629
|
+
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).
|
|
630
|
+
|
|
631
|
+
### Filter conformance
|
|
632
|
+
|
|
633
|
+
- **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`.
|
|
634
|
+
- **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`.
|
|
635
|
+
- **Warnings:** Methods that take filters only via `**kwargs` are reported as warnings (filter names cannot be verified against the manifest).
|
|
636
|
+
|
|
637
|
+
### Shape conformance
|
|
638
|
+
|
|
639
|
+
- **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).
|
|
640
|
+
- **Why it matters:** Ensures default shapes never reference invalid or renamed fields, so default list/get behavior stays valid after schema or API changes.
|
|
641
|
+
- **Errors:** Parse failures or invalid field names in any `ShapeConfig` constant are reported as errors and fail the check.
|
|
642
|
+
|
|
643
|
+
### Running the conformance check
|
|
644
|
+
|
|
645
|
+
- **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.
|
|
646
|
+
- **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:
|
|
647
|
+
```bash
|
|
648
|
+
uv run python scripts/check_filter_shape_conformance.py --manifest /path/to/filter_shape_contract.json
|
|
649
|
+
```
|
|
650
|
+
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.
|
|
651
|
+
- **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`.
|
|
652
|
+
|
|
626
653
|
## Next Steps
|
|
627
654
|
|
|
628
655
|
- Read the [API Reference](API_REFERENCE.md) for detailed class and method documentation
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
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.
|
|
4
4
|
|
|
5
|
+
**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.
|
|
6
|
+
|
|
5
7
|
## Why Use Response Shaping?
|
|
6
8
|
|
|
7
9
|
**Performance Benefits:**
|
|
@@ -27,11 +29,36 @@ contracts = client.list_contracts(
|
|
|
27
29
|
shape="key,piid,recipient(display_name),total_contract_value"
|
|
28
30
|
)
|
|
29
31
|
|
|
32
|
+
# Or use a predefined shape constant
|
|
33
|
+
from tango import ShapeConfig
|
|
34
|
+
contracts = client.list_contracts(shape=ShapeConfig.CONTRACTS_MINIMAL, limit=10)
|
|
35
|
+
|
|
30
36
|
# Access the data
|
|
31
37
|
for contract in contracts.results:
|
|
32
38
|
print(f"{contract['piid']}: {contract['recipient']['display_name']}")
|
|
33
39
|
```
|
|
34
40
|
|
|
41
|
+
## Predefined shapes (ShapeConfig)
|
|
42
|
+
|
|
43
|
+
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.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from tango import TangoClient, ShapeConfig
|
|
47
|
+
|
|
48
|
+
client = TangoClient()
|
|
49
|
+
|
|
50
|
+
# These are equivalent (list_contracts defaults to CONTRACTS_MINIMAL)
|
|
51
|
+
contracts = client.list_contracts(limit=10)
|
|
52
|
+
contracts = client.list_contracts(shape=ShapeConfig.CONTRACTS_MINIMAL, limit=10)
|
|
53
|
+
|
|
54
|
+
# Other resources
|
|
55
|
+
entities = client.list_entities(shape=ShapeConfig.ENTITIES_MINIMAL)
|
|
56
|
+
idvs = client.list_idvs(shape=ShapeConfig.IDVS_MINIMAL)
|
|
57
|
+
grants = client.list_grants(shape=ShapeConfig.GRANTS_MINIMAL)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**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.
|
|
61
|
+
|
|
35
62
|
## Basic Shaping
|
|
36
63
|
|
|
37
64
|
### Simple Fields
|
|
@@ -455,5 +482,6 @@ display_name = contract.get('recipient', {}).get('display_name', 'Unknown')
|
|
|
455
482
|
- **Define patterns** - Create reusable shapes for your common queries
|
|
456
483
|
|
|
457
484
|
For more help, see:
|
|
458
|
-
- [
|
|
459
|
-
- [
|
|
485
|
+
- [API Reference](API_REFERENCE.md) - Method parameters, [ShapeConfig table](API_REFERENCE.md#shapeconfig-predefined-shapes), and field context
|
|
486
|
+
- [Developer Guide](DEVELOPERS.md) - Dynamic models, predefined shapes in depth, and SDK conformance (for maintainers)
|
|
487
|
+
- [Quick Start Guide](quick_start.ipynb) - Interactive examples
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tango-python"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.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]
|