tango-python 1.6.0__tar.gz → 1.7.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.6.0 → tango_python-1.7.0}/CHANGELOG.md +26 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/PKG-INFO +1 -1
- {tango_python-1.6.0 → tango_python-1.7.0}/docs/API_REFERENCE.md +110 -5
- {tango_python-1.6.0 → tango_python-1.7.0}/docs/WEBHOOKS.md +1 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/pyproject.toml +1 -1
- {tango_python-1.6.0 → tango_python-1.7.0}/scripts/check_filter_shape_conformance.py +1 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/scripts/check_shape_coverage.py +1 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/scripts/generate_shape_overlay.py +18 -3
- {tango_python-1.6.0 → tango_python-1.7.0}/tango/__init__.py +3 -1
- {tango_python-1.6.0 → tango_python-1.7.0}/tango/client.py +143 -4
- {tango_python-1.6.0 → tango_python-1.7.0}/tango/models.py +84 -6
- {tango_python-1.6.0 → tango_python-1.7.0}/tango/shapes/explicit_schemas.py +38 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tango/shapes/generated_overlay.py +61 -23
- tango_python-1.7.0/tests/test_contract_appeals.py +213 -0
- tango_python-1.7.0/tests/test_opportunity_attachments.py +72 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/test_sled.py +61 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/.gitignore +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/LICENSE +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/README.md +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/docs/CLIENT.md +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/docs/DYNAMIC_MODELS.md +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/docs/ERRORS.md +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/docs/PAGINATION.md +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/docs/SHAPES.md +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/scripts/README.md +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/scripts/fetch_api_schema.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/scripts/generate_schemas_from_api.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/scripts/pr_review.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/scripts/probe_shape_types.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/scripts/refresh_contract.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/scripts/smoke_api_parity.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/scripts/test_production.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tango/exceptions.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tango/shapes/__init__.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tango/shapes/factory.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tango/shapes/generator.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tango/shapes/models.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tango/shapes/parser.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tango/shapes/schema.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tango/shapes/types.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tango/webhooks/__init__.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tango/webhooks/cli.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tango/webhooks/receiver.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tango/webhooks/signing.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tango/webhooks/simulate.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/__init__.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestAgenciesIntegration.test_get_agency +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestAgenciesIntegration.test_list_agencies +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_field_type_validation +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_parsing_consistency +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestBusinessTypesIntegration.test_list_business_types +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestContractsIntegration.test_combined_filters_work_together +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestContractsIntegration.test_contract_cursor_pagination +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestContractsIntegration.test_contract_data_object_parsing +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestContractsIntegration.test_contract_field_types +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[keyword-software] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[psc_code-R425] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_awarding_agency_filter +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_date_range_filter +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_flat +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_naics_code_filter +0 -0
- {tango_python-1.6.0 → tango_python-1.7.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.6.0 → tango_python-1.7.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[default-None] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.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.6.0 → tango_python-1.7.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.6.0 → tango_python-1.7.0}/tests/cassettes/TestContractsIntegration.test_new_expiring_filters +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestContractsIntegration.test_new_fiscal_year_range_filters +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestContractsIntegration.test_new_identifier_filters +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestContractsIntegration.test_search_contracts_with_filters +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestContractsIntegration.test_search_filters_object_with_new_parameters +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[asc-] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[desc--] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_contracts +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_entities +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestEdgeCasesIntegration.test_date_field_parsing_edge_cases +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestEdgeCasesIntegration.test_decimal_field_parsing_edge_cases +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestEdgeCasesIntegration.test_empty_list_responses +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestEdgeCasesIntegration.test_entity_parsing_with_various_address_formats +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestEdgeCasesIntegration.test_flattened_responses_with_flat_lists +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestEdgeCasesIntegration.test_list_field_parsing_consistency +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_nested_objects_with_missing_data +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_null_missing_fields_in_contracts +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_with_minimal_shape_sparse_data +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestEntitiesIntegration.test_entity_field_types +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestEntitiesIntegration.test_entity_location_parsing +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestEntitiesIntegration.test_entity_parsing_with_business_types +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestEntitiesIntegration.test_entity_with_various_identifiers +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_budget_flows +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_by_uei +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_flat +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_search +0 -0
- {tango_python-1.6.0 → tango_python-1.7.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.6.0 → tango_python-1.7.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[custom-uei,legal_business_name,cage_code] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.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.6.0 → tango_python-1.7.0}/tests/cassettes/TestForecastsIntegration.test_forecast_field_types +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[custom-id,title,anticipated_award_date] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[default-None] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.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.6.0 → tango_python-1.7.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.6.0 → tango_python-1.7.0}/tests/cassettes/TestGrantsIntegration.test_grant_field_types +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestGrantsIntegration.test_grant_pagination +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[custom-grant_id,title,opportunity_number] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[default-None] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.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.6.0 → tango_python-1.7.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[minimal-grant_id,opportunity_number,title,status(-),agency_code] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestIDVsIntegration.test_get_idv_uses_default_shape +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_awards_uses_default_shape +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_child_idvs_uses_default_shape +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_transactions +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestIDVsIntegration.test_list_idvs_uses_default_shape_and_keyset_params +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_code +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_name_text +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating_max +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_performance_risk +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_type_of_investment +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_updated_time_range +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestITDashboardIntegration.test_funding_and_cio_evaluation_expansions +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestITDashboardIntegration.test_get_itdashboard_investment_by_uii +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestITDashboardIntegration.test_itdashboard_pagination +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_search +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[custom-uii,agency_name,investment_title,updated_time] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[default-None] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.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.6.0 → tango_python-1.7.0}/tests/cassettes/TestNaicsIntegration.test_list_naics +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[custom-notice_id,title,solicitation_number] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[default-None] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.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.6.0 → tango_python-1.7.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestNoticesIntegration.test_notice_field_types +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestNoticesIntegration.test_notice_pagination +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestNoticesIntegration.test_notice_with_meta_fields +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestOTAsIntegration.test_get_ota +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestOTAsIntegration.test_list_otas +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestOTIDVsIntegration.test_get_otidv +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestOTIDVsIntegration.test_list_otidvs +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestOfficesIntegration.test_get_office +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestOfficesIntegration.test_list_offices +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[custom-opportunity_id,title,solicitation_number] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[default-None] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.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.6.0 → tango_python-1.7.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestOpportunitiesIntegration.test_opportunity_field_types +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestOrganizationsIntegration.test_get_organization +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestOrganizationsIntegration.test_list_organizations +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestProtestsIntegration.test_get_protest_by_case_id +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_filter +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[custom-case_id,title,source_system,outcome] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[default-None] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.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.6.0 → tango_python-1.7.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.6.0 → tango_python-1.7.0}/tests/cassettes/TestProtestsIntegration.test_protest_pagination +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestSubawardsIntegration.test_list_subawards +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[custom-key,piid,description] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.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.6.0 → tango_python-1.7.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[ultra_minimal-key,piid,recipient(display_name),total_contract_value] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.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.6.0 → tango_python-1.7.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.6.0 → tango_python-1.7.0}/tests/cassettes/TestTypeHintsIntegration.test_notices_dict_access[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.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.6.0 → tango_python-1.7.0}/tests/cassettes/TestTypeHintsIntegration.test_opportunities_dict_access[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_with_metrics_expansion +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_orders_uses_default_shape +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_with_ordering +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/conftest.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/integration/README.md +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/integration/__init__.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/integration/conftest.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/integration/test_agencies_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/integration/test_contracts_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/integration/test_edge_cases_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/integration/test_entities_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/integration/test_forecasts_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/integration/test_grants_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/integration/test_itdashboard_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/integration/test_naics_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/integration/test_notices_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/integration/test_offices_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/integration/test_opportunities_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/integration/test_organizations_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/integration/test_otas_otidvs_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/integration/test_protests_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/integration/test_reference_data_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/integration/test_subawards_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/integration/test_vehicles_idvs_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/integration/validation.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/production/__init__.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/production/conftest.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/production/test_production_smoke.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/test_api_parity.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/test_client.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/test_dibbs_exclusions_sbir.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/test_models.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/test_shape_coverage_gate.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/test_shapes.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/test_webhooks_cli.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/test_webhooks_receiver.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/test_webhooks_signing.py +0 -0
- {tango_python-1.6.0 → tango_python-1.7.0}/tests/test_webhooks_simulate.py +0 -0
|
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.7.0] - 2026-09-23
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Contract Disputes Act appeals** (Tango API 4.26.0). Two methods over the new `/api/contract_appeals/` resource: `list_contract_appeals()` and `get_contract_appeal()`, plus a `ContractAppeal` schema and the `CONTRACT_APPEALS_MINIMAL` / `CONTRACT_APPEALS_COMPREHENSIVE` defaults. These are decisions of the Civilian Board of Contract Appeals (CBCA) and the Armed Services Board of Contract Appeals (ASBCA) — appeals of a contracting officer's final decision under an existing contract, which is a different dispute from a bid protest and shares no identifiers with `list_protests()`. All ten of the API's filters are explicit named parameters.
|
|
14
|
+
|
|
15
|
+
Three behaviors are documented on the methods and the model, because each is otherwise a support question. **`decision_text` needs an Enterprise plan, and below that tier the key is absent rather than null** — read it with `.get()`. **Searching that text is free at every plan**: `search=` matches inside the decision body and returns no fragment of it, so buying the body does not change what you can find. And **`docket` accepts a docket as it is cited** — a leading `CBCA` / `ASBCA` and `No.` / `Nos.` are ignored, while commas stay part of a docket (`7092-C(6682, 6765, 6767)`) rather than separating two.
|
|
16
|
+
|
|
17
|
+
Neither default shape names `decision_text`: the body runs to roughly 100K characters, so putting it in a default would make every fetch ask for something most callers cannot read. `ordering="rank"` requires a non-empty `search`, matching the API.
|
|
18
|
+
- **`alerts.contract_appeal.match` is an alertable event type.** `create_webhook_alert(query_type="contract_appeal", ...)` works. That method's `query_type` docstring now names the full current set and points at `list_webhook_event_types()` as the authority, since a list written into a docstring can only go stale.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- **`attachments(doc_role, doc_role_alt)` on opportunities and notices no longer raises `ShapeValidationError`.** The Tango API serves both fields on a Pro plan or above, but the SDK rejected them client-side after the request had already been made. `doc_role` says what an attachment is for (`requirement`, `instructions`, `pricing`, `terms`, `reference` or `unknown`) and `doc_role_alt` is a runner-up role that is usually null. Both must be named in the shape, since `attachments(*)` does not include them, and an attachment that has not been classified omits both keys, so read them with `.get()`.
|
|
22
|
+
- **The shape overlay generator no longer lets one resource's view of a model hide fields from another's.** An opportunity is reachable both from `list_opportunities()` and as the `opportunity` expand on vehicles, and the vehicles embed exposes fewer attachment fields. Whichever tree was generated last replaced the other, so the opportunity schema lost fields its own endpoint serves. The generator now adds the missing fields instead of replacing the schema, and keeps every existing field's type as it was.
|
|
23
|
+
- **SLED solicitations accept `delisted_at` and `meta(jurisdiction_declared)`.** Both were rejected client-side even though the API returns them. `delisted_at` is when a portal dropped a solicitation from its listing before its deadline, which is what `status_reason="delisted"` means. `meta.jurisdiction_declared` says whether the source stated the jurisdiction level itself or Tango classified it from the issuer's name. `delisted_at` is now in both `SLED_OPPORTUNITIES_MINIMAL` and `SLED_OPPORTUNITIES_COMPREHENSIVE`, matching the API's own list default.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- **SLED response values now carry the types the API returns.** The SDK's SLED schema had guessed several types from field names. `posted_date`, `response_deadline`, `response_deadline_original` and `bid_opening_date` on `SledOpportunity` now parse to timezone-aware `datetime` values (before, `posted_date` and `bid_opening_date` were truncated to a `date` and the two deadlines stayed strings), as does `estimated_advertisement_date` on `SledForecast`. `estimated_value(min, max)` on forecasts now parses to `Decimal`. `category_codes` is typed as a list of `{scheme, code}` objects, `attachments` and `revisions` as lists, `meta(last_change_source_declared)` and `revisions(source_declared)` as booleans, and `attachments(size_bytes, pages)` and `revisions(sequence)` as integers. If you compared any of the date fields as strings, compare them as `datetime` values instead.
|
|
27
|
+
- Re-vendored `contracts/filter_shape_contract.json` (Tango API 5.1.0) and regenerated `tango/shapes/generated_overlay.py`. The one resource the SDK does not wrap yet, `ebuy/requests`, is recorded in `contracts/shape_coverage_baseline.json` as a known gap.
|
|
28
|
+
- The `ContractAppeal` shape schema is hand-curated in `tango/shapes/explicit_schemas.py` rather than left to the generated overlay. The generator falls back to name heuristics for a resource the live-API type probe has not sampled yet, and those heuristics get four of this resource's fields wrong: `docket_numbers` is a list, `listed` and `decision_date_repaired` are booleans, and `listing_year` is an integer.
|
|
29
|
+
|
|
30
|
+
### Documentation
|
|
31
|
+
- `list_protests()` and `docs/API_REFERENCE.md` now describe all three protest venues: GAO, the U.S. Court of Federal Claims (COFC) and the SBA Office of Hearings and Appeals (SBA OHA). `naics_code` is documented as the NAICS code at issue in an SBA OHA size or NAICS appeal, which GAO and COFC protests never carry, rather than the solicitation's code. `case_number` examples now cover each venue's format, and `naics_code` is listed among the filter parameters.
|
|
32
|
+
- `docs/API_REFERENCE.md` documents the document-role fields on opportunity and notice attachments, and `delisted_at` and `meta.jurisdiction_declared` on SLED solicitations.
|
|
33
|
+
- New **Contract Appeals** section in `docs/API_REFERENCE.md`, leading with what separates an appeal from a protest, plus the two new `ShapeConfig` constants in the shapes table.
|
|
34
|
+
- Added `alerts.contract_appeal.match` to the event-type listing in `docs/WEBHOOKS.md`.
|
|
35
|
+
|
|
10
36
|
## [1.6.0] - 2026-09-10
|
|
11
37
|
|
|
12
38
|
### Added
|
|
@@ -22,6 +22,7 @@ Complete reference for all Tango Python SDK methods and functionality.
|
|
|
22
22
|
- [Grants](#grants)
|
|
23
23
|
- [GSA eLibrary Contracts](#gsa-elibrary-contracts)
|
|
24
24
|
- [Protests](#protests)
|
|
25
|
+
- [Contract Appeals](#contract-appeals)
|
|
25
26
|
- [Budget](#budget)
|
|
26
27
|
- [Business Types](#business-types)
|
|
27
28
|
- [NAICS](#naics)
|
|
@@ -925,6 +926,25 @@ for opp in opportunities.results:
|
|
|
925
926
|
- `naics_code` - Industry code
|
|
926
927
|
- `psc_code` - Product/service code
|
|
927
928
|
|
|
929
|
+
**Document roles — `attachments(doc_role, doc_role_alt)`:**
|
|
930
|
+
|
|
931
|
+
On a **Pro plan or above**, each attachment can say what the document is for.
|
|
932
|
+
`doc_role` is one of `requirement`, `instructions`, `pricing`, `terms`, `reference` or `unknown`; `doc_role_alt` is a runner-up role, and is null on nearly every attachment.
|
|
933
|
+
|
|
934
|
+
```python
|
|
935
|
+
opp = client.get_opportunity(
|
|
936
|
+
opportunity_id,
|
|
937
|
+
shape="opportunity_id,title,attachments(name,url,doc_role,doc_role_alt)",
|
|
938
|
+
)
|
|
939
|
+
for attachment in opp["attachments"]:
|
|
940
|
+
print(attachment["name"], attachment.get("doc_role"))
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
- **You have to name them.** `attachments(*)` does not include either field, and no default shape does.
|
|
944
|
+
- **The keys are absent, not null**, on an attachment Tango has not classified. Use `attachment.get("doc_role")`.
|
|
945
|
+
- A Free-plan request that names them gets the response without them, plus an entry in `meta.upgrade_hints`.
|
|
946
|
+
- There is no filter on document role.
|
|
947
|
+
|
|
928
948
|
---
|
|
929
949
|
|
|
930
950
|
## Notices
|
|
@@ -1143,7 +1163,7 @@ contract = client.get_gsa_elibrary_contract("UUID_HERE")
|
|
|
1143
1163
|
|
|
1144
1164
|
## Protests
|
|
1145
1165
|
|
|
1146
|
-
Bid protest records
|
|
1166
|
+
Bid protest records from three venues: GAO, the U.S. Court of Federal Claims (COFC), and the SBA Office of Hearings and Appeals (SBA OHA).
|
|
1147
1167
|
|
|
1148
1168
|
### list_protests()
|
|
1149
1169
|
|
|
@@ -1166,20 +1186,22 @@ protests = client.list_protests(
|
|
|
1166
1186
|
filed_date_before=None,
|
|
1167
1187
|
decision_date_after=None,
|
|
1168
1188
|
decision_date_before=None,
|
|
1189
|
+
naics_code=None,
|
|
1169
1190
|
search=None,
|
|
1170
1191
|
)
|
|
1171
1192
|
```
|
|
1172
1193
|
|
|
1173
1194
|
**Filter Parameters:**
|
|
1174
|
-
- `source_system` - Filter by source system
|
|
1195
|
+
- `source_system` - Filter by source system: `"gao"`, `"cofc"`, or `"sba_oha"`
|
|
1175
1196
|
- `outcome` - Filter by outcome (e.g., `"Denied"`, `"Dismissed"`, `"Withdrawn"`, `"Sustained"`)
|
|
1176
1197
|
- `case_type` - Filter by case type
|
|
1177
1198
|
- `agency` - Filter by protested agency
|
|
1178
|
-
- `case_number` - Filter by case number (e.g., `"b-423274"`)
|
|
1199
|
+
- `case_number` - Filter by base case number, matched case-insensitively (e.g., `"b-423274"` for GAO, `"26-1391"` for COFC, `"SIZ-6100"` for SBA OHA)
|
|
1179
1200
|
- `solicitation_number` - Filter by solicitation number
|
|
1180
1201
|
- `protester` - Search by protester name
|
|
1181
1202
|
- `filed_date_after` / `filed_date_before` - Filed date range
|
|
1182
1203
|
- `decision_date_after` / `decision_date_before` - Decision date range
|
|
1204
|
+
- `naics_code` - The NAICS code at issue in an SBA OHA size or NAICS appeal (e.g., `"541519"`). GAO and COFC protests carry no NAICS code, so this filter returns SBA OHA records only
|
|
1183
1205
|
- `search` - Full-text search
|
|
1184
1206
|
|
|
1185
1207
|
**Returns:** [PaginatedResponse](#paginatedresponse) with protest dictionaries
|
|
@@ -1214,6 +1236,85 @@ protest = client.get_protest(
|
|
|
1214
1236
|
|
|
1215
1237
|
---
|
|
1216
1238
|
|
|
1239
|
+
## Contract Appeals
|
|
1240
|
+
|
|
1241
|
+
Contract Disputes Act appeal decisions from the Civilian Board of Contract Appeals (CBCA) and the Armed Services Board of Contract Appeals (ASBCA).
|
|
1242
|
+
|
|
1243
|
+
**These are not bid protests.** A protest challenges an award or a solicitation before performance; an appeal here challenges a contracting officer's final decision under an existing contract — a claim, a termination, a differing-site-conditions dispute. Protests live at [Protests](#protests) and share no identifiers with this resource.
|
|
1244
|
+
|
|
1245
|
+
One row is one decision as the board's own listing publishes it, so several fields describe the listing rather than the dispute. `listed` goes false once the board's newest listing stops carrying the decision; the row is kept, never dropped.
|
|
1246
|
+
|
|
1247
|
+
### list_contract_appeals()
|
|
1248
|
+
|
|
1249
|
+
List appeal decisions with filtering and shaping.
|
|
1250
|
+
|
|
1251
|
+
```python
|
|
1252
|
+
appeals = client.list_contract_appeals(
|
|
1253
|
+
page=1,
|
|
1254
|
+
limit=25,
|
|
1255
|
+
shape=ShapeConfig.CONTRACT_APPEALS_MINIMAL,
|
|
1256
|
+
# Filter parameters (all optional)
|
|
1257
|
+
board=None,
|
|
1258
|
+
docket=None,
|
|
1259
|
+
appellant=None,
|
|
1260
|
+
judge=None,
|
|
1261
|
+
decision_type=None,
|
|
1262
|
+
decision_date_after=None,
|
|
1263
|
+
decision_date_before=None,
|
|
1264
|
+
listed=None,
|
|
1265
|
+
document_id=None,
|
|
1266
|
+
search=None,
|
|
1267
|
+
ordering=None,
|
|
1268
|
+
)
|
|
1269
|
+
```
|
|
1270
|
+
|
|
1271
|
+
**Filter Parameters:**
|
|
1272
|
+
- `board` - `"cbca"` or `"asbca"`; OR both with `"cbca|asbca"`
|
|
1273
|
+
- `docket` - Exact docket match, e.g. `"3288-R"`, `"59116"` or `"7092-C(6682, 6765, 6767)"`. A leading `CBCA` / `ASBCA` and `No.` / `Nos.` are ignored, so a docket can be pasted as it is cited. Commas are part of a docket, never a separator; OR several with `|`
|
|
1274
|
+
- `appellant` - Case-insensitive substring match on the appellant's name (min 2 characters)
|
|
1275
|
+
- `judge` - Case-insensitive exact match on the judge as the listing names them
|
|
1276
|
+
- `decision_type` - CBCA only: `"Decision"`, `"Dismissal"`, `"Order"`, `"Full Board Order"`, or the listing's own text. ASBCA rows carry no type
|
|
1277
|
+
- `decision_date_after` / `decision_date_before` - Decision date range (`YYYY-MM-DD`)
|
|
1278
|
+
- `listed` - Whether the board's newest listing still carries the decision
|
|
1279
|
+
- `document_id` - The board's own document id
|
|
1280
|
+
- `search` - Ranked full-text search over the appellant and the full decision text (min 2 characters); wrap in double quotes for a phrase
|
|
1281
|
+
- `ordering` - `decision_date` (the default, as `-decision_date`), `appellant`, `first_listed_at` or `rank`. `rank` requires a non-empty `search`
|
|
1282
|
+
|
|
1283
|
+
**Returns:** [PaginatedResponse](#paginatedresponse) with appeal-decision dictionaries
|
|
1284
|
+
|
|
1285
|
+
**Example:**
|
|
1286
|
+
```python
|
|
1287
|
+
appeals = client.list_contract_appeals(
|
|
1288
|
+
board="asbca",
|
|
1289
|
+
search="differing site conditions",
|
|
1290
|
+
decision_date_after="2025-01-01",
|
|
1291
|
+
ordering="rank",
|
|
1292
|
+
limit=25,
|
|
1293
|
+
)
|
|
1294
|
+
|
|
1295
|
+
for appeal in appeals.results:
|
|
1296
|
+
dockets = ", ".join(appeal["docket_numbers"])
|
|
1297
|
+
print(f"{appeal['board'].upper()} {dockets}: {appeal['appellant']} ({appeal['decision_date']})")
|
|
1298
|
+
```
|
|
1299
|
+
|
|
1300
|
+
### get_contract_appeal()
|
|
1301
|
+
|
|
1302
|
+
Get a single decision by `uuid`.
|
|
1303
|
+
|
|
1304
|
+
```python
|
|
1305
|
+
appeal = client.get_contract_appeal(
|
|
1306
|
+
"DECISION_UUID",
|
|
1307
|
+
shape=ShapeConfig.CONTRACT_APPEALS_COMPREHENSIVE,
|
|
1308
|
+
)
|
|
1309
|
+
```
|
|
1310
|
+
|
|
1311
|
+
**Notes:**
|
|
1312
|
+
- `docket_numbers` is a list — the dockets with the board prefix stripped (`3288-R`, `59116`), and a consolidated appeal carries several. `docket_raw` keeps the listing's own text, and `docket_source` says where the dockets came from.
|
|
1313
|
+
- **Reading `decision_text` requires an Enterprise plan, and below that tier the key is absent rather than null** — read it with `.get()`, not `[...]`. Neither default shape names it: the body runs to roughly 100K characters, so asking for it on every row is rarely what you want.
|
|
1314
|
+
- **Searching that text is free at every plan.** `search=` matches inside the decision body and returns no fragment of it, so buying the body does not change what you can find.
|
|
1315
|
+
|
|
1316
|
+
---
|
|
1317
|
+
|
|
1217
1318
|
## State & Local (SLED)
|
|
1218
1319
|
|
|
1219
1320
|
State, local and education procurement — solicitations that never appear on SAM.gov because they were never federal. **Beta**: coverage is partial and grows one jurisdiction at a time.
|
|
@@ -1323,6 +1424,8 @@ row = client.get_sled_opportunity(
|
|
|
1323
1424
|
- `meta.attachment_count` can be **lower** than `len(row["attachments"])`. Some portals auto-generate a cover sheet alongside the real documents; it is listed and flagged `is_generated_summary`, but excluded from the count and from `has_documents`. The count answers "does this hold its solicitation package"; the array answers "what files exist".
|
|
1324
1425
|
- `size_bytes` and `char_count` only mean something as a pair — 3 MB that yielded no characters is a scan awaiting OCR.
|
|
1325
1426
|
- `raw(*)` needs a Small plan or above, and is explicitly unstable: its shape varies by portal platform.
|
|
1427
|
+
- `delisted_at` is when a portal dropped the solicitation from its listing before its deadline, and is what `status_reason="delisted"` means. It is null when the solicitation was never delisted or has been seen again since.
|
|
1428
|
+
- `meta.jurisdiction_declared` says whether the source stated the jurisdiction level itself. `False` means Tango classified it from the issuer's name, which is the common case on a state's central portal.
|
|
1326
1429
|
|
|
1327
1430
|
**Reading a document body — `attachments(extracted_text)`:**
|
|
1328
1431
|
|
|
@@ -2206,12 +2309,14 @@ entity = client.get_entity("UEI_KEY", shape=ShapeConfig.ENTITIES_COMPREHENSIVE)
|
|
|
2206
2309
|
| `SUBAWARDS_MINIMAL` | `list_subawards` | award_key, prime_recipient(uei,display_name), subaward_recipient(uei,display_name) |
|
|
2207
2310
|
| `GSA_ELIBRARY_CONTRACTS_MINIMAL` | `list_gsa_elibrary_contracts` | uuid, contract_number, schedule, recipient(display_name,uei), idv(key,award_date) |
|
|
2208
2311
|
| `PROTESTS_MINIMAL` | `list_protests` | case_id, case_number, title, source_system, outcome, filed_date |
|
|
2312
|
+
| `CONTRACT_APPEALS_MINIMAL` | `list_contract_appeals` | uuid, board, docket_numbers, decision_date, appellant, judge, decision_type, url |
|
|
2313
|
+
| `CONTRACT_APPEALS_COMPREHENSIVE` | `get_contract_appeal` | The list fields plus docket_raw, docket_source, decision_date_repaired, decision_type_raw, listing_year, first_listed_at, listed, text_status, text_char_count (omits `decision_text`, which needs an Enterprise plan) |
|
|
2209
2314
|
| `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 |
|
|
2210
2315
|
| `VEHICLE_ORDERS_MINIMAL` | `list_vehicle_orders` | key, piid, award_date, recipient(display_name,uei), total_contract_value, obligated |
|
|
2211
2316
|
| `ITDASHBOARD_INVESTMENTS_MINIMAL` | `list_itdashboard_investments` | Minimal IT Dashboard investment fields |
|
|
2212
2317
|
| `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 |
|
|
2213
|
-
| `SLED_OPPORTUNITIES_MINIMAL` | `list_sled_opportunities` | opportunity_id, solicitation_number, solicitation_type, title, state, jurisdiction, agency, status, status_reason, posted_date, response_deadline, source_url, has_documents, first_seen_at, last_change_seen_at (no `description` — detail-only on the API) |
|
|
2214
|
-
| `SLED_OPPORTUNITIES_COMPREHENSIVE` | `get_sled_opportunity` | Full solicitation with description, the raw portal status, both deadlines, bid opening, category codes, and organization / contact / meta / attachments / revisions |
|
|
2318
|
+
| `SLED_OPPORTUNITIES_MINIMAL` | `list_sled_opportunities` | opportunity_id, solicitation_number, solicitation_type, title, state, jurisdiction, agency, status, status_reason, delisted_at, posted_date, response_deadline, source_url, has_documents, first_seen_at, last_change_seen_at (no `description` — detail-only on the API) |
|
|
2319
|
+
| `SLED_OPPORTUNITIES_COMPREHENSIVE` | `get_sled_opportunity` | Full solicitation with description, the raw portal status, the delisting timestamp, both deadlines, bid opening, category codes, and organization / contact / meta / attachments / revisions |
|
|
2215
2320
|
| `SLED_REVISIONS_MINIMAL` | `list_sled_opportunity_revisions` | observed_at, sequence, kind, changed_fields, source_declared (omits `changes`, which needs a Small plan) |
|
|
2216
2321
|
| `SLED_FORECASTS_MINIMAL` | `list_sled_forecasts` | forecast_id, state, agency, title, estimated_advertisement_date, estimated_advertisement_raw, procurement_category, procurement_method, contract_number, incumbent_name, source_url, estimated_value(*) |
|
|
2217
2322
|
| `SLED_FORECASTS_COMPREHENSIVE` | `get_sled_forecast` | Full forecast with description, contract_term, mbe_dbe_goal, delivery_location, and organization / contact / estimated_value |
|
|
@@ -88,6 +88,7 @@ tango webhooks list-event-types
|
|
|
88
88
|
# alerts.dibbs_rfq.match DLA DIBBS RFQ matched a saved alert
|
|
89
89
|
# alerts.dibbs_rfp.match DLA DIBBS RFP matched a saved alert
|
|
90
90
|
# alerts.sled_opportunity.match State/local/education solicitation matched a saved alert
|
|
91
|
+
# alerts.contract_appeal.match CBCA/ASBCA appeal decision matched a saved alert
|
|
91
92
|
```
|
|
92
93
|
|
|
93
94
|
This list is served by the API, so it is always current — the SDK does not hardcode
|
|
@@ -77,6 +77,7 @@ RESOURCE_TO_METHOD: dict[str, str | None] = {
|
|
|
77
77
|
"sled/opportunities": "list_sled_opportunities",
|
|
78
78
|
"sled/forecasts": "list_sled_forecasts",
|
|
79
79
|
"protests": "list_protests",
|
|
80
|
+
"contract_appeals": "list_contract_appeals",
|
|
80
81
|
"offices": "list_offices",
|
|
81
82
|
"psc": "list_psc",
|
|
82
83
|
"mas_sins": "list_mas_sins",
|
|
@@ -68,6 +68,7 @@ RESOURCE_TO_MODEL: dict[str, str] = {
|
|
|
68
68
|
"budget/accounts": "BudgetAccount",
|
|
69
69
|
"budget_accounts": "BudgetAccount",
|
|
70
70
|
"protests": "Protest",
|
|
71
|
+
"contract_appeals": "ContractAppeal",
|
|
71
72
|
"offices": "Office",
|
|
72
73
|
"assistance_listings": "AssistanceListing",
|
|
73
74
|
"business_types": "BusinessType",
|
|
@@ -174,6 +174,20 @@ overlay: dict[str, dict[str, dict]] = {}
|
|
|
174
174
|
report_rows: list[str] = []
|
|
175
175
|
|
|
176
176
|
|
|
177
|
+
def put(container: str, name: str, e: dict) -> None:
|
|
178
|
+
"""Record an overlay entry, unioning nested schemas when two resources reach the same container.
|
|
179
|
+
|
|
180
|
+
A model can be reached from its own resource and as another resource's expand (`opportunities` vs `vehicles.opportunity`), and those trees can expose different sub-fields.
|
|
181
|
+
Overwriting let a narrower embed hide fields the resource itself serves; the union only adds the missing ones, and the later entry still decides every field both trees define, so no existing type moves.
|
|
182
|
+
"""
|
|
183
|
+
cur = overlay.setdefault(container, {}).get(name)
|
|
184
|
+
nested = {cur.get("nested") if cur else None, e.get("nested")}
|
|
185
|
+
if cur and None not in nested and "CodeDescription" not in nested and len(nested) == 2:
|
|
186
|
+
merged = {**nested_schemas[cur["nested"]], **nested_schemas[e["nested"]]}
|
|
187
|
+
e = {**e, "nested": intern_nested(re.sub(r"\d+$", "", e["nested"]), merged)}
|
|
188
|
+
overlay[container][name] = e
|
|
189
|
+
|
|
190
|
+
|
|
177
191
|
def walk(res: str, path: str, node: dict, schema, container: str) -> None:
|
|
178
192
|
if schema is None:
|
|
179
193
|
return
|
|
@@ -182,7 +196,7 @@ def walk(res: str, path: str, node: dict, schema, container: str) -> None:
|
|
|
182
196
|
for f in fields:
|
|
183
197
|
if f != "*" and f not in schema:
|
|
184
198
|
t, lst = resolve_scalar(res, path, f)
|
|
185
|
-
|
|
199
|
+
put(container, f, entry(t, True, lst))
|
|
186
200
|
report_rows.append(f"{res}:{path or '(root)'}.{f} -> {t}{'[]' if lst else ''}")
|
|
187
201
|
for ename, enode in (node.get("expands") or {}).items():
|
|
188
202
|
fs = schema.get(ename)
|
|
@@ -192,7 +206,7 @@ def walk(res: str, path: str, node: dict, schema, container: str) -> None:
|
|
|
192
206
|
if fs is None or child_schema is None:
|
|
193
207
|
if is_wildcard(enode) and fs is not None:
|
|
194
208
|
continue
|
|
195
|
-
|
|
209
|
+
put(container, ename, expand_entry(res, path, ename, enode))
|
|
196
210
|
report_rows.append(f"{res}:{path or '(root)'}.{ename} -> expand")
|
|
197
211
|
else:
|
|
198
212
|
walk(res, child_path, enode, child_schema, container=nested_name)
|
|
@@ -214,7 +228,8 @@ for rkey, r in contract["resources"].items():
|
|
|
214
228
|
for cname, cnode in (shape.get("expands") or {}).items():
|
|
215
229
|
s[cname] = expand_entry(rkey, "", cname, cnode)
|
|
216
230
|
if model_name:
|
|
217
|
-
|
|
231
|
+
for name, e in s.items():
|
|
232
|
+
put(model_name, name, e)
|
|
218
233
|
continue
|
|
219
234
|
walk(rkey, "", shape, schema, container=model_name)
|
|
220
235
|
|
|
@@ -10,6 +10,7 @@ from .exceptions import (
|
|
|
10
10
|
)
|
|
11
11
|
from .models import (
|
|
12
12
|
BudgetAccount,
|
|
13
|
+
ContractAppeal,
|
|
13
14
|
DibbsAward,
|
|
14
15
|
DibbsRfp,
|
|
15
16
|
DibbsRfq,
|
|
@@ -53,7 +54,7 @@ from .webhooks import (
|
|
|
53
54
|
)
|
|
54
55
|
from .webhooks.receiver import Delivery, WebhookReceiver
|
|
55
56
|
|
|
56
|
-
__version__ = "1.
|
|
57
|
+
__version__ = "1.7.0"
|
|
57
58
|
__all__ = [
|
|
58
59
|
"TangoClient",
|
|
59
60
|
"TangoAPIError",
|
|
@@ -65,6 +66,7 @@ __all__ = [
|
|
|
65
66
|
"ResolveCandidate",
|
|
66
67
|
"ResolveResult",
|
|
67
68
|
"BudgetAccount",
|
|
69
|
+
"ContractAppeal",
|
|
68
70
|
"DibbsAward",
|
|
69
71
|
"DibbsRfp",
|
|
70
72
|
"DibbsRfq",
|
|
@@ -24,6 +24,7 @@ from tango.models import (
|
|
|
24
24
|
BudgetAccount,
|
|
25
25
|
BusinessType,
|
|
26
26
|
Contract,
|
|
27
|
+
ContractAppeal,
|
|
27
28
|
DibbsAward,
|
|
28
29
|
DibbsRfp,
|
|
29
30
|
DibbsRfq,
|
|
@@ -2592,18 +2593,18 @@ class TangoClient:
|
|
|
2592
2593
|
shape: Response shape string (defaults to minimal shape)
|
|
2593
2594
|
flat: If True, flatten nested objects in shaped response
|
|
2594
2595
|
flat_lists: If True, flatten arrays using indexed keys
|
|
2595
|
-
source_system: Filter by source system
|
|
2596
|
+
source_system: Filter by source system: gao, cofc, or sba_oha
|
|
2596
2597
|
outcome: Filter by outcome (e.g. Denied, Dismissed, Withdrawn, Sustained)
|
|
2597
2598
|
case_type: Filter by case type
|
|
2598
2599
|
agency: Filter by protested agency text
|
|
2599
|
-
case_number: Filter by case number (e.g. b-423274)
|
|
2600
|
+
case_number: Filter by base case number, matched case-insensitively (e.g. b-423274 for GAO, 26-1391 for COFC, SIZ-6100 for SBA OHA)
|
|
2600
2601
|
solicitation_number: Filter by exact solicitation number
|
|
2601
2602
|
protester: Filter by protester name text
|
|
2602
2603
|
filed_date_after: Filed date on or after
|
|
2603
2604
|
filed_date_before: Filed date on or before
|
|
2604
2605
|
decision_date_after: Decision date on or after
|
|
2605
2606
|
decision_date_before: Decision date on or before
|
|
2606
|
-
naics_code: Filter by the
|
|
2607
|
+
naics_code: Filter by the NAICS code at issue in an SBA OHA size or NAICS appeal (e.g. 541519); GAO and COFC protests carry no NAICS code and never match
|
|
2607
2608
|
search: Full-text search over protest searchable fields
|
|
2608
2609
|
"""
|
|
2609
2610
|
params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
|
|
@@ -2682,6 +2683,141 @@ class TangoClient:
|
|
|
2682
2683
|
data = self._get(f"/api/protests/{case_id}/", params)
|
|
2683
2684
|
return self._parse_response_with_shape(data, shape, Protest, flat, flat_lists)
|
|
2684
2685
|
|
|
2686
|
+
# ============================================================================
|
|
2687
|
+
# Contract Disputes Act appeals (CBCA and ASBCA)
|
|
2688
|
+
# ============================================================================
|
|
2689
|
+
|
|
2690
|
+
def list_contract_appeals(
|
|
2691
|
+
self,
|
|
2692
|
+
page: int = 1,
|
|
2693
|
+
limit: int = 25,
|
|
2694
|
+
shape: str | None = None,
|
|
2695
|
+
flat: bool = False,
|
|
2696
|
+
flat_lists: bool = False,
|
|
2697
|
+
board: str | None = None,
|
|
2698
|
+
docket: str | None = None,
|
|
2699
|
+
appellant: str | None = None,
|
|
2700
|
+
judge: str | None = None,
|
|
2701
|
+
decision_type: str | None = None,
|
|
2702
|
+
decision_date_after: str | None = None,
|
|
2703
|
+
decision_date_before: str | None = None,
|
|
2704
|
+
listed: bool | None = None,
|
|
2705
|
+
document_id: str | None = None,
|
|
2706
|
+
search: str | None = None,
|
|
2707
|
+
ordering: str | None = None,
|
|
2708
|
+
) -> PaginatedResponse:
|
|
2709
|
+
"""
|
|
2710
|
+
List Contract Disputes Act appeal decisions.
|
|
2711
|
+
|
|
2712
|
+
Decisions of the Civilian Board of Contract Appeals (CBCA) and the Armed
|
|
2713
|
+
Services Board of Contract Appeals (ASBCA) — appeals of a contracting
|
|
2714
|
+
officer's decision, which is a different dispute from a bid protest. For
|
|
2715
|
+
protests, use :meth:`list_protests`.
|
|
2716
|
+
|
|
2717
|
+
Args:
|
|
2718
|
+
page: Page number
|
|
2719
|
+
limit: Results per page (max 100)
|
|
2720
|
+
shape: Response shape string (defaults to minimal shape)
|
|
2721
|
+
flat: If True, flatten nested objects in shaped response
|
|
2722
|
+
flat_lists: If True, flatten arrays using indexed keys
|
|
2723
|
+
board: ``cbca`` or ``asbca``. Multi-value: ``cbca|asbca``
|
|
2724
|
+
docket: Exact docket match, e.g. ``3288-R``, ``59116`` or
|
|
2725
|
+
``7092-C(6682, 6765, 6767)``. A leading ``CBCA`` / ``ASBCA`` and
|
|
2726
|
+
``No.`` / ``Nos.`` are ignored, so a docket can be pasted as it is
|
|
2727
|
+
cited. Commas are part of a docket, never a separator; OR several
|
|
2728
|
+
with ``|``
|
|
2729
|
+
appellant: Case-insensitive substring match on the appellant's name
|
|
2730
|
+
(min 2 characters)
|
|
2731
|
+
judge: Case-insensitive exact match on the judge as the listing names
|
|
2732
|
+
them
|
|
2733
|
+
decision_type: CBCA only — ``Decision``, ``Dismissal``, ``Order``,
|
|
2734
|
+
``Full Board Order``, or the listing's own text where it matches
|
|
2735
|
+
none of those. ASBCA rows carry no type
|
|
2736
|
+
decision_date_after: Decided on or after (YYYY-MM-DD)
|
|
2737
|
+
decision_date_before: Decided on or before (YYYY-MM-DD)
|
|
2738
|
+
listed: Whether the board's newest listing still carries the decision.
|
|
2739
|
+
``False`` returns the ones it has dropped, which are kept here
|
|
2740
|
+
rather than deleted
|
|
2741
|
+
document_id: The board's own document id
|
|
2742
|
+
search: Ranked full-text search over the appellant and the full
|
|
2743
|
+
decision text (min 2 characters). Wrap in double quotes for a
|
|
2744
|
+
phrase. Free at every plan, and it returns no fragment of the
|
|
2745
|
+
text — only reading ``decision_text`` needs Enterprise
|
|
2746
|
+
ordering: Sort field — ``decision_date`` (the default, as
|
|
2747
|
+
``-decision_date``), ``appellant``, ``first_listed_at`` or
|
|
2748
|
+
``rank``. ``rank`` requires a non-empty ``search``
|
|
2749
|
+
"""
|
|
2750
|
+
params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
|
|
2751
|
+
|
|
2752
|
+
if shape is None:
|
|
2753
|
+
shape = ShapeConfig.CONTRACT_APPEALS_MINIMAL
|
|
2754
|
+
if shape:
|
|
2755
|
+
params["shape"] = shape
|
|
2756
|
+
if flat:
|
|
2757
|
+
params["flat"] = "true"
|
|
2758
|
+
if flat_lists:
|
|
2759
|
+
params["flat_lists"] = "true"
|
|
2760
|
+
|
|
2761
|
+
for key, val in (
|
|
2762
|
+
("board", board),
|
|
2763
|
+
("docket", docket),
|
|
2764
|
+
("appellant", appellant),
|
|
2765
|
+
("judge", judge),
|
|
2766
|
+
("decision_type", decision_type),
|
|
2767
|
+
("decision_date_after", decision_date_after),
|
|
2768
|
+
("decision_date_before", decision_date_before),
|
|
2769
|
+
("listed", listed),
|
|
2770
|
+
("document_id", document_id),
|
|
2771
|
+
("search", search),
|
|
2772
|
+
("ordering", ordering),
|
|
2773
|
+
):
|
|
2774
|
+
if val is not None:
|
|
2775
|
+
params[key] = val
|
|
2776
|
+
|
|
2777
|
+
data = self._get("/api/contract_appeals/", params)
|
|
2778
|
+
|
|
2779
|
+
results = [
|
|
2780
|
+
self._parse_response_with_shape(item, shape, ContractAppeal, flat, flat_lists)
|
|
2781
|
+
for item in data["results"]
|
|
2782
|
+
]
|
|
2783
|
+
|
|
2784
|
+
return PaginatedResponse(
|
|
2785
|
+
count=data["count"],
|
|
2786
|
+
next=data.get("next"),
|
|
2787
|
+
previous=data.get("previous"),
|
|
2788
|
+
results=results,
|
|
2789
|
+
meta=data.get("meta"),
|
|
2790
|
+
)
|
|
2791
|
+
|
|
2792
|
+
def get_contract_appeal(
|
|
2793
|
+
self,
|
|
2794
|
+
appeal_id: str,
|
|
2795
|
+
shape: str | None = None,
|
|
2796
|
+
flat: bool = False,
|
|
2797
|
+
flat_lists: bool = False,
|
|
2798
|
+
) -> Any:
|
|
2799
|
+
"""
|
|
2800
|
+
Get a single Contract Disputes Act appeal decision by uuid.
|
|
2801
|
+
|
|
2802
|
+
Args:
|
|
2803
|
+
appeal_id: Decision UUID
|
|
2804
|
+
shape: Response shape string (defaults to the comprehensive shape)
|
|
2805
|
+
flat: If True, flatten nested objects in shaped response
|
|
2806
|
+
flat_lists: If True, flatten arrays using indexed keys
|
|
2807
|
+
"""
|
|
2808
|
+
params: dict[str, Any] = {}
|
|
2809
|
+
if shape is None:
|
|
2810
|
+
shape = ShapeConfig.CONTRACT_APPEALS_COMPREHENSIVE
|
|
2811
|
+
if shape:
|
|
2812
|
+
params["shape"] = shape
|
|
2813
|
+
if flat:
|
|
2814
|
+
params["flat"] = "true"
|
|
2815
|
+
if flat_lists:
|
|
2816
|
+
params["flat_lists"] = "true"
|
|
2817
|
+
|
|
2818
|
+
data = self._get(f"/api/contract_appeals/{appeal_id}/", params)
|
|
2819
|
+
return self._parse_response_with_shape(data, shape, ContractAppeal, flat, flat_lists)
|
|
2820
|
+
|
|
2685
2821
|
# ============================================================================
|
|
2686
2822
|
# DLA DIBBS (RFQs, RFPs, awards)
|
|
2687
2823
|
# ============================================================================
|
|
@@ -4733,7 +4869,10 @@ class TangoClient:
|
|
|
4733
4869
|
Args:
|
|
4734
4870
|
name: Human-readable name for this alert.
|
|
4735
4871
|
query_type: One of ``opportunity``, ``contract``, ``idv``, ``ota``,
|
|
4736
|
-
``otidv``, ``entity``, ``grant``, ``forecast
|
|
4872
|
+
``otidv``, ``entity``, ``grant``, ``forecast``, ``exclusion``,
|
|
4873
|
+
``dibbs_rfq``, ``dibbs_rfp``, ``sled_opportunity`` or
|
|
4874
|
+
``contract_appeal``. :meth:`list_webhook_event_types` is the
|
|
4875
|
+
current authority — this list can only go stale.
|
|
4737
4876
|
filters: Dict of query parameters that the alert matches against
|
|
4738
4877
|
(e.g. ``{"naics": "541330", "set_aside": "SBA"}``).
|
|
4739
4878
|
frequency: ``realtime`` | ``daily`` | ``weekly`` | ``custom``.
|