tango-python 1.6.0__tar.gz → 1.8.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.8.0}/CHANGELOG.md +39 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/PKG-INFO +1 -1
- {tango_python-1.6.0 → tango_python-1.8.0}/docs/API_REFERENCE.md +200 -5
- {tango_python-1.6.0 → tango_python-1.8.0}/docs/WEBHOOKS.md +2 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/pyproject.toml +1 -1
- {tango_python-1.6.0 → tango_python-1.8.0}/scripts/check_filter_shape_conformance.py +2 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/scripts/check_shape_coverage.py +2 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/scripts/generate_shape_overlay.py +18 -3
- {tango_python-1.6.0 → tango_python-1.8.0}/tango/__init__.py +5 -1
- {tango_python-1.6.0 → tango_python-1.8.0}/tango/client.py +299 -9
- {tango_python-1.6.0 → tango_python-1.8.0}/tango/models.py +161 -6
- {tango_python-1.6.0 → tango_python-1.8.0}/tango/shapes/explicit_schemas.py +116 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tango/shapes/generated_overlay.py +95 -23
- tango_python-1.8.0/tests/test_contract_appeals.py +213 -0
- tango_python-1.8.0/tests/test_federal_register.py +233 -0
- tango_python-1.8.0/tests/test_opportunity_attachments.py +72 -0
- tango_python-1.8.0/tests/test_organization_scope.py +107 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/test_sled.py +61 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/.gitignore +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/LICENSE +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/README.md +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/docs/CLIENT.md +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/docs/DYNAMIC_MODELS.md +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/docs/ERRORS.md +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/docs/PAGINATION.md +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/docs/SHAPES.md +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/scripts/README.md +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/scripts/fetch_api_schema.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/scripts/generate_schemas_from_api.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/scripts/pr_review.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/scripts/probe_shape_types.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/scripts/refresh_contract.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/scripts/smoke_api_parity.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/scripts/test_production.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tango/exceptions.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tango/shapes/__init__.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tango/shapes/factory.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tango/shapes/generator.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tango/shapes/models.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tango/shapes/parser.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tango/shapes/schema.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tango/shapes/types.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tango/webhooks/__init__.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tango/webhooks/cli.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tango/webhooks/receiver.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tango/webhooks/signing.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tango/webhooks/simulate.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/__init__.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestAgenciesIntegration.test_get_agency +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestAgenciesIntegration.test_list_agencies +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_field_type_validation +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_parsing_consistency +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestBusinessTypesIntegration.test_list_business_types +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestContractsIntegration.test_combined_filters_work_together +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestContractsIntegration.test_contract_cursor_pagination +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestContractsIntegration.test_contract_data_object_parsing +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestContractsIntegration.test_contract_field_types +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[keyword-software] +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[psc_code-R425] +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_awarding_agency_filter +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_date_range_filter +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_flat +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_naics_code_filter +0 -0
- {tango_python-1.6.0 → tango_python-1.8.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.8.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[default-None] +0 -0
- {tango_python-1.6.0 → tango_python-1.8.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.8.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.8.0}/tests/cassettes/TestContractsIntegration.test_new_expiring_filters +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestContractsIntegration.test_new_fiscal_year_range_filters +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestContractsIntegration.test_new_identifier_filters +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestContractsIntegration.test_search_contracts_with_filters +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestContractsIntegration.test_search_filters_object_with_new_parameters +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[asc-] +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[desc--] +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_contracts +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_entities +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestEdgeCasesIntegration.test_date_field_parsing_edge_cases +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestEdgeCasesIntegration.test_decimal_field_parsing_edge_cases +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestEdgeCasesIntegration.test_empty_list_responses +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestEdgeCasesIntegration.test_entity_parsing_with_various_address_formats +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestEdgeCasesIntegration.test_flattened_responses_with_flat_lists +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestEdgeCasesIntegration.test_list_field_parsing_consistency +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_nested_objects_with_missing_data +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_null_missing_fields_in_contracts +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_with_minimal_shape_sparse_data +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestEntitiesIntegration.test_entity_field_types +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestEntitiesIntegration.test_entity_location_parsing +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestEntitiesIntegration.test_entity_parsing_with_business_types +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestEntitiesIntegration.test_entity_with_various_identifiers +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_budget_flows +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_by_uei +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_flat +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_search +0 -0
- {tango_python-1.6.0 → tango_python-1.8.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.8.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.8.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.8.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.8.0}/tests/cassettes/TestForecastsIntegration.test_forecast_field_types +0 -0
- {tango_python-1.6.0 → tango_python-1.8.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.8.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[default-None] +0 -0
- {tango_python-1.6.0 → tango_python-1.8.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.8.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.8.0}/tests/cassettes/TestGrantsIntegration.test_grant_field_types +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestGrantsIntegration.test_grant_pagination +0 -0
- {tango_python-1.6.0 → tango_python-1.8.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.8.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[default-None] +0 -0
- {tango_python-1.6.0 → tango_python-1.8.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.8.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.8.0}/tests/cassettes/TestIDVsIntegration.test_get_idv_uses_default_shape +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_awards_uses_default_shape +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_child_idvs_uses_default_shape +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_transactions +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestIDVsIntegration.test_list_idvs_uses_default_shape_and_keyset_params +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_code +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_name_text +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating_max +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_performance_risk +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_type_of_investment +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_updated_time_range +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestITDashboardIntegration.test_funding_and_cio_evaluation_expansions +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestITDashboardIntegration.test_get_itdashboard_investment_by_uii +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestITDashboardIntegration.test_itdashboard_pagination +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_search +0 -0
- {tango_python-1.6.0 → tango_python-1.8.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.8.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[default-None] +0 -0
- {tango_python-1.6.0 → tango_python-1.8.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.8.0}/tests/cassettes/TestNaicsIntegration.test_list_naics +0 -0
- {tango_python-1.6.0 → tango_python-1.8.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.8.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[default-None] +0 -0
- {tango_python-1.6.0 → tango_python-1.8.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.8.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.8.0}/tests/cassettes/TestNoticesIntegration.test_notice_field_types +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestNoticesIntegration.test_notice_pagination +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestNoticesIntegration.test_notice_with_meta_fields +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestOTAsIntegration.test_get_ota +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestOTAsIntegration.test_list_otas +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestOTIDVsIntegration.test_get_otidv +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestOTIDVsIntegration.test_list_otidvs +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestOfficesIntegration.test_get_office +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestOfficesIntegration.test_list_offices +0 -0
- {tango_python-1.6.0 → tango_python-1.8.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.8.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[default-None] +0 -0
- {tango_python-1.6.0 → tango_python-1.8.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.8.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.8.0}/tests/cassettes/TestOpportunitiesIntegration.test_opportunity_field_types +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestOrganizationsIntegration.test_get_organization +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestOrganizationsIntegration.test_list_organizations +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestProtestsIntegration.test_get_protest_by_case_id +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_filter +0 -0
- {tango_python-1.6.0 → tango_python-1.8.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.8.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[default-None] +0 -0
- {tango_python-1.6.0 → tango_python-1.8.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.8.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.8.0}/tests/cassettes/TestProtestsIntegration.test_protest_pagination +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestSubawardsIntegration.test_list_subawards +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[custom-key,piid,description] +0 -0
- {tango_python-1.6.0 → tango_python-1.8.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.8.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.8.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.8.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.8.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.8.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.8.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.8.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.8.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_with_metrics_expansion +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_orders_uses_default_shape +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_with_ordering +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/conftest.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/integration/README.md +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/integration/__init__.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/integration/conftest.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/integration/test_agencies_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/integration/test_contracts_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/integration/test_edge_cases_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/integration/test_entities_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/integration/test_forecasts_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/integration/test_grants_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/integration/test_itdashboard_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/integration/test_naics_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/integration/test_notices_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/integration/test_offices_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/integration/test_opportunities_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/integration/test_organizations_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/integration/test_otas_otidvs_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/integration/test_protests_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/integration/test_reference_data_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/integration/test_subawards_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/integration/test_vehicles_idvs_integration.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/integration/validation.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/production/__init__.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/production/conftest.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/production/test_production_smoke.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/test_api_parity.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/test_client.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/test_dibbs_exclusions_sbir.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/test_models.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/test_shape_coverage_gate.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/test_shapes.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/test_webhooks_cli.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/test_webhooks_receiver.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/test_webhooks_signing.py +0 -0
- {tango_python-1.6.0 → tango_python-1.8.0}/tests/test_webhooks_simulate.py +0 -0
|
@@ -7,6 +7,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.8.0] - 2026-09-28
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Federal Register documents** (Tango API 5.3.0). Two methods over the new `/api/federal_register/` resource: `list_federal_register_documents()` and `get_federal_register_document()`, plus a `FederalRegisterDocument` schema and the `FEDERAL_REGISTER_MINIMAL` / `FEDERAL_REGISTER_COMPREHENSIVE` defaults. These are the rules, proposed rules, notices and presidential documents published in the Federal Register since 1994. All seventeen of the API's filters are explicit named parameters, including `comments_open`, `cfr_title` / `cfr_part`, `rin`, and both `agency` (a Tango organization, including its sub-agencies) and `fr_agency` (the Federal Register's own agency slug).
|
|
14
|
+
|
|
15
|
+
`get_federal_register_document()` takes the document's `uuid`, not its `document_number`, because a document number is not unique on its own: the Federal Register reused some before 2016. Look a document up by number with `list_federal_register_documents(document_number=...)`. `full_text` is available on the detail method only when named in `shape`, and neither default shape includes it, since it can run to several MB. `ordering="rank"` requires a non-empty `search`, matching the API.
|
|
16
|
+
- **`alerts.federal_register.match` is an alertable event type.** `create_webhook_alert(query_type="federal_register", ...)` works.
|
|
17
|
+
- **`agency` filter on `list_budget_accounts()`, `list_exclusions()` and `list_itdashboard_investments()`** (Tango API 5.3.0). It takes a Tango agency name, abbreviation, code or organization key (e.g. `EPA`) and matches the whole organization subtree, so a department includes its sub-agencies; OR several with `|`. On exclusions it scopes by the excluding agency. On IT Dashboard it is available at every plan.
|
|
18
|
+
- **The `organization` expand is accepted on budget accounts, exclusions, SBIR topics and SBIR solicitations**, along with the `organization_id` field on budget accounts and exclusions. `organization(...)` carries `agency_code`, `agency_name`, `department_code`, `department_name`, `office_code`, `office_name` and `organization_id`. The SDK rejected these shapes client-side before. The default shapes are unchanged.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- **`list_itdashboard_investments()` documents the API's current filter tiers.** `agency_name` is available at every plan, not Business+ only.
|
|
22
|
+
|
|
23
|
+
## [1.7.0] - 2026-09-23
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- **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.
|
|
27
|
+
|
|
28
|
+
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.
|
|
29
|
+
|
|
30
|
+
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.
|
|
31
|
+
- **`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.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
- **`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()`.
|
|
35
|
+
- **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.
|
|
36
|
+
- **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.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
- **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.
|
|
40
|
+
- 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.
|
|
41
|
+
- 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.
|
|
42
|
+
|
|
43
|
+
### Documentation
|
|
44
|
+
- `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.
|
|
45
|
+
- `docs/API_REFERENCE.md` documents the document-role fields on opportunity and notice attachments, and `delisted_at` and `meta.jurisdiction_declared` on SLED solicitations.
|
|
46
|
+
- 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.
|
|
47
|
+
- Added `alerts.contract_appeal.match` to the event-type listing in `docs/WEBHOOKS.md`.
|
|
48
|
+
|
|
10
49
|
## [1.6.0] - 2026-09-10
|
|
11
50
|
|
|
12
51
|
### Added
|
|
@@ -22,6 +22,8 @@ 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)
|
|
26
|
+
- [Federal Register](#federal-register)
|
|
25
27
|
- [Budget](#budget)
|
|
26
28
|
- [Business Types](#business-types)
|
|
27
29
|
- [NAICS](#naics)
|
|
@@ -925,6 +927,25 @@ for opp in opportunities.results:
|
|
|
925
927
|
- `naics_code` - Industry code
|
|
926
928
|
- `psc_code` - Product/service code
|
|
927
929
|
|
|
930
|
+
**Document roles — `attachments(doc_role, doc_role_alt)`:**
|
|
931
|
+
|
|
932
|
+
On a **Pro plan or above**, each attachment can say what the document is for.
|
|
933
|
+
`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.
|
|
934
|
+
|
|
935
|
+
```python
|
|
936
|
+
opp = client.get_opportunity(
|
|
937
|
+
opportunity_id,
|
|
938
|
+
shape="opportunity_id,title,attachments(name,url,doc_role,doc_role_alt)",
|
|
939
|
+
)
|
|
940
|
+
for attachment in opp["attachments"]:
|
|
941
|
+
print(attachment["name"], attachment.get("doc_role"))
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
- **You have to name them.** `attachments(*)` does not include either field, and no default shape does.
|
|
945
|
+
- **The keys are absent, not null**, on an attachment Tango has not classified. Use `attachment.get("doc_role")`.
|
|
946
|
+
- A Free-plan request that names them gets the response without them, plus an entry in `meta.upgrade_hints`.
|
|
947
|
+
- There is no filter on document role.
|
|
948
|
+
|
|
928
949
|
---
|
|
929
950
|
|
|
930
951
|
## Notices
|
|
@@ -1143,7 +1164,7 @@ contract = client.get_gsa_elibrary_contract("UUID_HERE")
|
|
|
1143
1164
|
|
|
1144
1165
|
## Protests
|
|
1145
1166
|
|
|
1146
|
-
Bid protest records
|
|
1167
|
+
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
1168
|
|
|
1148
1169
|
### list_protests()
|
|
1149
1170
|
|
|
@@ -1166,20 +1187,22 @@ protests = client.list_protests(
|
|
|
1166
1187
|
filed_date_before=None,
|
|
1167
1188
|
decision_date_after=None,
|
|
1168
1189
|
decision_date_before=None,
|
|
1190
|
+
naics_code=None,
|
|
1169
1191
|
search=None,
|
|
1170
1192
|
)
|
|
1171
1193
|
```
|
|
1172
1194
|
|
|
1173
1195
|
**Filter Parameters:**
|
|
1174
|
-
- `source_system` - Filter by source system
|
|
1196
|
+
- `source_system` - Filter by source system: `"gao"`, `"cofc"`, or `"sba_oha"`
|
|
1175
1197
|
- `outcome` - Filter by outcome (e.g., `"Denied"`, `"Dismissed"`, `"Withdrawn"`, `"Sustained"`)
|
|
1176
1198
|
- `case_type` - Filter by case type
|
|
1177
1199
|
- `agency` - Filter by protested agency
|
|
1178
|
-
- `case_number` - Filter by case number (e.g., `"b-423274"`)
|
|
1200
|
+
- `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
1201
|
- `solicitation_number` - Filter by solicitation number
|
|
1180
1202
|
- `protester` - Search by protester name
|
|
1181
1203
|
- `filed_date_after` / `filed_date_before` - Filed date range
|
|
1182
1204
|
- `decision_date_after` / `decision_date_before` - Decision date range
|
|
1205
|
+
- `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
1206
|
- `search` - Full-text search
|
|
1184
1207
|
|
|
1185
1208
|
**Returns:** [PaginatedResponse](#paginatedresponse) with protest dictionaries
|
|
@@ -1214,6 +1237,172 @@ protest = client.get_protest(
|
|
|
1214
1237
|
|
|
1215
1238
|
---
|
|
1216
1239
|
|
|
1240
|
+
## Contract Appeals
|
|
1241
|
+
|
|
1242
|
+
Contract Disputes Act appeal decisions from the Civilian Board of Contract Appeals (CBCA) and the Armed Services Board of Contract Appeals (ASBCA).
|
|
1243
|
+
|
|
1244
|
+
**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.
|
|
1245
|
+
|
|
1246
|
+
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.
|
|
1247
|
+
|
|
1248
|
+
### list_contract_appeals()
|
|
1249
|
+
|
|
1250
|
+
List appeal decisions with filtering and shaping.
|
|
1251
|
+
|
|
1252
|
+
```python
|
|
1253
|
+
appeals = client.list_contract_appeals(
|
|
1254
|
+
page=1,
|
|
1255
|
+
limit=25,
|
|
1256
|
+
shape=ShapeConfig.CONTRACT_APPEALS_MINIMAL,
|
|
1257
|
+
# Filter parameters (all optional)
|
|
1258
|
+
board=None,
|
|
1259
|
+
docket=None,
|
|
1260
|
+
appellant=None,
|
|
1261
|
+
judge=None,
|
|
1262
|
+
decision_type=None,
|
|
1263
|
+
decision_date_after=None,
|
|
1264
|
+
decision_date_before=None,
|
|
1265
|
+
listed=None,
|
|
1266
|
+
document_id=None,
|
|
1267
|
+
search=None,
|
|
1268
|
+
ordering=None,
|
|
1269
|
+
)
|
|
1270
|
+
```
|
|
1271
|
+
|
|
1272
|
+
**Filter Parameters:**
|
|
1273
|
+
- `board` - `"cbca"` or `"asbca"`; OR both with `"cbca|asbca"`
|
|
1274
|
+
- `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 `|`
|
|
1275
|
+
- `appellant` - Case-insensitive substring match on the appellant's name (min 2 characters)
|
|
1276
|
+
- `judge` - Case-insensitive exact match on the judge as the listing names them
|
|
1277
|
+
- `decision_type` - CBCA only: `"Decision"`, `"Dismissal"`, `"Order"`, `"Full Board Order"`, or the listing's own text. ASBCA rows carry no type
|
|
1278
|
+
- `decision_date_after` / `decision_date_before` - Decision date range (`YYYY-MM-DD`)
|
|
1279
|
+
- `listed` - Whether the board's newest listing still carries the decision
|
|
1280
|
+
- `document_id` - The board's own document id
|
|
1281
|
+
- `search` - Ranked full-text search over the appellant and the full decision text (min 2 characters); wrap in double quotes for a phrase
|
|
1282
|
+
- `ordering` - `decision_date` (the default, as `-decision_date`), `appellant`, `first_listed_at` or `rank`. `rank` requires a non-empty `search`
|
|
1283
|
+
|
|
1284
|
+
**Returns:** [PaginatedResponse](#paginatedresponse) with appeal-decision dictionaries
|
|
1285
|
+
|
|
1286
|
+
**Example:**
|
|
1287
|
+
```python
|
|
1288
|
+
appeals = client.list_contract_appeals(
|
|
1289
|
+
board="asbca",
|
|
1290
|
+
search="differing site conditions",
|
|
1291
|
+
decision_date_after="2025-01-01",
|
|
1292
|
+
ordering="rank",
|
|
1293
|
+
limit=25,
|
|
1294
|
+
)
|
|
1295
|
+
|
|
1296
|
+
for appeal in appeals.results:
|
|
1297
|
+
dockets = ", ".join(appeal["docket_numbers"])
|
|
1298
|
+
print(f"{appeal['board'].upper()} {dockets}: {appeal['appellant']} ({appeal['decision_date']})")
|
|
1299
|
+
```
|
|
1300
|
+
|
|
1301
|
+
### get_contract_appeal()
|
|
1302
|
+
|
|
1303
|
+
Get a single decision by `uuid`.
|
|
1304
|
+
|
|
1305
|
+
```python
|
|
1306
|
+
appeal = client.get_contract_appeal(
|
|
1307
|
+
"DECISION_UUID",
|
|
1308
|
+
shape=ShapeConfig.CONTRACT_APPEALS_COMPREHENSIVE,
|
|
1309
|
+
)
|
|
1310
|
+
```
|
|
1311
|
+
|
|
1312
|
+
**Notes:**
|
|
1313
|
+
- `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.
|
|
1314
|
+
- **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.
|
|
1315
|
+
- **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.
|
|
1316
|
+
|
|
1317
|
+
---
|
|
1318
|
+
|
|
1319
|
+
## Federal Register
|
|
1320
|
+
|
|
1321
|
+
Federal Register documents — rules, proposed rules, notices and presidential documents published since 1994.
|
|
1322
|
+
|
|
1323
|
+
A document is identified by `uuid`. `document_number` is not unique on its own: the Federal Register reused some numbers before 2016, so `document_number=` can return more than one document.
|
|
1324
|
+
|
|
1325
|
+
### list_federal_register_documents()
|
|
1326
|
+
|
|
1327
|
+
List documents with filtering and shaping.
|
|
1328
|
+
|
|
1329
|
+
```python
|
|
1330
|
+
documents = client.list_federal_register_documents(
|
|
1331
|
+
page=1,
|
|
1332
|
+
limit=25,
|
|
1333
|
+
shape=ShapeConfig.FEDERAL_REGISTER_MINIMAL,
|
|
1334
|
+
# Filter parameters (all optional)
|
|
1335
|
+
search=None,
|
|
1336
|
+
document_number=None,
|
|
1337
|
+
type=None,
|
|
1338
|
+
agency=None,
|
|
1339
|
+
fr_agency=None,
|
|
1340
|
+
publication_date_after=None,
|
|
1341
|
+
publication_date_before=None,
|
|
1342
|
+
effective_on_after=None,
|
|
1343
|
+
effective_on_before=None,
|
|
1344
|
+
comments_close_on_after=None,
|
|
1345
|
+
comments_close_on_before=None,
|
|
1346
|
+
comments_open=None,
|
|
1347
|
+
cfr_title=None,
|
|
1348
|
+
cfr_part=None,
|
|
1349
|
+
significant=None,
|
|
1350
|
+
rin=None,
|
|
1351
|
+
executive_order_number=None,
|
|
1352
|
+
ordering=None,
|
|
1353
|
+
)
|
|
1354
|
+
```
|
|
1355
|
+
|
|
1356
|
+
**Filter Parameters:**
|
|
1357
|
+
- `search` - Ranked full-text search over the title, abstract and action; wrap in double quotes for a phrase
|
|
1358
|
+
- `document_number` - Exact FR document number, e.g. `"2016-31922"`; OR several with `|`
|
|
1359
|
+
- `type` - `"Notice"`, `"Rule"`, `"Proposed Rule"`, `"Presidential Document"`, `"Correction"`, `"Sunshine Act Document"` or `"Uncategorized Document"` (case-insensitive); OR several with `|`. An unknown type is an error, not an empty page. Documents published before 2008 are mostly `Uncategorized Document`, so a type filter undercounts that era
|
|
1360
|
+
- `agency` - A Tango agency name, abbreviation, code or organization key, e.g. `"EPA"`. Matches a document when any agency it lists falls within that organization, so a department includes its sub-agencies; OR several with `|`
|
|
1361
|
+
- `fr_agency` - The Federal Register's own agency slug, e.g. `"environmental-protection-agency"`; OR several with `|`
|
|
1362
|
+
- `publication_date_after` / `publication_date_before` - Publication date range (`YYYY-MM-DD`)
|
|
1363
|
+
- `effective_on_after` / `effective_on_before` - Effective date range (`YYYY-MM-DD`)
|
|
1364
|
+
- `comments_close_on_after` / `comments_close_on_before` - Comment-deadline range (`YYYY-MM-DD`)
|
|
1365
|
+
- `comments_open` - `True` for documents whose comment period closes today or later, `False` for those already closed; documents with no comment deadline match neither
|
|
1366
|
+
- `cfr_title` - A CFR title number, e.g. `"40"`
|
|
1367
|
+
- `cfr_part` - A CFR part number, e.g. `"52"`. Requires `cfr_title`, and both must match the same CFR reference, so `cfr_title="40", cfr_part="52"` finds 40 CFR 52
|
|
1368
|
+
- `significant` - Significant under Executive Order 12866
|
|
1369
|
+
- `rin` - A Regulation Identifier Number, e.g. `"2060-AV16"`; OR several with `|`
|
|
1370
|
+
- `executive_order_number` - Exact executive order number
|
|
1371
|
+
- `ordering` - `publication_date` (the default, as `-publication_date`), `effective_on`, `comments_close_on`, `document_number` or `rank`. `rank` requires a non-empty `search`
|
|
1372
|
+
|
|
1373
|
+
**Returns:** [PaginatedResponse](#paginatedresponse) with document dictionaries
|
|
1374
|
+
|
|
1375
|
+
**Example:**
|
|
1376
|
+
```python
|
|
1377
|
+
documents = client.list_federal_register_documents(
|
|
1378
|
+
type="Proposed Rule",
|
|
1379
|
+
comments_open=True,
|
|
1380
|
+
agency="EPA",
|
|
1381
|
+
limit=25,
|
|
1382
|
+
)
|
|
1383
|
+
|
|
1384
|
+
for doc in documents.results:
|
|
1385
|
+
print(f"{doc['document_number']} ({doc['publication_date']}): {doc['title']}")
|
|
1386
|
+
print(f" comments close {doc['comments_close_on']}")
|
|
1387
|
+
```
|
|
1388
|
+
|
|
1389
|
+
### get_federal_register_document()
|
|
1390
|
+
|
|
1391
|
+
Get a single document by `uuid`. To look one up by its document number, use `list_federal_register_documents(document_number=...)`.
|
|
1392
|
+
|
|
1393
|
+
```python
|
|
1394
|
+
document = client.get_federal_register_document(
|
|
1395
|
+
"DOCUMENT_UUID",
|
|
1396
|
+
shape=ShapeConfig.FEDERAL_REGISTER_COMPREHENSIVE,
|
|
1397
|
+
)
|
|
1398
|
+
```
|
|
1399
|
+
|
|
1400
|
+
**Notes:**
|
|
1401
|
+
- `agencies`, `cfr_references`, `dockets` and `topics` are the Federal Register's own structures, served as published. `agencies` carries Federal Register agency slugs, not Tango organization keys.
|
|
1402
|
+
- `full_text`, the document's plain text, is available on this method only and only when named in `shape` (e.g. `shape="uuid,title,full_text"`). It can run to several MB, so neither default shape includes it.
|
|
1403
|
+
|
|
1404
|
+
---
|
|
1405
|
+
|
|
1217
1406
|
## State & Local (SLED)
|
|
1218
1407
|
|
|
1219
1408
|
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 +1512,8 @@ row = client.get_sled_opportunity(
|
|
|
1323
1512
|
- `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
1513
|
- `size_bytes` and `char_count` only mean something as a pair — 3 MB that yielded no characters is a scan awaiting OCR.
|
|
1325
1514
|
- `raw(*)` needs a Small plan or above, and is explicitly unstable: its shape varies by portal platform.
|
|
1515
|
+
- `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.
|
|
1516
|
+
- `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
1517
|
|
|
1327
1518
|
**Reading a document body — `attachments(extracted_text)`:**
|
|
1328
1519
|
|
|
@@ -2206,12 +2397,16 @@ entity = client.get_entity("UEI_KEY", shape=ShapeConfig.ENTITIES_COMPREHENSIVE)
|
|
|
2206
2397
|
| `SUBAWARDS_MINIMAL` | `list_subawards` | award_key, prime_recipient(uei,display_name), subaward_recipient(uei,display_name) |
|
|
2207
2398
|
| `GSA_ELIBRARY_CONTRACTS_MINIMAL` | `list_gsa_elibrary_contracts` | uuid, contract_number, schedule, recipient(display_name,uei), idv(key,award_date) |
|
|
2208
2399
|
| `PROTESTS_MINIMAL` | `list_protests` | case_id, case_number, title, source_system, outcome, filed_date |
|
|
2400
|
+
| `CONTRACT_APPEALS_MINIMAL` | `list_contract_appeals` | uuid, board, docket_numbers, decision_date, appellant, judge, decision_type, url |
|
|
2401
|
+
| `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) |
|
|
2402
|
+
| `FEDERAL_REGISTER_MINIMAL` | `list_federal_register_documents` | uuid, document_number, publication_date, type, subtype, title, abstract, action, agencies, cfr_references, citation, significant, comments_close_on, effective_on, html_url, pdf_url |
|
|
2403
|
+
| `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`) |
|
|
2209
2404
|
| `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
2405
|
| `VEHICLE_ORDERS_MINIMAL` | `list_vehicle_orders` | key, piid, award_date, recipient(display_name,uei), total_contract_value, obligated |
|
|
2211
2406
|
| `ITDASHBOARD_INVESTMENTS_MINIMAL` | `list_itdashboard_investments` | Minimal IT Dashboard investment fields |
|
|
2212
2407
|
| `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 |
|
|
2408
|
+
| `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) |
|
|
2409
|
+
| `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
2410
|
| `SLED_REVISIONS_MINIMAL` | `list_sled_opportunity_revisions` | observed_at, sequence, kind, changed_fields, source_declared (omits `changes`, which needs a Small plan) |
|
|
2216
2411
|
| `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
2412
|
| `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,8 @@ 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
|
|
92
|
+
# alerts.federal_register.match Federal Register document matched a saved alert
|
|
91
93
|
```
|
|
92
94
|
|
|
93
95
|
This list is served by the API, so it is always current — the SDK does not hardcode
|
|
@@ -77,6 +77,8 @@ 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",
|
|
81
|
+
"federal_register": "list_federal_register_documents",
|
|
80
82
|
"offices": "list_offices",
|
|
81
83
|
"psc": "list_psc",
|
|
82
84
|
"mas_sins": "list_mas_sins",
|
|
@@ -68,6 +68,8 @@ RESOURCE_TO_MODEL: dict[str, str] = {
|
|
|
68
68
|
"budget/accounts": "BudgetAccount",
|
|
69
69
|
"budget_accounts": "BudgetAccount",
|
|
70
70
|
"protests": "Protest",
|
|
71
|
+
"contract_appeals": "ContractAppeal",
|
|
72
|
+
"federal_register": "FederalRegisterDocument",
|
|
71
73
|
"offices": "Office",
|
|
72
74
|
"assistance_listings": "AssistanceListing",
|
|
73
75
|
"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,10 +10,12 @@ from .exceptions import (
|
|
|
10
10
|
)
|
|
11
11
|
from .models import (
|
|
12
12
|
BudgetAccount,
|
|
13
|
+
ContractAppeal,
|
|
13
14
|
DibbsAward,
|
|
14
15
|
DibbsRfp,
|
|
15
16
|
DibbsRfq,
|
|
16
17
|
Exclusion,
|
|
18
|
+
FederalRegisterDocument,
|
|
17
19
|
GsaElibraryContract,
|
|
18
20
|
ITDashboardInvestment,
|
|
19
21
|
PaginatedResponse,
|
|
@@ -53,7 +55,7 @@ from .webhooks import (
|
|
|
53
55
|
)
|
|
54
56
|
from .webhooks.receiver import Delivery, WebhookReceiver
|
|
55
57
|
|
|
56
|
-
__version__ = "1.
|
|
58
|
+
__version__ = "1.8.0"
|
|
57
59
|
__all__ = [
|
|
58
60
|
"TangoClient",
|
|
59
61
|
"TangoAPIError",
|
|
@@ -65,6 +67,8 @@ __all__ = [
|
|
|
65
67
|
"ResolveCandidate",
|
|
66
68
|
"ResolveResult",
|
|
67
69
|
"BudgetAccount",
|
|
70
|
+
"ContractAppeal",
|
|
71
|
+
"FederalRegisterDocument",
|
|
68
72
|
"DibbsAward",
|
|
69
73
|
"DibbsRfp",
|
|
70
74
|
"DibbsRfq",
|