tango-python 1.4.0__tar.gz → 1.5.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-1.4.0 → tango_python-1.5.0}/CHANGELOG.md +15 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/PKG-INFO +30 -2
- {tango_python-1.4.0 → tango_python-1.5.0}/README.md +29 -1
- {tango_python-1.4.0 → tango_python-1.5.0}/pyproject.toml +1 -1
- {tango_python-1.4.0 → tango_python-1.5.0}/tango/__init__.py +1 -1
- {tango_python-1.4.0 → tango_python-1.5.0}/tango/client.py +48 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tango/models.py +93 -1
- {tango_python-1.4.0 → tango_python-1.5.0}/tango/shapes/generated_overlay.py +2 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/test_client.py +137 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/.github/workflows/docs-dispatch.yml +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/.github/workflows/lint.yml +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/.github/workflows/publish.yml +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/.github/workflows/test.yml +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/.gitignore +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/LICENSE +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/docs/API_REFERENCE.md +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/docs/CLIENT.md +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/docs/DYNAMIC_MODELS.md +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/docs/ERRORS.md +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/docs/PAGINATION.md +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/docs/SHAPES.md +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/docs/WEBHOOKS.md +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/scripts/README.md +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/scripts/check_filter_shape_conformance.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/scripts/check_shape_coverage.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/scripts/fetch_api_schema.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/scripts/generate_schemas_from_api.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/scripts/generate_shape_overlay.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/scripts/pr_review.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/scripts/probe_shape_types.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/scripts/refresh_contract.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/scripts/smoke_api_parity.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/scripts/test_production.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tango/exceptions.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tango/shapes/__init__.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tango/shapes/explicit_schemas.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tango/shapes/factory.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tango/shapes/generator.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tango/shapes/models.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tango/shapes/parser.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tango/shapes/schema.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tango/shapes/types.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tango/webhooks/__init__.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tango/webhooks/cli.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tango/webhooks/receiver.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tango/webhooks/signing.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tango/webhooks/simulate.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/__init__.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestAgenciesIntegration.test_get_agency +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestAgenciesIntegration.test_list_agencies +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_field_type_validation +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_parsing_consistency +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestBusinessTypesIntegration.test_list_business_types +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_combined_filters_work_together +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_contract_cursor_pagination +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_contract_data_object_parsing +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_contract_field_types +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[keyword-software] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[psc_code-R425] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_awarding_agency_filter +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_date_range_filter +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_flat +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_naics_code_filter +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[custom-key,piid,recipient(display_name),total_contract_value,award_date] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[default-None] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.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-1.4.0 → tango_python-1.5.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-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_new_expiring_filters +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_new_fiscal_year_range_filters +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_new_identifier_filters +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_search_contracts_with_filters +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_search_filters_object_with_new_parameters +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[asc-] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[desc--] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_contracts +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_entities +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_date_field_parsing_edge_cases +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_decimal_field_parsing_edge_cases +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_empty_list_responses +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_entity_parsing_with_various_address_formats +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_flattened_responses_with_flat_lists +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_list_field_parsing_consistency +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_nested_objects_with_missing_data +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_null_missing_fields_in_contracts +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_with_minimal_shape_sparse_data +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEntitiesIntegration.test_entity_field_types +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEntitiesIntegration.test_entity_location_parsing +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEntitiesIntegration.test_entity_parsing_with_business_types +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEntitiesIntegration.test_entity_with_various_identifiers +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_budget_flows +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_by_uei +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_flat +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_search +0 -0
- {tango_python-1.4.0 → tango_python-1.5.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...1603a7d52e211cf2b3bc7d32080238aa +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[custom-uei,legal_business_name,cage_code] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.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-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestForecastsIntegration.test_forecast_field_types +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[custom-id,title,anticipated_award_date] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[default-None] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.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-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[minimal-id,title,anticipated_award_date,fiscal_year,naics_code,status] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestGrantsIntegration.test_grant_field_types +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestGrantsIntegration.test_grant_pagination +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[custom-grant_id,title,opportunity_number] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[default-None] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.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-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[minimal-grant_id,opportunity_number,title,status(-),agency_code] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestIDVsIntegration.test_get_idv_uses_default_shape +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_awards_uses_default_shape +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_child_idvs_uses_default_shape +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_transactions +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestIDVsIntegration.test_list_idvs_uses_default_shape_and_keyset_params +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_code +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_name_text +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating_max +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_performance_risk +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_type_of_investment +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_updated_time_range +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_funding_and_cio_evaluation_expansions +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_get_itdashboard_investment_by_uii +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_itdashboard_pagination +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_search +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[custom-uii,agency_name,investment_title,updated_time] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[default-None] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[minimal-uii,agency_name,bureau_name,investment_title,type_of_investment,part_of_it_portfolio,updated_time,url] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestNaicsIntegration.test_list_naics +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[custom-notice_id,title,solicitation_number] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[default-None] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.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-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestNoticesIntegration.test_notice_field_types +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestNoticesIntegration.test_notice_pagination +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestNoticesIntegration.test_notice_with_meta_fields +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOTAsIntegration.test_get_ota +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOTAsIntegration.test_list_otas +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOTIDVsIntegration.test_get_otidv +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOTIDVsIntegration.test_list_otidvs +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOfficesIntegration.test_get_office +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOfficesIntegration.test_list_offices +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[custom-opportunity_id,title,solicitation_number] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[default-None] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.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-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOpportunitiesIntegration.test_opportunity_field_types +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOrganizationsIntegration.test_get_organization +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOrganizationsIntegration.test_list_organizations +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestProtestsIntegration.test_get_protest_by_case_id +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_filter +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[custom-case_id,title,source_system,outcome] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[default-None] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[minimal-case_id,case_number,title,source_system,outcome,filed_date] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[with_dockets-case_id,case_number,title,outcome,filed_date,dockets(docket_number,filed_date,outcome)] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestProtestsIntegration.test_protest_pagination +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestSubawardsIntegration.test_list_subawards +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[custom-key,piid,description] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[minimal-key,piid,award_date,recipient(display_name),description,total_contract_value] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[ultra_minimal-key,piid,recipient(display_name),total_contract_value] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[with_address-uei,legal_business_name,cage_code,business_types,physical_address] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.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-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestTypeHintsIntegration.test_notices_dict_access[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.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-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestTypeHintsIntegration.test_opportunities_dict_access[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_with_metrics_expansion +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_orders_uses_default_shape +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_with_ordering +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/conftest.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/README.md +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/__init__.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/conftest.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_agencies_integration.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_contracts_integration.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_edge_cases_integration.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_entities_integration.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_forecasts_integration.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_grants_integration.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_itdashboard_integration.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_naics_integration.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_notices_integration.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_offices_integration.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_opportunities_integration.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_organizations_integration.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_otas_otidvs_integration.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_protests_integration.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_reference_data_integration.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_subawards_integration.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_vehicles_idvs_integration.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/validation.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/production/__init__.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/production/conftest.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/production/test_production_smoke.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/test_api_parity.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/test_dibbs_exclusions_sbir.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/test_models.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/test_shape_coverage_gate.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/test_shapes.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/test_webhooks_cli.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/test_webhooks_receiver.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/test_webhooks_signing.py +0 -0
- {tango_python-1.4.0 → tango_python-1.5.0}/tests/test_webhooks_simulate.py +0 -0
|
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.5.0] - 2026-08-04
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **`relationships(type, source)` on entities.** Tango API 4.20.0 added two keys to each entry in the entity `relationships` expand: `type`, the stable relationship-type code (`prime_sub`, `parent_subsidiary`, `ultimate_parent`, `predecessor`), and `source`, where the tie came from (`sam`, `subawards`). Both are shape-selectable, so `shape="uei,relationships(type,source,uei)"` now resolves against the SDK's schema. Re-vendored the contract and regenerated the shape overlay; the coverage gate reports 0 gaps.
|
|
14
|
+
- `PaginatedResponse.meta` now carries the response-level `meta` block the API returns, with three accessors over it: `unresolved_agency_tokens` (agency tokens that matched nothing, per filter), `resolved_agencies` (the organization each token *did* match), and `agency_warnings` (the API's human-readable notes). Agency values resolve fuzzily, so a token can match an organization the caller did not intend and quietly scope the query to that subtree — and a token that resolves to nothing was previously dropped with no signal at all. Both cases were indistinguishable from "no such records exist". The API now reports both; until now the SDK read the envelope key-by-key and dropped `meta` on the floor, so SDK users were the one group that could not see it. `resolved_agencies` is the accessor that matters for the wrong-organization case: nothing is dropped there, so an unresolved-token check cannot detect it. All accessors return empty rather than raising when `meta` is absent (most responses) or malformed.
|
|
15
|
+
- A fully-unresolvable agency filter now raises `TangoValidationError` naming the offending value instead of returning an empty page. This needed no SDK change — the existing 400 handler already reads the API's `error` key — but it is new behavior for callers of `list_subawards()`, `list_opportunities()`, `list_notices()`, and `list_vehicles()`, which previously returned an empty result set. Contracts, IDVs, OTAs, and OTIDVs already behaved this way.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- **The `relation` value vocabulary changed upstream — match on `type` instead.** In Tango API 4.20.0 the `relation` label stopped collapsing to `affiliate` for subcontracting and corporate-succession ties and now names the partner's role: `subcontractor` / `prime`, `predecessor` / `successor`, and `descendant` (rather than `child`) on the far side of an ultimate-parent tie. `affiliate` survives only as a fallback for a type the API doesn't recognize. This affects the large majority of relationship entries.
|
|
19
|
+
|
|
20
|
+
No SDK code change is required — `relation` was and remains a `str`. But if you have application code branching on the string `affiliate`, switch it to `type`, which is stable and won't churn again. See the [entities data dictionary](https://docs.makegov.com/data-dictionary/entities/#relationships) for the full vocabulary table.
|
|
21
|
+
|
|
22
|
+
### Notes
|
|
23
|
+
- `PaginatedResponse.page_metadata` is documented as always `None`: the API has never emitted a `page_metadata` key, so the field has only ever read a value that does not exist. It is retained so existing attribute access keeps working. Use `meta`.
|
|
24
|
+
|
|
10
25
|
## [1.4.0] - 2026-07-20
|
|
11
26
|
|
|
12
27
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tango-python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.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
|
|
@@ -191,9 +191,37 @@ contracts = client.list_contracts(
|
|
|
191
191
|
- `expiring_gte`, `expiring_lte` - Contract expiration date range
|
|
192
192
|
|
|
193
193
|
**Party Filters:**
|
|
194
|
-
- `awarding_agency`, `funding_agency` - Agency codes
|
|
194
|
+
- `awarding_agency`, `funding_agency` - Agency codes, names, abbreviations, or organization UUIDs. Multi-value OR via `|`.
|
|
195
195
|
- `recipient_name`, `recipient_uei` - Vendor/recipient filters
|
|
196
196
|
|
|
197
|
+
### Checking how agency filters resolved
|
|
198
|
+
|
|
199
|
+
Agency values are resolved fuzzily, so a token can match an organization you did not
|
|
200
|
+
intend — which silently scopes the query to that organization's subtree. A short result
|
|
201
|
+
set is then indistinguishable from "no such records exist". Responses expose what
|
|
202
|
+
actually happened:
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
response = client.list_contracts(awarding_agency="HUD|HUDD")
|
|
206
|
+
|
|
207
|
+
# Tokens that matched nothing and were ignored.
|
|
208
|
+
if response.unresolved_agency_tokens:
|
|
209
|
+
raise SystemExit(f"dropped: {response.unresolved_agency_tokens}")
|
|
210
|
+
# {'awarding_agency': ['HUDD']}
|
|
211
|
+
|
|
212
|
+
# What the tokens that DID match resolved to — the only way to catch a
|
|
213
|
+
# plausible-but-wrong match, where nothing was dropped at all.
|
|
214
|
+
for org in response.resolved_agencies.get("awarding_agency", []):
|
|
215
|
+
print(org["name"], org["cgac"])
|
|
216
|
+
# Department of Housing and Urban Development 086
|
|
217
|
+
|
|
218
|
+
for warning in response.agency_warnings:
|
|
219
|
+
print(warning)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
If *every* token for a filter fails to resolve, the API returns `400` and the SDK raises
|
|
223
|
+
`TangoValidationError` naming the offending value, rather than an empty page.
|
|
224
|
+
|
|
197
225
|
**Classification:**
|
|
198
226
|
- `naics_code`, `psc_code` - Industry/product codes
|
|
199
227
|
- `set_aside_type` - Set-aside type
|
|
@@ -137,9 +137,37 @@ contracts = client.list_contracts(
|
|
|
137
137
|
- `expiring_gte`, `expiring_lte` - Contract expiration date range
|
|
138
138
|
|
|
139
139
|
**Party Filters:**
|
|
140
|
-
- `awarding_agency`, `funding_agency` - Agency codes
|
|
140
|
+
- `awarding_agency`, `funding_agency` - Agency codes, names, abbreviations, or organization UUIDs. Multi-value OR via `|`.
|
|
141
141
|
- `recipient_name`, `recipient_uei` - Vendor/recipient filters
|
|
142
142
|
|
|
143
|
+
### Checking how agency filters resolved
|
|
144
|
+
|
|
145
|
+
Agency values are resolved fuzzily, so a token can match an organization you did not
|
|
146
|
+
intend — which silently scopes the query to that organization's subtree. A short result
|
|
147
|
+
set is then indistinguishable from "no such records exist". Responses expose what
|
|
148
|
+
actually happened:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
response = client.list_contracts(awarding_agency="HUD|HUDD")
|
|
152
|
+
|
|
153
|
+
# Tokens that matched nothing and were ignored.
|
|
154
|
+
if response.unresolved_agency_tokens:
|
|
155
|
+
raise SystemExit(f"dropped: {response.unresolved_agency_tokens}")
|
|
156
|
+
# {'awarding_agency': ['HUDD']}
|
|
157
|
+
|
|
158
|
+
# What the tokens that DID match resolved to — the only way to catch a
|
|
159
|
+
# plausible-but-wrong match, where nothing was dropped at all.
|
|
160
|
+
for org in response.resolved_agencies.get("awarding_agency", []):
|
|
161
|
+
print(org["name"], org["cgac"])
|
|
162
|
+
# Department of Housing and Urban Development 086
|
|
163
|
+
|
|
164
|
+
for warning in response.agency_warnings:
|
|
165
|
+
print(warning)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
If *every* token for a filter fails to resolve, the API returns `400` and the SDK raises
|
|
169
|
+
`TangoValidationError` naming the offending value, rather than an empty page.
|
|
170
|
+
|
|
143
171
|
**Classification:**
|
|
144
172
|
- `naics_code`, `psc_code` - Industry/product codes
|
|
145
173
|
- `set_aside_type` - Set-aside type
|
|
@@ -491,6 +491,7 @@ class TangoClient:
|
|
|
491
491
|
for ag in (self._parse_agency(agency) for agency in data["results"])
|
|
492
492
|
if ag is not None
|
|
493
493
|
],
|
|
494
|
+
meta=data.get("meta"),
|
|
494
495
|
)
|
|
495
496
|
|
|
496
497
|
def get_agency(self, code: str) -> Agency:
|
|
@@ -521,6 +522,7 @@ class TangoClient:
|
|
|
521
522
|
next=data.get("next"),
|
|
522
523
|
previous=data.get("previous"),
|
|
523
524
|
results=data.get("results", []),
|
|
525
|
+
meta=data.get("meta"),
|
|
524
526
|
)
|
|
525
527
|
|
|
526
528
|
def get_office(self, code: str) -> dict[str, Any]:
|
|
@@ -573,6 +575,7 @@ class TangoClient:
|
|
|
573
575
|
next=data.get("next"),
|
|
574
576
|
previous=data.get("previous"),
|
|
575
577
|
results=results,
|
|
578
|
+
meta=data.get("meta"),
|
|
576
579
|
)
|
|
577
580
|
|
|
578
581
|
def get_organization(
|
|
@@ -785,6 +788,7 @@ class TangoClient:
|
|
|
785
788
|
previous=data.get("previous"),
|
|
786
789
|
results=results,
|
|
787
790
|
cursor=data.get("cursor"),
|
|
791
|
+
meta=data.get("meta"),
|
|
788
792
|
)
|
|
789
793
|
|
|
790
794
|
def get_contract(
|
|
@@ -846,6 +850,7 @@ class TangoClient:
|
|
|
846
850
|
previous=data.get("previous"),
|
|
847
851
|
results=results,
|
|
848
852
|
cursor=data.get("cursor"),
|
|
853
|
+
meta=data.get("meta"),
|
|
849
854
|
)
|
|
850
855
|
|
|
851
856
|
def get_contract_transactions(
|
|
@@ -868,6 +873,7 @@ class TangoClient:
|
|
|
868
873
|
previous=data.get("previous"),
|
|
869
874
|
results=data.get("results") or [],
|
|
870
875
|
cursor=data.get("cursor"),
|
|
876
|
+
meta=data.get("meta"),
|
|
871
877
|
)
|
|
872
878
|
|
|
873
879
|
# ============================================================================
|
|
@@ -971,6 +977,7 @@ class TangoClient:
|
|
|
971
977
|
previous=data.get("previous"),
|
|
972
978
|
results=results,
|
|
973
979
|
page_metadata=data.get("page_metadata"),
|
|
980
|
+
meta=data.get("meta"),
|
|
974
981
|
)
|
|
975
982
|
|
|
976
983
|
def get_idv(
|
|
@@ -1072,6 +1079,7 @@ class TangoClient:
|
|
|
1072
1079
|
previous=data.get("previous"),
|
|
1073
1080
|
results=results,
|
|
1074
1081
|
page_metadata=data.get("page_metadata"),
|
|
1082
|
+
meta=data.get("meta"),
|
|
1075
1083
|
)
|
|
1076
1084
|
|
|
1077
1085
|
def list_idv_child_idvs(
|
|
@@ -1116,6 +1124,7 @@ class TangoClient:
|
|
|
1116
1124
|
previous=data.get("previous"),
|
|
1117
1125
|
results=results,
|
|
1118
1126
|
page_metadata=data.get("page_metadata"),
|
|
1127
|
+
meta=data.get("meta"),
|
|
1119
1128
|
)
|
|
1120
1129
|
|
|
1121
1130
|
def list_idv_transactions(
|
|
@@ -1132,6 +1141,7 @@ class TangoClient:
|
|
|
1132
1141
|
previous=data.get("previous"),
|
|
1133
1142
|
results=data.get("results") or [],
|
|
1134
1143
|
page_metadata=data.get("page_metadata"),
|
|
1144
|
+
meta=data.get("meta"),
|
|
1135
1145
|
)
|
|
1136
1146
|
|
|
1137
1147
|
def list_otas(
|
|
@@ -1214,6 +1224,7 @@ class TangoClient:
|
|
|
1214
1224
|
results=results,
|
|
1215
1225
|
cursor=data.get("cursor"),
|
|
1216
1226
|
page_metadata=data.get("page_metadata"),
|
|
1227
|
+
meta=data.get("meta"),
|
|
1217
1228
|
)
|
|
1218
1229
|
|
|
1219
1230
|
def get_ota(
|
|
@@ -1319,6 +1330,7 @@ class TangoClient:
|
|
|
1319
1330
|
results=results,
|
|
1320
1331
|
cursor=data.get("cursor"),
|
|
1321
1332
|
page_metadata=data.get("page_metadata"),
|
|
1333
|
+
meta=data.get("meta"),
|
|
1322
1334
|
)
|
|
1323
1335
|
|
|
1324
1336
|
def get_otidv(
|
|
@@ -1395,6 +1407,7 @@ class TangoClient:
|
|
|
1395
1407
|
results=results,
|
|
1396
1408
|
cursor=data.get("cursor"),
|
|
1397
1409
|
page_metadata=data.get("page_metadata"),
|
|
1410
|
+
meta=data.get("meta"),
|
|
1398
1411
|
)
|
|
1399
1412
|
|
|
1400
1413
|
def list_subawards(
|
|
@@ -1449,6 +1462,7 @@ class TangoClient:
|
|
|
1449
1462
|
next=data.get("next"),
|
|
1450
1463
|
previous=data.get("previous"),
|
|
1451
1464
|
results=results,
|
|
1465
|
+
meta=data.get("meta"),
|
|
1452
1466
|
)
|
|
1453
1467
|
|
|
1454
1468
|
def get_subaward(
|
|
@@ -1533,6 +1547,7 @@ class TangoClient:
|
|
|
1533
1547
|
next=data.get("next"),
|
|
1534
1548
|
previous=data.get("previous"),
|
|
1535
1549
|
results=results,
|
|
1550
|
+
meta=data.get("meta"),
|
|
1536
1551
|
)
|
|
1537
1552
|
|
|
1538
1553
|
def get_gsa_elibrary_contract(
|
|
@@ -1639,6 +1654,7 @@ class TangoClient:
|
|
|
1639
1654
|
next=data.get("next"),
|
|
1640
1655
|
previous=data.get("previous"),
|
|
1641
1656
|
results=results,
|
|
1657
|
+
meta=data.get("meta"),
|
|
1642
1658
|
)
|
|
1643
1659
|
|
|
1644
1660
|
def get_itdashboard_investment(
|
|
@@ -1808,6 +1824,7 @@ class TangoClient:
|
|
|
1808
1824
|
next=data.get("next"),
|
|
1809
1825
|
previous=data.get("previous"),
|
|
1810
1826
|
results=results,
|
|
1827
|
+
meta=data.get("meta"),
|
|
1811
1828
|
)
|
|
1812
1829
|
|
|
1813
1830
|
def get_vehicle(
|
|
@@ -1889,6 +1906,7 @@ class TangoClient:
|
|
|
1889
1906
|
next=data.get("next"),
|
|
1890
1907
|
previous=data.get("previous"),
|
|
1891
1908
|
results=results,
|
|
1909
|
+
meta=data.get("meta"),
|
|
1892
1910
|
)
|
|
1893
1911
|
|
|
1894
1912
|
def list_vehicle_orders(
|
|
@@ -1937,6 +1955,7 @@ class TangoClient:
|
|
|
1937
1955
|
next=data.get("next"),
|
|
1938
1956
|
previous=data.get("previous"),
|
|
1939
1957
|
results=results,
|
|
1958
|
+
meta=data.get("meta"),
|
|
1940
1959
|
)
|
|
1941
1960
|
|
|
1942
1961
|
# Business Types endpoints
|
|
@@ -1971,6 +1990,7 @@ class TangoClient:
|
|
|
1971
1990
|
next=data.get("next"),
|
|
1972
1991
|
previous=data.get("previous"),
|
|
1973
1992
|
results=results,
|
|
1993
|
+
meta=data.get("meta"),
|
|
1974
1994
|
)
|
|
1975
1995
|
|
|
1976
1996
|
def list_naics(
|
|
@@ -2022,6 +2042,7 @@ class TangoClient:
|
|
|
2022
2042
|
next=data.get("next"),
|
|
2023
2043
|
previous=data.get("previous"),
|
|
2024
2044
|
results=data.get("results", []),
|
|
2045
|
+
meta=data.get("meta"),
|
|
2025
2046
|
)
|
|
2026
2047
|
|
|
2027
2048
|
# Entity endpoints
|
|
@@ -2108,6 +2129,7 @@ class TangoClient:
|
|
|
2108
2129
|
next=data.get("next"),
|
|
2109
2130
|
previous=data.get("previous"),
|
|
2110
2131
|
results=results,
|
|
2132
|
+
meta=data.get("meta"),
|
|
2111
2133
|
)
|
|
2112
2134
|
|
|
2113
2135
|
def get_entity(
|
|
@@ -2164,6 +2186,7 @@ class TangoClient:
|
|
|
2164
2186
|
next=data.get("next"),
|
|
2165
2187
|
previous=data.get("previous"),
|
|
2166
2188
|
results=list(data.get("results") or []),
|
|
2189
|
+
meta=data.get("meta"),
|
|
2167
2190
|
)
|
|
2168
2191
|
|
|
2169
2192
|
# Forecast endpoints
|
|
@@ -2256,6 +2279,7 @@ class TangoClient:
|
|
|
2256
2279
|
next=data.get("next"),
|
|
2257
2280
|
previous=data.get("previous"),
|
|
2258
2281
|
results=results,
|
|
2282
|
+
meta=data.get("meta"),
|
|
2259
2283
|
)
|
|
2260
2284
|
|
|
2261
2285
|
def get_forecast(
|
|
@@ -2379,6 +2403,7 @@ class TangoClient:
|
|
|
2379
2403
|
next=data.get("next"),
|
|
2380
2404
|
previous=data.get("previous"),
|
|
2381
2405
|
results=results,
|
|
2406
|
+
meta=data.get("meta"),
|
|
2382
2407
|
)
|
|
2383
2408
|
|
|
2384
2409
|
def get_opportunity(
|
|
@@ -2493,6 +2518,7 @@ class TangoClient:
|
|
|
2493
2518
|
next=data.get("next"),
|
|
2494
2519
|
previous=data.get("previous"),
|
|
2495
2520
|
results=results,
|
|
2521
|
+
meta=data.get("meta"),
|
|
2496
2522
|
)
|
|
2497
2523
|
|
|
2498
2524
|
def get_notice(
|
|
@@ -2614,6 +2640,7 @@ class TangoClient:
|
|
|
2614
2640
|
next=data.get("next"),
|
|
2615
2641
|
previous=data.get("previous"),
|
|
2616
2642
|
results=results,
|
|
2643
|
+
meta=data.get("meta"),
|
|
2617
2644
|
)
|
|
2618
2645
|
|
|
2619
2646
|
def get_protest(
|
|
@@ -2750,6 +2777,7 @@ class TangoClient:
|
|
|
2750
2777
|
next=data.get("next"),
|
|
2751
2778
|
previous=data.get("previous"),
|
|
2752
2779
|
results=results,
|
|
2780
|
+
meta=data.get("meta"),
|
|
2753
2781
|
)
|
|
2754
2782
|
|
|
2755
2783
|
def get_dibbs_rfq(
|
|
@@ -2869,6 +2897,7 @@ class TangoClient:
|
|
|
2869
2897
|
next=data.get("next"),
|
|
2870
2898
|
previous=data.get("previous"),
|
|
2871
2899
|
results=results,
|
|
2900
|
+
meta=data.get("meta"),
|
|
2872
2901
|
)
|
|
2873
2902
|
|
|
2874
2903
|
def get_dibbs_rfp(
|
|
@@ -3006,6 +3035,7 @@ class TangoClient:
|
|
|
3006
3035
|
next=data.get("next"),
|
|
3007
3036
|
previous=data.get("previous"),
|
|
3008
3037
|
results=results,
|
|
3038
|
+
meta=data.get("meta"),
|
|
3009
3039
|
)
|
|
3010
3040
|
|
|
3011
3041
|
def get_dibbs_award(
|
|
@@ -3151,6 +3181,7 @@ class TangoClient:
|
|
|
3151
3181
|
next=data.get("next"),
|
|
3152
3182
|
previous=data.get("previous"),
|
|
3153
3183
|
results=results,
|
|
3184
|
+
meta=data.get("meta"),
|
|
3154
3185
|
)
|
|
3155
3186
|
|
|
3156
3187
|
def get_exclusion(
|
|
@@ -3278,6 +3309,7 @@ class TangoClient:
|
|
|
3278
3309
|
next=data.get("next"),
|
|
3279
3310
|
previous=data.get("previous"),
|
|
3280
3311
|
results=results,
|
|
3312
|
+
meta=data.get("meta"),
|
|
3281
3313
|
)
|
|
3282
3314
|
|
|
3283
3315
|
def get_sbir_topic(
|
|
@@ -3398,6 +3430,7 @@ class TangoClient:
|
|
|
3398
3430
|
next=data.get("next"),
|
|
3399
3431
|
previous=data.get("previous"),
|
|
3400
3432
|
results=results,
|
|
3433
|
+
meta=data.get("meta"),
|
|
3401
3434
|
)
|
|
3402
3435
|
|
|
3403
3436
|
def get_sbir_solicitation(
|
|
@@ -3772,6 +3805,7 @@ class TangoClient:
|
|
|
3772
3805
|
next=data.get("next"),
|
|
3773
3806
|
previous=data.get("previous"),
|
|
3774
3807
|
results=results,
|
|
3808
|
+
meta=data.get("meta"),
|
|
3775
3809
|
)
|
|
3776
3810
|
|
|
3777
3811
|
def get_budget_account(
|
|
@@ -3823,6 +3857,7 @@ class TangoClient:
|
|
|
3823
3857
|
next=data.get("next"),
|
|
3824
3858
|
previous=data.get("previous"),
|
|
3825
3859
|
results=data.get("results") or [],
|
|
3860
|
+
meta=data.get("meta"),
|
|
3826
3861
|
)
|
|
3827
3862
|
|
|
3828
3863
|
def get_budget_account_recipients(
|
|
@@ -3850,6 +3885,7 @@ class TangoClient:
|
|
|
3850
3885
|
next=data.get("next"),
|
|
3851
3886
|
previous=data.get("previous"),
|
|
3852
3887
|
results=data.get("results") or [],
|
|
3888
|
+
meta=data.get("meta"),
|
|
3853
3889
|
)
|
|
3854
3890
|
|
|
3855
3891
|
# Grant endpoints
|
|
@@ -3943,6 +3979,7 @@ class TangoClient:
|
|
|
3943
3979
|
next=data.get("next"),
|
|
3944
3980
|
previous=data.get("previous"),
|
|
3945
3981
|
results=results,
|
|
3982
|
+
meta=data.get("meta"),
|
|
3946
3983
|
)
|
|
3947
3984
|
|
|
3948
3985
|
def get_grant(
|
|
@@ -4035,6 +4072,7 @@ class TangoClient:
|
|
|
4035
4072
|
next=data.get("next"),
|
|
4036
4073
|
previous=data.get("previous"),
|
|
4037
4074
|
results=results,
|
|
4075
|
+
meta=data.get("meta"),
|
|
4038
4076
|
)
|
|
4039
4077
|
|
|
4040
4078
|
def create_webhook_endpoint(
|
|
@@ -4219,6 +4257,7 @@ class TangoClient:
|
|
|
4219
4257
|
next=data.get("next"),
|
|
4220
4258
|
previous=data.get("previous"),
|
|
4221
4259
|
results=results,
|
|
4260
|
+
meta=data.get("meta"),
|
|
4222
4261
|
)
|
|
4223
4262
|
|
|
4224
4263
|
def get_webhook_alert(self, alert_id: str) -> WebhookAlert:
|
|
@@ -4433,6 +4472,7 @@ class TangoClient:
|
|
|
4433
4472
|
next=data.get("next"),
|
|
4434
4473
|
previous=data.get("previous"),
|
|
4435
4474
|
results=list(data.get("results") or []),
|
|
4475
|
+
meta=data.get("meta"),
|
|
4436
4476
|
)
|
|
4437
4477
|
|
|
4438
4478
|
def get_department(self, code: str) -> dict[str, Any]:
|
|
@@ -4470,6 +4510,7 @@ class TangoClient:
|
|
|
4470
4510
|
next=data.get("next"),
|
|
4471
4511
|
previous=data.get("previous"),
|
|
4472
4512
|
results=list(data.get("results") or []),
|
|
4513
|
+
meta=data.get("meta"),
|
|
4473
4514
|
)
|
|
4474
4515
|
|
|
4475
4516
|
def get_psc(
|
|
@@ -4570,6 +4611,7 @@ class TangoClient:
|
|
|
4570
4611
|
next=data.get("next"),
|
|
4571
4612
|
previous=data.get("previous"),
|
|
4572
4613
|
results=list(data.get("results") or []),
|
|
4614
|
+
meta=data.get("meta"),
|
|
4573
4615
|
)
|
|
4574
4616
|
|
|
4575
4617
|
def get_assistance_listing(
|
|
@@ -4616,6 +4658,7 @@ class TangoClient:
|
|
|
4616
4658
|
next=data.get("next"),
|
|
4617
4659
|
previous=data.get("previous"),
|
|
4618
4660
|
results=list(data.get("results") or []),
|
|
4661
|
+
meta=data.get("meta"),
|
|
4619
4662
|
)
|
|
4620
4663
|
|
|
4621
4664
|
def get_mas_sin(
|
|
@@ -4685,6 +4728,7 @@ class TangoClient:
|
|
|
4685
4728
|
results=results,
|
|
4686
4729
|
cursor=data.get("cursor"),
|
|
4687
4730
|
page_metadata=data.get("page_metadata"),
|
|
4731
|
+
meta=data.get("meta"),
|
|
4688
4732
|
)
|
|
4689
4733
|
|
|
4690
4734
|
def list_entity_contracts(
|
|
@@ -4860,6 +4904,7 @@ class TangoClient:
|
|
|
4860
4904
|
next=data.get("next"),
|
|
4861
4905
|
previous=data.get("previous"),
|
|
4862
4906
|
results=results,
|
|
4907
|
+
meta=data.get("meta"),
|
|
4863
4908
|
)
|
|
4864
4909
|
|
|
4865
4910
|
def list_entity_lcats(
|
|
@@ -4890,6 +4935,7 @@ class TangoClient:
|
|
|
4890
4935
|
next=data.get("next"),
|
|
4891
4936
|
previous=data.get("previous"),
|
|
4892
4937
|
results=list(data.get("results") or []),
|
|
4938
|
+
meta=data.get("meta"),
|
|
4893
4939
|
)
|
|
4894
4940
|
|
|
4895
4941
|
def get_entity_metrics(self, uei: str, months: int, period_grouping: str) -> dict[str, Any]:
|
|
@@ -4930,6 +4976,7 @@ class TangoClient:
|
|
|
4930
4976
|
next=data.get("next"),
|
|
4931
4977
|
previous=data.get("previous"),
|
|
4932
4978
|
results=list(data.get("results") or []),
|
|
4979
|
+
meta=data.get("meta"),
|
|
4933
4980
|
)
|
|
4934
4981
|
|
|
4935
4982
|
# ============================================================================
|
|
@@ -4987,6 +5034,7 @@ class TangoClient:
|
|
|
4987
5034
|
results=results,
|
|
4988
5035
|
cursor=data.get("cursor"),
|
|
4989
5036
|
page_metadata=data.get("page_metadata"),
|
|
5037
|
+
meta=data.get("meta"),
|
|
4990
5038
|
)
|
|
4991
5039
|
|
|
4992
5040
|
def list_agency_awarding_contracts(
|
|
@@ -1044,7 +1044,15 @@ class PaginatedResponse[T]:
|
|
|
1044
1044
|
previous: URL for the previous page of results (None if first page)
|
|
1045
1045
|
results: List of result items (type depends on shape parameter)
|
|
1046
1046
|
cursor: Cursor token for cursor-based pagination (None if not available)
|
|
1047
|
-
|
|
1047
|
+
meta: Response-level metadata the API attached to this page, when present.
|
|
1048
|
+
Currently carries agency-filter diagnostics: ``resolved_filters`` maps
|
|
1049
|
+
each agency filter to the organizations its ``|``-separated tokens
|
|
1050
|
+
resolved to (or ``None``), and ``warnings`` lists human-readable notes
|
|
1051
|
+
about tokens that were dropped or matched loosely. See
|
|
1052
|
+
:meth:`agency_warnings` and :meth:`unresolved_agency_tokens`.
|
|
1053
|
+
page_metadata: Always ``None`` — the API has never emitted a
|
|
1054
|
+
``page_metadata`` key. Retained so existing attribute access keeps
|
|
1055
|
+
working; use ``meta`` instead.
|
|
1048
1056
|
|
|
1049
1057
|
Examples:
|
|
1050
1058
|
>>> from tango import TangoClient, ShapeConfig
|
|
@@ -1063,8 +1071,92 @@ class PaginatedResponse[T]:
|
|
|
1063
1071
|
previous: str | None
|
|
1064
1072
|
results: list[T]
|
|
1065
1073
|
cursor: str | None = None
|
|
1074
|
+
meta: dict[str, Any] | None = None
|
|
1066
1075
|
page_metadata: dict[str, Any] | None = None
|
|
1067
1076
|
|
|
1077
|
+
@property
|
|
1078
|
+
def agency_warnings(self) -> list[str]:
|
|
1079
|
+
"""Warnings the API raised about agency filters on this request.
|
|
1080
|
+
|
|
1081
|
+
Empty when every supplied agency token resolved cleanly. A non-empty list
|
|
1082
|
+
means part of the filter did not apply, so a small or empty ``results`` is
|
|
1083
|
+
not evidence that no such records exist.
|
|
1084
|
+
|
|
1085
|
+
Examples:
|
|
1086
|
+
>>> response = client.list_contracts(awarding_agency="GSA|NOTANAGENCY")
|
|
1087
|
+
>>> for warning in response.agency_warnings:
|
|
1088
|
+
... print(warning)
|
|
1089
|
+
Agency filter 'awarding_agency': 'NOTANAGENCY' did not match any organization and was ignored.
|
|
1090
|
+
"""
|
|
1091
|
+
if not self.meta:
|
|
1092
|
+
return []
|
|
1093
|
+
warnings = self.meta.get("warnings")
|
|
1094
|
+
return list(warnings) if isinstance(warnings, list) else []
|
|
1095
|
+
|
|
1096
|
+
@property
|
|
1097
|
+
def unresolved_agency_tokens(self) -> dict[str, list[str]]:
|
|
1098
|
+
"""Agency tokens that matched no organization, keyed by filter name.
|
|
1099
|
+
|
|
1100
|
+
Empty when everything resolved. Use this to fail loudly in a pipeline
|
|
1101
|
+
rather than treating a silently-narrowed result set as an answer.
|
|
1102
|
+
|
|
1103
|
+
Examples:
|
|
1104
|
+
>>> response = client.list_contracts(awarding_agency="GSA|NOTANAGENCY")
|
|
1105
|
+
>>> response.unresolved_agency_tokens
|
|
1106
|
+
{'awarding_agency': ['NOTANAGENCY']}
|
|
1107
|
+
"""
|
|
1108
|
+
if not self.meta:
|
|
1109
|
+
return {}
|
|
1110
|
+
resolved = self.meta.get("resolved_filters")
|
|
1111
|
+
if not isinstance(resolved, dict):
|
|
1112
|
+
return {}
|
|
1113
|
+
dropped: dict[str, list[str]] = {}
|
|
1114
|
+
for filter_name, entries in resolved.items():
|
|
1115
|
+
if not isinstance(entries, list):
|
|
1116
|
+
continue
|
|
1117
|
+
tokens = [
|
|
1118
|
+
entry["token"]
|
|
1119
|
+
for entry in entries
|
|
1120
|
+
if isinstance(entry, dict)
|
|
1121
|
+
and entry.get("resolved") is None
|
|
1122
|
+
and entry.get("token") is not None
|
|
1123
|
+
]
|
|
1124
|
+
if tokens:
|
|
1125
|
+
dropped[filter_name] = tokens
|
|
1126
|
+
return dropped
|
|
1127
|
+
|
|
1128
|
+
@property
|
|
1129
|
+
def resolved_agencies(self) -> dict[str, list[dict[str, Any]]]:
|
|
1130
|
+
"""What each agency token actually resolved to, keyed by filter name.
|
|
1131
|
+
|
|
1132
|
+
Agency resolution is fuzzy, so a token can match an organization the caller
|
|
1133
|
+
did not intend and scope the query to that subtree instead. Checking the
|
|
1134
|
+
resolved ``name`` is the only way to catch that from the client side — an
|
|
1135
|
+
unresolved-token check cannot, because nothing was dropped.
|
|
1136
|
+
|
|
1137
|
+
Examples:
|
|
1138
|
+
>>> response = client.list_contracts(awarding_agency="HUD")
|
|
1139
|
+
>>> [org["name"] for org in response.resolved_agencies["awarding_agency"]]
|
|
1140
|
+
['Department of Housing and Urban Development']
|
|
1141
|
+
"""
|
|
1142
|
+
if not self.meta:
|
|
1143
|
+
return {}
|
|
1144
|
+
resolved = self.meta.get("resolved_filters")
|
|
1145
|
+
if not isinstance(resolved, dict):
|
|
1146
|
+
return {}
|
|
1147
|
+
matched: dict[str, list[dict[str, Any]]] = {}
|
|
1148
|
+
for filter_name, entries in resolved.items():
|
|
1149
|
+
if not isinstance(entries, list):
|
|
1150
|
+
continue
|
|
1151
|
+
orgs = [
|
|
1152
|
+
entry["resolved"]
|
|
1153
|
+
for entry in entries
|
|
1154
|
+
if isinstance(entry, dict) and isinstance(entry.get("resolved"), dict)
|
|
1155
|
+
]
|
|
1156
|
+
if orgs:
|
|
1157
|
+
matched[filter_name] = orgs
|
|
1158
|
+
return matched
|
|
1159
|
+
|
|
1068
1160
|
|
|
1069
1161
|
class ShapeConfig:
|
|
1070
1162
|
"""Predefined response shape configurations used as defaults for API methods
|
|
@@ -716,6 +716,8 @@ PRIMARY_CONTACT_SCHEMA: dict[str, FieldSchema] = {
|
|
|
716
716
|
RELATIONSHIPS_SCHEMA: dict[str, FieldSchema] = {
|
|
717
717
|
"display_name": FieldSchema(name="display_name", type=str, is_optional=True, is_list=False),
|
|
718
718
|
"relation": FieldSchema(name="relation", type=str, is_optional=True, is_list=False),
|
|
719
|
+
"source": FieldSchema(name="source", type=str, is_optional=True, is_list=False),
|
|
720
|
+
"type": FieldSchema(name="type", type=str, is_optional=True, is_list=False),
|
|
719
721
|
"uei": FieldSchema(name="uei", type=str, is_optional=True, is_list=False),
|
|
720
722
|
}
|
|
721
723
|
|