tango-python 1.10.0__tar.gz → 1.12.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.10.0 → tango_python-1.12.0}/CHANGELOG.md +22 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/PKG-INFO +1 -1
- {tango_python-1.10.0 → tango_python-1.12.0}/docs/API_REFERENCE.md +45 -2
- {tango_python-1.10.0 → tango_python-1.12.0}/pyproject.toml +1 -1
- {tango_python-1.10.0 → tango_python-1.12.0}/tango/__init__.py +7 -1
- {tango_python-1.10.0 → tango_python-1.12.0}/tango/client.py +23 -1
- {tango_python-1.10.0 → tango_python-1.12.0}/tango/models.py +73 -2
- {tango_python-1.10.0 → tango_python-1.12.0}/tango/shapes/generated_overlay.py +33 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_client.py +135 -0
- tango_python-1.12.0/tests/test_opportunity_awards.py +136 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/.gitignore +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/LICENSE +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/README.md +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/docs/CLIENT.md +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/docs/DYNAMIC_MODELS.md +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/docs/ERRORS.md +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/docs/PAGINATION.md +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/docs/SHAPES.md +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/docs/WEBHOOKS.md +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/scripts/README.md +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/scripts/check_filter_shape_conformance.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/scripts/check_shape_coverage.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/scripts/fetch_api_schema.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/scripts/generate_schemas_from_api.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/scripts/generate_shape_overlay.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/scripts/pr_review.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/scripts/probe_shape_types.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/scripts/refresh_contract.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/scripts/smoke_api_parity.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/scripts/test_production.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tango/exceptions.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tango/shapes/__init__.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tango/shapes/explicit_schemas.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tango/shapes/factory.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tango/shapes/generator.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tango/shapes/models.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tango/shapes/parser.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tango/shapes/schema.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tango/shapes/types.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tango/webhooks/__init__.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tango/webhooks/cli.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tango/webhooks/receiver.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tango/webhooks/signing.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tango/webhooks/simulate.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/__init__.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestAgenciesIntegration.test_get_agency +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestAgenciesIntegration.test_list_agencies +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_field_type_validation +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_parsing_consistency +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestBusinessTypesIntegration.test_list_business_types +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_combined_filters_work_together +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_contract_cursor_pagination +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_contract_data_object_parsing +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_contract_field_types +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[keyword-software] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[psc_code-R425] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_awarding_agency_filter +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_date_range_filter +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_flat +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_naics_code_filter +0 -0
- {tango_python-1.10.0 → tango_python-1.12.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.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[default-None] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.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.10.0 → tango_python-1.12.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.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_new_expiring_filters +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_new_fiscal_year_range_filters +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_new_identifier_filters +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_search_contracts_with_filters +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_search_filters_object_with_new_parameters +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[asc-] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[desc--] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_contracts +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_entities +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_date_field_parsing_edge_cases +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_decimal_field_parsing_edge_cases +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_empty_list_responses +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_entity_parsing_with_various_address_formats +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_flattened_responses_with_flat_lists +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_list_field_parsing_consistency +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_nested_objects_with_missing_data +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_null_missing_fields_in_contracts +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_with_minimal_shape_sparse_data +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_entity_field_types +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_entity_location_parsing +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_entity_parsing_with_business_types +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_entity_with_various_identifiers +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_budget_flows +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_by_uei +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_flat +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_search +0 -0
- {tango_python-1.10.0 → tango_python-1.12.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.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[custom-uei,legal_business_name,cage_code] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.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.10.0 → tango_python-1.12.0}/tests/cassettes/TestForecastsIntegration.test_forecast_field_types +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[custom-id,title,anticipated_award_date] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[default-None] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.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.10.0 → tango_python-1.12.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.10.0 → tango_python-1.12.0}/tests/cassettes/TestGrantsIntegration.test_grant_field_types +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestGrantsIntegration.test_grant_pagination +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[custom-grant_id,title,opportunity_number] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[default-None] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.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.10.0 → tango_python-1.12.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[minimal-grant_id,opportunity_number,title,status(-),agency_code] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestIDVsIntegration.test_get_idv_uses_default_shape +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_awards_uses_default_shape +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_child_idvs_uses_default_shape +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_transactions +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestIDVsIntegration.test_list_idvs_uses_default_shape_and_keyset_params +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_code +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_name_text +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating_max +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_performance_risk +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_type_of_investment +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_updated_time_range +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_funding_and_cio_evaluation_expansions +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_get_itdashboard_investment_by_uii +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_itdashboard_pagination +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_search +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[custom-uii,agency_name,investment_title,updated_time] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[default-None] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.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.10.0 → tango_python-1.12.0}/tests/cassettes/TestNaicsIntegration.test_list_naics +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[custom-notice_id,title,solicitation_number] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[default-None] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.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.10.0 → tango_python-1.12.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestNoticesIntegration.test_notice_field_types +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestNoticesIntegration.test_notice_pagination +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestNoticesIntegration.test_notice_with_meta_fields +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOTAsIntegration.test_get_ota +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOTAsIntegration.test_list_otas +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOTIDVsIntegration.test_get_otidv +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOTIDVsIntegration.test_list_otidvs +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOfficesIntegration.test_get_office +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOfficesIntegration.test_list_offices +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[custom-opportunity_id,title,solicitation_number] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[default-None] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.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.10.0 → tango_python-1.12.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOpportunitiesIntegration.test_opportunity_field_types +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOrganizationsIntegration.test_get_organization +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOrganizationsIntegration.test_list_organizations +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestProtestsIntegration.test_get_protest_by_case_id +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_filter +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[custom-case_id,title,source_system,outcome] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[default-None] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.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.10.0 → tango_python-1.12.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.10.0 → tango_python-1.12.0}/tests/cassettes/TestProtestsIntegration.test_protest_pagination +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestSubawardsIntegration.test_list_subawards +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[custom-key,piid,description] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.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.10.0 → tango_python-1.12.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[ultra_minimal-key,piid,recipient(display_name),total_contract_value] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.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.10.0 → tango_python-1.12.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.10.0 → tango_python-1.12.0}/tests/cassettes/TestTypeHintsIntegration.test_notices_dict_access[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.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.10.0 → tango_python-1.12.0}/tests/cassettes/TestTypeHintsIntegration.test_opportunities_dict_access[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_with_metrics_expansion +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_orders_uses_default_shape +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_with_ordering +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/conftest.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/README.md +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/__init__.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/conftest.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_agencies_integration.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_contracts_integration.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_edge_cases_integration.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_entities_integration.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_forecasts_integration.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_grants_integration.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_itdashboard_integration.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_naics_integration.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_notices_integration.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_offices_integration.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_opportunities_integration.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_organizations_integration.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_otas_otidvs_integration.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_protests_integration.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_reference_data_integration.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_subawards_integration.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_vehicles_idvs_integration.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/validation.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/production/__init__.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/production/conftest.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/production/test_production_smoke.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_api_parity.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_contract_appeals.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_dibbs_exclusions_sbir.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_ebuy.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_federal_register.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_models.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_opportunity_attachments.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_organization_scope.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_shape_coverage_gate.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_shapes.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_sled.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_webhooks_cli.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_webhooks_receiver.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_webhooks_signing.py +0 -0
- {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_webhooks_simulate.py +0 -0
|
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.12.0] - 2026-10-02
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`account_category` and `source_anomalies` on budget accounts** (Tango API 5.7.0). Both are in the default `BUDGET_ACCOUNTS_MINIMAL` shape and accepted in custom shapes. `account_category` is `budgetary` or `credit_financing` today. `source_anomalies` lists the problems found in the source data behind an account, and is `[]` when there are none. Each element is typed as the new `BudgetAccountSourceAnomaly` `TypedDict` (with `BudgetAccountAnomalySource` and `BudgetAccountAnomalySourceRow` for its nested `source`), and every key is optional. Treat `account_category` and an anomaly's `code` as open strings, since new values may appear.
|
|
15
|
+
- **`list_budget_accounts(account_category=..., account_category_in=...)`** filters by account category, exactly or against a comma-separated list.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **`list_budget_accounts()` documents the API's default ordering**: the latest fiscal year first, then largest `enacted_ba` first, with accounts that have no `enacted_ba` last.
|
|
20
|
+
- Re-vendored `contracts/filter_shape_contract.json` (Tango API 5.7.0) and regenerated `tango/shapes/generated_overlay.py`.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **`list_idvs()`, `list_idv_awards()`, `list_idv_child_idvs()` and `list_idv_transactions()` now return the API's pagination `cursor`.** They dropped it, so `response.cursor` was always `None` and a caller following the cursor stopped after the first page.
|
|
25
|
+
|
|
26
|
+
## [1.11.0] - 2026-09-29
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- **Award fields on opportunities and notices** (Tango API 5.5.0) — new `awarded` / `awardee_uei` filters on `list_opportunities()`, plus award leaves and an `awards(...)` expand in the opportunity and notice shapes (see `docs/API_REFERENCE.md`).
|
|
31
|
+
|
|
10
32
|
## [1.10.0] - 2026-09-29
|
|
11
33
|
|
|
12
34
|
### Added
|
|
@@ -868,6 +868,8 @@ opportunities = client.list_opportunities(
|
|
|
868
868
|
# Filter parameters (all optional)
|
|
869
869
|
active=None,
|
|
870
870
|
agency=None,
|
|
871
|
+
awarded=None,
|
|
872
|
+
awardee_uei=None,
|
|
871
873
|
first_notice_date_after=None,
|
|
872
874
|
first_notice_date_before=None,
|
|
873
875
|
last_notice_date_after=None,
|
|
@@ -894,6 +896,8 @@ opportunities = client.list_opportunities(
|
|
|
894
896
|
**Filter Parameters:**
|
|
895
897
|
- `active` - Filter by active status (bool)
|
|
896
898
|
- `agency` - Filter by agency code
|
|
899
|
+
- `awarded` - Whether the opportunity has an award, either posted on it or linked to it (bool). Requires Tango API 5.5.0
|
|
900
|
+
- `awardee_uei` - Awardee UEI, case-insensitive; OR several with `|`. Requires Tango API 5.5.0
|
|
897
901
|
- `first_notice_date_after` / `first_notice_date_before` - First notice date range
|
|
898
902
|
- `last_notice_date_after` / `last_notice_date_before` - Last notice date range
|
|
899
903
|
- `naics` - NAICS code
|
|
@@ -947,6 +951,33 @@ for attachment in opp["attachments"]:
|
|
|
947
951
|
- A Free-plan request that names them gets the response without them, plus an entry in `meta.upgrade_hints`.
|
|
948
952
|
- There is no filter on document role.
|
|
949
953
|
|
|
954
|
+
**Award fields:**
|
|
955
|
+
|
|
956
|
+
Tango API 5.5.0 adds award information to opportunities.
|
|
957
|
+
None of it is in the SDK's default shape, so name the fields you want.
|
|
958
|
+
|
|
959
|
+
```python
|
|
960
|
+
opp = client.get_opportunity(
|
|
961
|
+
opportunity_id,
|
|
962
|
+
shape=(
|
|
963
|
+
"opportunity_id,title,awarded,award_date,award_amount,awardee,awardee_uei,"
|
|
964
|
+
"award_count,solicitation_opportunity_id,"
|
|
965
|
+
"awards(opportunity_id,notice_id,award_number,award_date,award_amount,awardee,awardee_uei)"
|
|
966
|
+
),
|
|
967
|
+
)
|
|
968
|
+
|
|
969
|
+
awarded = client.list_opportunities(
|
|
970
|
+
awardee_uei="ABCDEF123456",
|
|
971
|
+
shape="opportunity_id,title,award_date,awardee",
|
|
972
|
+
)
|
|
973
|
+
```
|
|
974
|
+
|
|
975
|
+
- `award_date`, `award_amount`, `awardee` and `awardee_uei` are filled only where SAM.gov posted an award notice; elsewhere they are null. `award_amount` is the text SAM.gov published, not a number.
|
|
976
|
+
- SAM.gov often posts an award as its own opportunity. When that award notice references its solicitation, the award's `solicitation_opportunity_id` points back to the solicitation, and the solicitation's `awards(...)` lists up to ten of the most recent linked awards; `award_count` is the full count. An award notice that does not reference its solicitation is not linked.
|
|
977
|
+
- `awarded` is true when the opportunity has an award of its own or has linked awards.
|
|
978
|
+
- The `awarded` and `awardee_uei` filters search every opportunity, not just active ones, so pass `active=True` to narrow to open opportunities.
|
|
979
|
+
- Notices accept `award_date`, `award_amount`, `awardee` and `awardee_uei` in `shape` too.
|
|
980
|
+
|
|
950
981
|
---
|
|
951
982
|
|
|
952
983
|
## Notices
|
|
@@ -1739,6 +1770,8 @@ accounts = client.list_budget_accounts(
|
|
|
1739
1770
|
bea_category=None,
|
|
1740
1771
|
on_off_budget=None,
|
|
1741
1772
|
subfunction_code=None,
|
|
1773
|
+
account_category=None,
|
|
1774
|
+
account_category_in=None,
|
|
1742
1775
|
search=None,
|
|
1743
1776
|
ordering=None,
|
|
1744
1777
|
)
|
|
@@ -1754,8 +1787,18 @@ accounts = client.list_budget_accounts(
|
|
|
1754
1787
|
- `bea_category` - BEA category (exact)
|
|
1755
1788
|
- `on_off_budget` - On/off budget flag (exact)
|
|
1756
1789
|
- `subfunction_code` - Subfunction code (exact)
|
|
1790
|
+
- `account_category` - Account category (exact): `budgetary` or `credit_financing` today; treat it as an open string
|
|
1791
|
+
- `account_category_in` - Comma-separated account categories to match any of (e.g. `"budgetary,credit_financing"`)
|
|
1757
1792
|
- `search` - Full-text search over `account_title`, `agency_name`, `bureau_name`
|
|
1758
|
-
- `ordering` - Sort field; prefix with `-` for descending
|
|
1793
|
+
- `ordering` - Sort field; prefix with `-` for descending. The default is the latest fiscal year first, then largest `enacted_ba` first, with accounts that have no `enacted_ba` last.
|
|
1794
|
+
|
|
1795
|
+
**Source anomalies:** the default shape includes `account_category` and `source_anomalies`. `source_anomalies` is a list of problems found in the source data behind the account, and `[]` when there are none. Each element is a `BudgetAccountSourceAnomaly` (a `TypedDict`; every key is optional and may be `None`, so read keys with `.get()`): `code`, `field`, `bound_field`, `action` (`capped` or `flagged`), `reported_value`, `served_value`, `likely_cause`, `affected_fields`, `message`, and `source` (`dataset`, `fiscal_year`, and `rows` of `fiscal_period`, `piid`, `parent_piid`, `tas`, `reporting_agency_id`, `transaction_obligated_amount`, `file_c_source`). Today `code` is one of `contract_exceeds_obligations`, `assistance_exceeds_obligations` or `contract_without_obligations`; treat it as an open string. There is no filter on anomalies.
|
|
1796
|
+
|
|
1797
|
+
```python
|
|
1798
|
+
for acct in client.list_budget_accounts(fiscal_year=2025).results:
|
|
1799
|
+
for anomaly in acct["source_anomalies"] or []:
|
|
1800
|
+
print(acct["federal_account_symbol"], anomaly.get("code"), anomaly.get("action"))
|
|
1801
|
+
```
|
|
1759
1802
|
|
|
1760
1803
|
**Returns:** [PaginatedResponse](#paginatedresponse) of `BudgetAccount` records (see [ShapeConfig](#shapeconfig-predefined-shapes) for the default shape).
|
|
1761
1804
|
|
|
@@ -2516,7 +2559,7 @@ entity = client.get_entity("UEI_KEY", shape=ShapeConfig.ENTITIES_COMPREHENSIVE)
|
|
|
2516
2559
|
| `FEDERAL_REGISTER_COMPREHENSIVE` | `get_federal_register_document` | The list fields plus dates, signing_date, start_page, end_page, volume, docket_ids, dockets, regulation_id_numbers, topics, correction_of, corrections, executive_order_number, presidential_document_number, proclamation_number, comment_url, regulations_dot_gov_url, raw_text_url, body_html_url (omits `full_text`) |
|
|
2517
2560
|
| `EBUY_REQUESTS_MINIMAL` | `list_ebuy_requests` | rfq_id, request_type, title, schedule, sin, status, buyer_name, buyer_agency, buyer_agency_code, reference_number, issue_date, close_date, attachment_count, link_count, last_seen |
|
|
2518
2561
|
| `EBUY_REQUESTS_COMPREHENSIVE` | `get_ebuy_request` | Every field, plus the `organization` and `attachments` expands |
|
|
2519
|
-
| `BUDGET_ACCOUNTS_MINIMAL` | `list_budget_accounts`, `get_budget_account` | id, federal_account_symbol, fiscal_year, agency_code/name, bureau_name, account_title, bea_category, on_off_budget, subfunction_code, lifecycle (requested/enacted/apportioned/obligated/outlayed/unobligated), contract & assistance rollups, key ratios, next-year growth |
|
|
2562
|
+
| `BUDGET_ACCOUNTS_MINIMAL` | `list_budget_accounts`, `get_budget_account` | id, federal_account_symbol, fiscal_year, agency_code/name, bureau_name, account_title, bea_category, on_off_budget, subfunction_code, account_category, lifecycle (requested/enacted/apportioned/obligated/outlayed/unobligated), contract & assistance rollups, key ratios, next-year growth, source_anomalies |
|
|
2520
2563
|
| `VEHICLE_ORDERS_MINIMAL` | `list_vehicle_orders` | key, piid, award_date, recipient(display_name,uei), total_contract_value, obligated |
|
|
2521
2564
|
| `ITDASHBOARD_INVESTMENTS_MINIMAL` | `list_itdashboard_investments` | Minimal IT Dashboard investment fields |
|
|
2522
2565
|
| `ITDASHBOARD_INVESTMENTS_COMPREHENSIVE` | `get_itdashboard_investment` | Full investment fields: uii, agency_code, agency_name, bureau_code, bureau_name, investment_title, type_of_investment, part_of_it_portfolio, updated_time, url |
|
|
@@ -11,6 +11,9 @@ from .exceptions import (
|
|
|
11
11
|
)
|
|
12
12
|
from .models import (
|
|
13
13
|
BudgetAccount,
|
|
14
|
+
BudgetAccountAnomalySource,
|
|
15
|
+
BudgetAccountAnomalySourceRow,
|
|
16
|
+
BudgetAccountSourceAnomaly,
|
|
14
17
|
ContractAppeal,
|
|
15
18
|
DibbsAward,
|
|
16
19
|
DibbsRfp,
|
|
@@ -59,7 +62,7 @@ from .webhooks import (
|
|
|
59
62
|
)
|
|
60
63
|
from .webhooks.receiver import Delivery, WebhookReceiver
|
|
61
64
|
|
|
62
|
-
__version__ = "1.
|
|
65
|
+
__version__ = "1.12.0"
|
|
63
66
|
__all__ = [
|
|
64
67
|
"TangoClient",
|
|
65
68
|
"TangoAPIError",
|
|
@@ -72,6 +75,9 @@ __all__ = [
|
|
|
72
75
|
"ResolveCandidate",
|
|
73
76
|
"ResolveResult",
|
|
74
77
|
"BudgetAccount",
|
|
78
|
+
"BudgetAccountAnomalySource",
|
|
79
|
+
"BudgetAccountAnomalySourceRow",
|
|
80
|
+
"BudgetAccountSourceAnomaly",
|
|
75
81
|
"ContractAppeal",
|
|
76
82
|
"FederalRegisterDocument",
|
|
77
83
|
"EbuyAccess",
|
|
@@ -986,6 +986,7 @@ class TangoClient:
|
|
|
986
986
|
next=data.get("next"),
|
|
987
987
|
previous=data.get("previous"),
|
|
988
988
|
results=results,
|
|
989
|
+
cursor=data.get("cursor"),
|
|
989
990
|
page_metadata=data.get("page_metadata"),
|
|
990
991
|
meta=data.get("meta"),
|
|
991
992
|
)
|
|
@@ -1088,6 +1089,7 @@ class TangoClient:
|
|
|
1088
1089
|
next=data.get("next"),
|
|
1089
1090
|
previous=data.get("previous"),
|
|
1090
1091
|
results=results,
|
|
1092
|
+
cursor=data.get("cursor"),
|
|
1091
1093
|
page_metadata=data.get("page_metadata"),
|
|
1092
1094
|
meta=data.get("meta"),
|
|
1093
1095
|
)
|
|
@@ -1133,6 +1135,7 @@ class TangoClient:
|
|
|
1133
1135
|
next=data.get("next"),
|
|
1134
1136
|
previous=data.get("previous"),
|
|
1135
1137
|
results=results,
|
|
1138
|
+
cursor=data.get("cursor"),
|
|
1136
1139
|
page_metadata=data.get("page_metadata"),
|
|
1137
1140
|
meta=data.get("meta"),
|
|
1138
1141
|
)
|
|
@@ -1150,6 +1153,7 @@ class TangoClient:
|
|
|
1150
1153
|
next=data.get("next"),
|
|
1151
1154
|
previous=data.get("previous"),
|
|
1152
1155
|
results=data.get("results") or [],
|
|
1156
|
+
cursor=data.get("cursor"),
|
|
1153
1157
|
page_metadata=data.get("page_metadata"),
|
|
1154
1158
|
meta=data.get("meta"),
|
|
1155
1159
|
)
|
|
@@ -2346,6 +2350,9 @@ class TangoClient:
|
|
|
2346
2350
|
search: str | None = None,
|
|
2347
2351
|
set_aside: str | None = None,
|
|
2348
2352
|
solicitation_number: str | None = None,
|
|
2353
|
+
*,
|
|
2354
|
+
awarded: bool | None = None,
|
|
2355
|
+
awardee_uei: str | None = None,
|
|
2349
2356
|
) -> PaginatedResponse:
|
|
2350
2357
|
"""
|
|
2351
2358
|
List contract opportunities/solicitations
|
|
@@ -2358,6 +2365,8 @@ class TangoClient:
|
|
|
2358
2365
|
flat_lists: If True, flatten arrays using indexed keys
|
|
2359
2366
|
active: Filter by active status
|
|
2360
2367
|
agency: Agency filter
|
|
2368
|
+
awarded: Filter by whether the opportunity has an award, either posted on it or linked to it from a separate award notice. Searches every opportunity rather than only active ones. Requires Tango API 5.5.0
|
|
2369
|
+
awardee_uei: Filter by the awardee's UEI (case-insensitive). OR several with ``|``. Searches every opportunity rather than only active ones. Requires Tango API 5.5.0
|
|
2361
2370
|
first_notice_date_after: First notice date after
|
|
2362
2371
|
first_notice_date_before: First notice date before
|
|
2363
2372
|
last_notice_date_after: Last notice date after
|
|
@@ -2387,6 +2396,8 @@ class TangoClient:
|
|
|
2387
2396
|
for key, val in (
|
|
2388
2397
|
("active", active),
|
|
2389
2398
|
("agency", agency),
|
|
2399
|
+
("awarded", awarded),
|
|
2400
|
+
("awardee_uei", awardee_uei),
|
|
2390
2401
|
("first_notice_date_after", first_notice_date_after),
|
|
2391
2402
|
("first_notice_date_before", first_notice_date_before),
|
|
2392
2403
|
("last_notice_date_after", last_notice_date_after),
|
|
@@ -4437,6 +4448,8 @@ class TangoClient:
|
|
|
4437
4448
|
bea_category: str | None = None,
|
|
4438
4449
|
on_off_budget: str | None = None,
|
|
4439
4450
|
subfunction_code: str | None = None,
|
|
4451
|
+
account_category: str | None = None,
|
|
4452
|
+
account_category_in: str | None = None,
|
|
4440
4453
|
# Range-numeric filters: each field also accepts ``__gte`` / ``__lte``.
|
|
4441
4454
|
requested_ba: float | None = None,
|
|
4442
4455
|
requested_ba_gte: float | None = None,
|
|
@@ -4543,6 +4556,11 @@ class TangoClient:
|
|
|
4543
4556
|
bea_category: BEA category (exact).
|
|
4544
4557
|
on_off_budget: On/off budget flag (exact).
|
|
4545
4558
|
subfunction_code: Subfunction code (exact).
|
|
4559
|
+
account_category: Account category (exact), e.g. ``budgetary`` or
|
|
4560
|
+
``credit_financing``. Treat the value as an open string; new
|
|
4561
|
+
categories may be added.
|
|
4562
|
+
account_category_in: Comma-separated account categories to match
|
|
4563
|
+
any of, e.g. ``"budgetary,credit_financing"``.
|
|
4546
4564
|
requested_ba: President's-budget requested BA (exact). Also
|
|
4547
4565
|
``requested_ba_gte`` / ``requested_ba_lte`` for range queries.
|
|
4548
4566
|
enacted_ba: Enacted budget authority (exact / gte / lte).
|
|
@@ -4585,7 +4603,9 @@ class TangoClient:
|
|
|
4585
4603
|
ordering: Sort field (prefix with '-' for descending). Any of the
|
|
4586
4604
|
numeric fields above is a valid ordering target — e.g.
|
|
4587
4605
|
``ordering="-unobligated_balance"`` to rank by largest
|
|
4588
|
-
headroom first.
|
|
4606
|
+
headroom first. The default ordering is the latest fiscal year
|
|
4607
|
+
first, then largest ``enacted_ba`` first, with accounts that
|
|
4608
|
+
have no ``enacted_ba`` sorted last.
|
|
4589
4609
|
"""
|
|
4590
4610
|
params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
|
|
4591
4611
|
if shape is None:
|
|
@@ -4608,6 +4628,8 @@ class TangoClient:
|
|
|
4608
4628
|
("bea_category", bea_category),
|
|
4609
4629
|
("on_off_budget", on_off_budget),
|
|
4610
4630
|
("subfunction_code", subfunction_code),
|
|
4631
|
+
("account_category", account_category),
|
|
4632
|
+
("account_category__in", account_category_in),
|
|
4611
4633
|
("search", search),
|
|
4612
4634
|
("ordering", ordering),
|
|
4613
4635
|
)
|
|
@@ -565,6 +565,10 @@ class Opportunity:
|
|
|
565
565
|
Both must be named, e.g. ``attachments(name,url,doc_role,doc_role_alt)``, because ``attachments(*)`` does not include them.
|
|
566
566
|
An attachment Tango has not classified omits both keys rather than returning null, so read them with ``.get()``.
|
|
567
567
|
A Free-plan request that names them gets the response without them, plus an entry in ``meta.upgrade_hints``.
|
|
568
|
+
|
|
569
|
+
The award fields (``award_date``, ``award_amount``, ``awardee``, ``awardee_uei``) are filled only where SAM.gov posted an award notice, and ``award_amount`` is the text SAM.gov published, not a number.
|
|
570
|
+
SAM.gov often posts an award as its own opportunity; when that award notice references its solicitation, ``solicitation_opportunity_id`` points back to it, and the solicitation's ``awards(...)`` expand lists up to ten of the most recent linked awards, with ``award_count`` giving the full count.
|
|
571
|
+
An award notice that does not reference its solicitation is not linked.
|
|
568
572
|
"""
|
|
569
573
|
|
|
570
574
|
opportunity_id: str
|
|
@@ -575,6 +579,15 @@ class Opportunity:
|
|
|
575
579
|
active: bool | None = None
|
|
576
580
|
naics_code: str | None = None
|
|
577
581
|
psc_code: str | None = None
|
|
582
|
+
award_number: str | None = None
|
|
583
|
+
awarded: bool | None = None
|
|
584
|
+
award_date: date | None = None
|
|
585
|
+
award_amount: str | None = None
|
|
586
|
+
awardee: str | None = None
|
|
587
|
+
awardee_uei: str | None = None
|
|
588
|
+
award_count: int | None = None
|
|
589
|
+
solicitation_opportunity_id: str | None = None
|
|
590
|
+
awards: list[dict[str, Any]] | None = None
|
|
578
591
|
|
|
579
592
|
|
|
580
593
|
@dataclass
|
|
@@ -593,6 +606,11 @@ class Notice:
|
|
|
593
606
|
description: str | None = None
|
|
594
607
|
posted_date: datetime | None = None
|
|
595
608
|
naics_code: str | None = None
|
|
609
|
+
award_number: str | None = None
|
|
610
|
+
award_date: date | None = None
|
|
611
|
+
award_amount: str | None = None
|
|
612
|
+
awardee: str | None = None
|
|
613
|
+
awardee_uei: str | None = None
|
|
596
614
|
|
|
597
615
|
|
|
598
616
|
@dataclass
|
|
@@ -768,6 +786,55 @@ class ValidateResult:
|
|
|
768
786
|
errors: list[str] | None = None
|
|
769
787
|
|
|
770
788
|
|
|
789
|
+
class BudgetAccountAnomalySourceRow(TypedDict, total=False):
|
|
790
|
+
"""One source record behind a budget-account source anomaly.
|
|
791
|
+
|
|
792
|
+
Every key is optional and any value may be ``None``.
|
|
793
|
+
"""
|
|
794
|
+
|
|
795
|
+
fiscal_period: int | None
|
|
796
|
+
piid: str | None
|
|
797
|
+
parent_piid: str | None
|
|
798
|
+
tas: str | None
|
|
799
|
+
reporting_agency_id: str | None
|
|
800
|
+
transaction_obligated_amount: float | str | None
|
|
801
|
+
file_c_source: str | None
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
class BudgetAccountAnomalySource(TypedDict, total=False):
|
|
805
|
+
"""Where a budget-account source anomaly was found.
|
|
806
|
+
|
|
807
|
+
Every key is optional and any value may be ``None``.
|
|
808
|
+
"""
|
|
809
|
+
|
|
810
|
+
dataset: str | None
|
|
811
|
+
fiscal_year: int | None
|
|
812
|
+
rows: list[BudgetAccountAnomalySourceRow] | None
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
class BudgetAccountSourceAnomaly(TypedDict, total=False):
|
|
816
|
+
"""One problem found in the source data behind a budget account.
|
|
817
|
+
|
|
818
|
+
An element of ``BudgetAccount.source_anomalies``. Every key is optional and
|
|
819
|
+
any value may be ``None``, so read keys with ``.get()``. ``code`` and
|
|
820
|
+
``action`` are open strings: today ``code`` is one of
|
|
821
|
+
``contract_exceeds_obligations``, ``assistance_exceeds_obligations`` or
|
|
822
|
+
``contract_without_obligations``, and ``action`` is ``capped`` or
|
|
823
|
+
``flagged``, but new values may appear.
|
|
824
|
+
"""
|
|
825
|
+
|
|
826
|
+
code: str | None
|
|
827
|
+
field: str | None
|
|
828
|
+
bound_field: str | None
|
|
829
|
+
action: str | None
|
|
830
|
+
reported_value: float | str | None
|
|
831
|
+
served_value: float | str | None
|
|
832
|
+
likely_cause: str | None
|
|
833
|
+
affected_fields: list[str] | None
|
|
834
|
+
message: str | None
|
|
835
|
+
source: BudgetAccountAnomalySource | None
|
|
836
|
+
|
|
837
|
+
|
|
771
838
|
@dataclass
|
|
772
839
|
class BudgetAccount:
|
|
773
840
|
"""Schema definition for BudgetAccount (not used for instances).
|
|
@@ -787,6 +854,8 @@ class BudgetAccount:
|
|
|
787
854
|
bea_category: str | None = None
|
|
788
855
|
on_off_budget: str | None = None
|
|
789
856
|
subfunction_code: str | None = None
|
|
857
|
+
# ``budgetary`` or ``credit_financing`` today; treat as an open string.
|
|
858
|
+
account_category: str | None = None
|
|
790
859
|
# Lifecycle
|
|
791
860
|
requested_ba: Decimal | None = None
|
|
792
861
|
enacted_ba: Decimal | None = None
|
|
@@ -810,6 +879,8 @@ class BudgetAccount:
|
|
|
810
879
|
assistance_share_of_obligated: Decimal | None = None
|
|
811
880
|
assistance_share_of_obligated_capped: Decimal | None = None
|
|
812
881
|
assistance_share_capped_flag: bool | None = None
|
|
882
|
+
# Problems found in the source data; ``[]`` when there are none.
|
|
883
|
+
source_anomalies: list[BudgetAccountSourceAnomaly] | None = None
|
|
813
884
|
# Forward-look
|
|
814
885
|
next_year_requested_ba: Decimal | None = None
|
|
815
886
|
ba_growth_next_year: Decimal | None = None
|
|
@@ -1710,12 +1781,12 @@ class ShapeConfig:
|
|
|
1710
1781
|
# Mirrors the API's BUDGET_ACCOUNT_DEFAULT_SHAPE.
|
|
1711
1782
|
BUDGET_ACCOUNTS_MINIMAL: Final = (
|
|
1712
1783
|
"id,federal_account_symbol,fiscal_year,agency_code,agency_name,bureau_name,"
|
|
1713
|
-
"account_title,bea_category,on_off_budget,subfunction_code,"
|
|
1784
|
+
"account_title,bea_category,on_off_budget,subfunction_code,account_category,"
|
|
1714
1785
|
"requested_ba,enacted_ba,apportioned,obligated_total,outlayed_total,"
|
|
1715
1786
|
"unobligated_balance,contract_obligated,contract_share_of_obligated_capped,"
|
|
1716
1787
|
"assistance_obligated,obligated_to_apportioned_pct_capped,"
|
|
1717
1788
|
"obligated_to_enacted_pct_capped,outlayed_to_obligated_pct_capped,"
|
|
1718
|
-
"ba_growth_next_year_pct"
|
|
1789
|
+
"ba_growth_next_year_pct,source_anomalies"
|
|
1719
1790
|
)
|
|
1720
1791
|
|
|
1721
1792
|
# Default for list_organizations()
|
|
@@ -213,6 +213,16 @@ AWARDS_SCHEMA: dict[str, FieldSchema] = {
|
|
|
213
213
|
"transactions": FieldSchema(name="transactions", type=str, is_optional=True, is_list=False),
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
AWARDS2_SCHEMA: dict[str, FieldSchema] = {
|
|
217
|
+
"award_amount": FieldSchema(name="award_amount", type=str, is_optional=True, is_list=False),
|
|
218
|
+
"award_date": FieldSchema(name="award_date", type=date, is_optional=True, is_list=False),
|
|
219
|
+
"award_number": FieldSchema(name="award_number", type=str, is_optional=True, is_list=False),
|
|
220
|
+
"awardee": FieldSchema(name="awardee", type=str, is_optional=True, is_list=False),
|
|
221
|
+
"awardee_uei": FieldSchema(name="awardee_uei", type=str, is_optional=True, is_list=False),
|
|
222
|
+
"notice_id": FieldSchema(name="notice_id", type=str, is_optional=True, is_list=False),
|
|
223
|
+
"opportunity_id": FieldSchema(name="opportunity_id", type=str, is_optional=True, is_list=False),
|
|
224
|
+
}
|
|
225
|
+
|
|
216
226
|
BUDGET_APPROPRIATION_SCHEMA: dict[str, FieldSchema] = {
|
|
217
227
|
"cgac": FieldSchema(name="cgac", type=int, is_optional=True, is_list=False),
|
|
218
228
|
"fiscal_year": FieldSchema(name="fiscal_year", type=int, is_optional=True, is_list=False),
|
|
@@ -1112,6 +1122,7 @@ GENERATED_NESTED: dict[str, dict[str, FieldSchema]] = {
|
|
|
1112
1122
|
"AwardingOffice": AWARDING_OFFICE_SCHEMA,
|
|
1113
1123
|
"AwardingOffice2": AWARDING_OFFICE2_SCHEMA,
|
|
1114
1124
|
"Awards": AWARDS_SCHEMA,
|
|
1125
|
+
"Awards2": AWARDS2_SCHEMA,
|
|
1115
1126
|
"BudgetAppropriation": BUDGET_APPROPRIATION_SCHEMA,
|
|
1116
1127
|
"BudgetSpending": BUDGET_SPENDING_SCHEMA,
|
|
1117
1128
|
"Children": CHILDREN_SCHEMA,
|
|
@@ -1214,6 +1225,9 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
|
|
|
1214
1225
|
),
|
|
1215
1226
|
},
|
|
1216
1227
|
"BudgetAccount": {
|
|
1228
|
+
"account_category": FieldSchema(
|
|
1229
|
+
name="account_category", type=str, is_optional=True, is_list=False
|
|
1230
|
+
),
|
|
1217
1231
|
"account_narrative_excerpt": FieldSchema(
|
|
1218
1232
|
name="account_narrative_excerpt", type=str, is_optional=True, is_list=False
|
|
1219
1233
|
),
|
|
@@ -1399,6 +1413,9 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
|
|
|
1399
1413
|
"requested_personnel_share": FieldSchema(
|
|
1400
1414
|
name="requested_personnel_share", type=str, is_optional=True, is_list=False
|
|
1401
1415
|
),
|
|
1416
|
+
"source_anomalies": FieldSchema(
|
|
1417
|
+
name="source_anomalies", type=dict, is_optional=True, is_list=True
|
|
1418
|
+
),
|
|
1402
1419
|
"subfunction_code": FieldSchema(
|
|
1403
1420
|
name="subfunction_code", type=str, is_optional=True, is_list=False
|
|
1404
1421
|
),
|
|
@@ -2228,6 +2245,10 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
|
|
|
2228
2245
|
is_list=True,
|
|
2229
2246
|
nested_model="Attachments",
|
|
2230
2247
|
),
|
|
2248
|
+
"award_amount": FieldSchema(name="award_amount", type=str, is_optional=True, is_list=False),
|
|
2249
|
+
"award_date": FieldSchema(name="award_date", type=date, is_optional=True, is_list=False),
|
|
2250
|
+
"awardee": FieldSchema(name="awardee", type=str, is_optional=True, is_list=False),
|
|
2251
|
+
"awardee_uei": FieldSchema(name="awardee_uei", type=str, is_optional=True, is_list=False),
|
|
2231
2252
|
"meta": FieldSchema(
|
|
2232
2253
|
name="meta", type=dict, is_optional=True, is_list=False, nested_model="Meta"
|
|
2233
2254
|
),
|
|
@@ -2476,6 +2497,15 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
|
|
|
2476
2497
|
is_list=False,
|
|
2477
2498
|
nested_model="Attachments5",
|
|
2478
2499
|
),
|
|
2500
|
+
"award_amount": FieldSchema(name="award_amount", type=str, is_optional=True, is_list=False),
|
|
2501
|
+
"award_count": FieldSchema(name="award_count", type=int, is_optional=True, is_list=False),
|
|
2502
|
+
"award_date": FieldSchema(name="award_date", type=date, is_optional=True, is_list=False),
|
|
2503
|
+
"awarded": FieldSchema(name="awarded", type=bool, is_optional=True, is_list=False),
|
|
2504
|
+
"awardee": FieldSchema(name="awardee", type=str, is_optional=True, is_list=False),
|
|
2505
|
+
"awardee_uei": FieldSchema(name="awardee_uei", type=str, is_optional=True, is_list=False),
|
|
2506
|
+
"awards": FieldSchema(
|
|
2507
|
+
name="awards", type=dict, is_optional=True, is_list=True, nested_model="Awards2"
|
|
2508
|
+
),
|
|
2479
2509
|
"department": FieldSchema(
|
|
2480
2510
|
name="department",
|
|
2481
2511
|
type=dict,
|
|
@@ -2529,6 +2559,9 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
|
|
|
2529
2559
|
nested_model="CodeDescription",
|
|
2530
2560
|
),
|
|
2531
2561
|
"snippet": FieldSchema(name="snippet", type=str, is_optional=True, is_list=False),
|
|
2562
|
+
"solicitation_opportunity_id": FieldSchema(
|
|
2563
|
+
name="solicitation_opportunity_id", type=str, is_optional=True, is_list=False
|
|
2564
|
+
),
|
|
2532
2565
|
},
|
|
2533
2566
|
"Organization": {
|
|
2534
2567
|
"aac_code": FieldSchema(name="aac_code", type=str, is_optional=True, is_list=False),
|
|
@@ -271,6 +271,34 @@ class TestTangoClient:
|
|
|
271
271
|
assert otas.results[0]["key"] == "OTA-1"
|
|
272
272
|
assert otas.cursor == "next-page-token"
|
|
273
273
|
|
|
274
|
+
@pytest.mark.parametrize(
|
|
275
|
+
("method", "args"),
|
|
276
|
+
[
|
|
277
|
+
("list_idvs", ()),
|
|
278
|
+
("list_idv_awards", ("IDV-1",)),
|
|
279
|
+
("list_idv_child_idvs", ("IDV-1",)),
|
|
280
|
+
("list_idv_transactions", ("IDV-1",)),
|
|
281
|
+
],
|
|
282
|
+
)
|
|
283
|
+
@patch("tango.client.httpx.Client.request")
|
|
284
|
+
def test_idv_list_methods_return_cursor(self, mock_request, method, args):
|
|
285
|
+
mock_response = Mock()
|
|
286
|
+
mock_response.is_success = True
|
|
287
|
+
mock_response.json.return_value = {
|
|
288
|
+
"count": 250,
|
|
289
|
+
"next": "https://tango.makegov.com/api/idvs/?cursor=next-page-token",
|
|
290
|
+
"previous": None,
|
|
291
|
+
"results": [{"key": "IDV-1"}],
|
|
292
|
+
"cursor": "next-page-token",
|
|
293
|
+
}
|
|
294
|
+
mock_response.content = b'{"count": 250}'
|
|
295
|
+
mock_request.return_value = mock_response
|
|
296
|
+
|
|
297
|
+
client = TangoClient(api_key="test-key")
|
|
298
|
+
page = getattr(client, method)(*args, limit=1)
|
|
299
|
+
|
|
300
|
+
assert page.cursor == "next-page-token"
|
|
301
|
+
|
|
274
302
|
@patch("tango.client.httpx.Client.request")
|
|
275
303
|
def test_list_otidvs_with_default_shape(self, mock_request):
|
|
276
304
|
"""Test list_otidvs uses default minimal shape"""
|
|
@@ -573,6 +601,113 @@ class TestTangoClient:
|
|
|
573
601
|
]
|
|
574
602
|
assert leaked == [], f"unexpected filter keys sent: {leaked}"
|
|
575
603
|
|
|
604
|
+
@patch("tango.client.httpx.Client.request")
|
|
605
|
+
def test_list_budget_accounts_account_category_filters(self, mock_request):
|
|
606
|
+
mock_response = Mock()
|
|
607
|
+
mock_response.is_success = True
|
|
608
|
+
mock_response.json.return_value = {
|
|
609
|
+
"count": 0,
|
|
610
|
+
"next": None,
|
|
611
|
+
"previous": None,
|
|
612
|
+
"results": [],
|
|
613
|
+
}
|
|
614
|
+
mock_response.content = b'{"count": 0, "results": []}'
|
|
615
|
+
mock_request.return_value = mock_response
|
|
616
|
+
|
|
617
|
+
client = TangoClient(api_key="test-key")
|
|
618
|
+
client.list_budget_accounts(
|
|
619
|
+
account_category="credit_financing",
|
|
620
|
+
account_category_in="budgetary,credit_financing",
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
params = mock_request.call_args[1]["params"]
|
|
624
|
+
assert params["account_category"] == "credit_financing"
|
|
625
|
+
assert params["account_category__in"] == "budgetary,credit_financing"
|
|
626
|
+
|
|
627
|
+
@patch("tango.client.httpx.Client.request")
|
|
628
|
+
def test_list_budget_accounts_default_shape_parses_source_anomalies(self, mock_request):
|
|
629
|
+
anomaly = {
|
|
630
|
+
"code": "contract_exceeds_obligations",
|
|
631
|
+
"field": "contract_obligated",
|
|
632
|
+
"bound_field": "obligated_total",
|
|
633
|
+
"action": "capped",
|
|
634
|
+
"reported_value": 1250.0,
|
|
635
|
+
"served_value": 1000.0,
|
|
636
|
+
"likely_cause": None,
|
|
637
|
+
"affected_fields": ["contract_obligated", "contract_share_of_obligated"],
|
|
638
|
+
"message": "Contract obligations exceed total obligations.",
|
|
639
|
+
"source": {
|
|
640
|
+
"dataset": "file_c",
|
|
641
|
+
"fiscal_year": 2025,
|
|
642
|
+
"rows": [
|
|
643
|
+
{
|
|
644
|
+
"fiscal_period": 12,
|
|
645
|
+
"piid": "ABC123",
|
|
646
|
+
"parent_piid": None,
|
|
647
|
+
"tas": "012-1234",
|
|
648
|
+
"reporting_agency_id": "012",
|
|
649
|
+
"transaction_obligated_amount": 1250.0,
|
|
650
|
+
"file_c_source": "award",
|
|
651
|
+
}
|
|
652
|
+
],
|
|
653
|
+
},
|
|
654
|
+
}
|
|
655
|
+
mock_response = Mock()
|
|
656
|
+
mock_response.is_success = True
|
|
657
|
+
mock_response.json.return_value = {
|
|
658
|
+
"count": 2,
|
|
659
|
+
"next": None,
|
|
660
|
+
"previous": None,
|
|
661
|
+
"results": [
|
|
662
|
+
{
|
|
663
|
+
"id": 1,
|
|
664
|
+
"fiscal_year": 2025,
|
|
665
|
+
"account_category": "budgetary",
|
|
666
|
+
"source_anomalies": [anomaly],
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
"id": 2,
|
|
670
|
+
"fiscal_year": 2025,
|
|
671
|
+
"account_category": "credit_financing",
|
|
672
|
+
"enacted_ba": None,
|
|
673
|
+
"source_anomalies": [],
|
|
674
|
+
},
|
|
675
|
+
],
|
|
676
|
+
}
|
|
677
|
+
mock_response.content = b"{}"
|
|
678
|
+
mock_request.return_value = mock_response
|
|
679
|
+
|
|
680
|
+
client = TangoClient(api_key="test-key")
|
|
681
|
+
page = client.list_budget_accounts()
|
|
682
|
+
|
|
683
|
+
shape_fields = mock_request.call_args[1]["params"]["shape"].split(",")
|
|
684
|
+
assert "account_category" in shape_fields
|
|
685
|
+
assert "source_anomalies" in shape_fields
|
|
686
|
+
first, second = page.results
|
|
687
|
+
assert first["account_category"] == "budgetary"
|
|
688
|
+
assert first["source_anomalies"] == [anomaly]
|
|
689
|
+
assert first["source_anomalies"][0]["source"]["rows"][0]["piid"] == "ABC123"
|
|
690
|
+
assert second["account_category"] == "credit_financing"
|
|
691
|
+
assert second["source_anomalies"] == []
|
|
692
|
+
|
|
693
|
+
@patch("tango.client.httpx.Client.request")
|
|
694
|
+
def test_get_budget_account_accepts_explicit_anomaly_shape(self, mock_request):
|
|
695
|
+
mock_response = Mock()
|
|
696
|
+
mock_response.is_success = True
|
|
697
|
+
mock_response.json.return_value = {
|
|
698
|
+
"id": 7,
|
|
699
|
+
"account_category": "budgetary",
|
|
700
|
+
"source_anomalies": [{"code": "some_future_code"}],
|
|
701
|
+
}
|
|
702
|
+
mock_response.content = b"{}"
|
|
703
|
+
mock_request.return_value = mock_response
|
|
704
|
+
|
|
705
|
+
client = TangoClient(api_key="test-key")
|
|
706
|
+
account = client.get_budget_account(7, shape="id,account_category,source_anomalies")
|
|
707
|
+
|
|
708
|
+
assert account["source_anomalies"] == [{"code": "some_future_code"}]
|
|
709
|
+
assert account["account_category"] == "budgetary"
|
|
710
|
+
|
|
576
711
|
|
|
577
712
|
class TestShapeConfig:
|
|
578
713
|
"""Test ShapeConfig class"""
|