tango-python 0.4.4__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tango_python-0.6.0/.claude-plugin/plugin-pointer.json +5 -0
- tango_python-0.6.0/.mg-tools/DIARY.md +5 -0
- tango_python-0.6.0/.mg-tools/config.json +8 -0
- tango_python-0.6.0/.mg-tools/version +1 -0
- tango_python-0.6.0/CHANGELOG.md +116 -0
- tango_python-0.6.0/CLAUDE.md +130 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/PKG-INFO +50 -2
- {tango_python-0.4.4 → tango_python-0.6.0}/README.md +47 -1
- {tango_python-0.4.4 → tango_python-0.6.0}/docs/API_REFERENCE.md +150 -11
- {tango_python-0.4.4 → tango_python-0.6.0}/docs/SHAPES.md +3 -1
- tango_python-0.6.0/docs/WEBHOOKS.md +430 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/pyproject.toml +11 -1
- {tango_python-0.4.4 → tango_python-0.6.0}/scripts/check_filter_shape_conformance.py +12 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tango/__init__.py +18 -1
- {tango_python-0.4.4 → tango_python-0.6.0}/tango/client.py +269 -4
- {tango_python-0.4.4 → tango_python-0.6.0}/tango/models.py +97 -6
- {tango_python-0.4.4 → tango_python-0.6.0}/tango/shapes/explicit_schemas.py +187 -0
- tango_python-0.6.0/tango/webhooks/__init__.py +27 -0
- tango_python-0.6.0/tango/webhooks/cli.py +519 -0
- tango_python-0.6.0/tango/webhooks/receiver.py +232 -0
- tango_python-0.6.0/tango/webhooks/signing.py +50 -0
- tango_python-0.6.0/tango/webhooks/simulate.py +102 -0
- tango_python-0.6.0/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_code +95 -0
- tango_python-0.6.0/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_name_text +93 -0
- tango_python-0.6.0/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating +94 -0
- tango_python-0.6.0/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating_max +93 -0
- tango_python-0.6.0/tests/cassettes/TestITDashboardIntegration.test_filter_by_performance_risk +94 -0
- tango_python-0.6.0/tests/cassettes/TestITDashboardIntegration.test_filter_by_type_of_investment +94 -0
- tango_python-0.6.0/tests/cassettes/TestITDashboardIntegration.test_filter_by_updated_time_range +93 -0
- tango_python-0.6.0/tests/cassettes/TestITDashboardIntegration.test_funding_and_cio_evaluation_expansions +88 -0
- tango_python-0.6.0/tests/cassettes/TestITDashboardIntegration.test_get_itdashboard_investment_by_uii +314 -0
- tango_python-0.6.0/tests/cassettes/TestITDashboardIntegration.test_itdashboard_pagination +364 -0
- tango_python-0.6.0/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_search +182 -0
- tango_python-0.6.0/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[custom-uii,agency_name,investment_title,updated_time] +164 -0
- tango_python-0.6.0/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[default-None] +184 -0
- tango_python-0.6.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] +184 -0
- tango_python-0.6.0/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists +161 -0
- tango_python-0.6.0/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_with_metrics_expansion +158 -0
- tango_python-0.6.0/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape +161 -0
- tango_python-0.6.0/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_orders_uses_default_shape +184 -0
- tango_python-0.6.0/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search +150 -0
- tango_python-0.6.0/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_with_ordering +78 -0
- tango_python-0.6.0/tests/integration/test_itdashboard_integration.py +290 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/integration/test_vehicles_idvs_integration.py +156 -11
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/production/test_production_smoke.py +19 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/test_client.py +138 -0
- tango_python-0.6.0/tests/test_webhooks_cli.py +348 -0
- tango_python-0.6.0/tests/test_webhooks_receiver.py +127 -0
- tango_python-0.6.0/tests/test_webhooks_signing.py +73 -0
- tango_python-0.6.0/tests/test_webhooks_simulate.py +62 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/uv.lock +18 -2
- tango_python-0.4.4/CHANGELOG.md +0 -73
- tango_python-0.4.4/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists +0 -152
- tango_python-0.4.4/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape +0 -169
- tango_python-0.4.4/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search +0 -86
- {tango_python-0.4.4 → tango_python-0.6.0}/.env.example +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/.github/workflows/lint.yml +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/.github/workflows/publish.yml +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/.github/workflows/test.yml +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/.gitignore +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/LICENSE +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/ROADMAP.md +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/docs/DEVELOPERS.md +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/docs/quick_start.ipynb +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/scripts/README.md +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/scripts/fetch_api_schema.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/scripts/generate_schemas_from_api.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/scripts/pr_review.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/scripts/test_production.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tango/exceptions.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tango/shapes/__init__.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tango/shapes/factory.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tango/shapes/generator.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tango/shapes/models.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tango/shapes/parser.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tango/shapes/schema.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tango/shapes/types.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/__init__.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestAgenciesIntegration.test_get_agency +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestAgenciesIntegration.test_list_agencies +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_field_type_validation +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_parsing_consistency +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestBusinessTypesIntegration.test_list_business_types +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_combined_filters_work_together +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_contract_cursor_pagination +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_contract_data_object_parsing +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_contract_field_types +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[keyword-software] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[psc_code-R425] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_awarding_agency_filter +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_date_range_filter +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_flat +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_naics_code_filter +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[custom-key,piid,recipient(display_name),total_contract_value,award_date] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[default-None] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[detailed-key,piid,award_date,description,total_contract_value,obligated,fiscal_year,set_aside,recipient(display_name,uei),awarding_office(-),place_of_performa...ce114a3c47e2037aaa3c15d00b7031bd +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[minimal-key,piid,award_date,recipient(display_name),description,total_contract_value] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_new_expiring_filters +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_new_fiscal_year_range_filters +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_new_identifier_filters +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_search_contracts_with_filters +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_search_filters_object_with_new_parameters +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[asc-] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[desc--] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_contracts +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_entities +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_date_field_parsing_edge_cases +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_decimal_field_parsing_edge_cases +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_empty_list_responses +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_entity_parsing_with_various_address_formats +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_flattened_responses_with_flat_lists +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_list_field_parsing_consistency +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_nested_objects_with_missing_data +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_null_missing_fields_in_contracts +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_with_minimal_shape_sparse_data +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestEntitiesIntegration.test_entity_field_types +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestEntitiesIntegration.test_entity_location_parsing +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestEntitiesIntegration.test_entity_parsing_with_business_types +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestEntitiesIntegration.test_entity_with_various_identifiers +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_by_uei +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_flat +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_search +0 -0
- {tango_python-0.4.4 → tango_python-0.6.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-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[custom-uei,legal_business_name,cage_code] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[with_address-uei,legal_business_name,cage_code,business_types,physical_address] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestForecastsIntegration.test_forecast_field_types +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[custom-id,title,anticipated_award_date] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[default-None] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[detailed-id,source_system,external_id,title,description,anticipated_award_date,fiscal_year,naics_code,status,is_active] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[minimal-id,title,anticipated_award_date,fiscal_year,naics_code,status] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestGrantsIntegration.test_grant_field_types +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestGrantsIntegration.test_grant_pagination +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[custom-grant_id,title,opportunity_number] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[default-None] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[detailed-grant_id,opportunity_number,title,status(-),agency_code,description,last_updated,cfda_numbers(number,title),applicant_types(-),funding_categories(-)] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[minimal-grant_id,opportunity_number,title,status(-),agency_code] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestIDVsIntegration.test_get_idv_uses_default_shape +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_awards_uses_default_shape +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_child_idvs_uses_default_shape +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_transactions +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestIDVsIntegration.test_list_idvs_uses_default_shape_and_keyset_params +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestNaicsIntegration.test_list_naics +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[custom-notice_id,title,solicitation_number] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[default-None] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[detailed-notice_id,title,description,solicitation_number,posted_date,naics_code,set_aside,office(-),place_of_performance(-)] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestNoticesIntegration.test_notice_field_types +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestNoticesIntegration.test_notice_pagination +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestNoticesIntegration.test_notice_with_meta_fields +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestOTAsIntegration.test_get_ota +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestOTAsIntegration.test_list_otas +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestOTIDVsIntegration.test_get_otidv +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestOTIDVsIntegration.test_list_otidvs +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestOfficesIntegration.test_get_office +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestOfficesIntegration.test_list_offices +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[custom-opportunity_id,title,solicitation_number] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[default-None] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[detailed-opportunity_id,title,description,solicitation_number,response_deadline,first_notice_date,last_notice_date,active,naics_code,psc_code,set_asid...23b6b4502ddd665b7184afcff6c6d8d9 +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestOpportunitiesIntegration.test_opportunity_field_types +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestOrganizationsIntegration.test_get_organization +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestOrganizationsIntegration.test_list_organizations +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestProtestsIntegration.test_get_protest_by_case_id +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_filter +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[custom-case_id,title,source_system,outcome] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[default-None] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[minimal-case_id,case_number,title,source_system,outcome,filed_date] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.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-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestProtestsIntegration.test_protest_pagination +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestSubawardsIntegration.test_list_subawards +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[custom-key,piid,description] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[minimal-key,piid,award_date,recipient(display_name),description,total_contract_value] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[ultra_minimal-key,piid,recipient(display_name),total_contract_value] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[with_address-uei,legal_business_name,cage_code,business_types,physical_address] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestTypeHintsIntegration.test_notices_dict_access[detailed-notice_id,title,description,solicitation_number,posted_date,naics_code,set_aside,office(-),place_of_performance(-)] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestTypeHintsIntegration.test_notices_dict_access[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestTypeHintsIntegration.test_opportunities_dict_access[detailed-opportunity_id,title,description,solicitation_number,response_deadline,first_notice_date,last_notice_date,active,naics_code,psc_code,set_aside,sam_url,office(-),place_of_performance(-)] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/cassettes/TestTypeHintsIntegration.test_opportunities_dict_access[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/conftest.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/integration/README.md +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/integration/__init__.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/integration/conftest.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/integration/test_agencies_integration.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/integration/test_contracts_integration.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/integration/test_edge_cases_integration.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/integration/test_entities_integration.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/integration/test_forecasts_integration.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/integration/test_grants_integration.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/integration/test_naics_integration.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/integration/test_notices_integration.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/integration/test_offices_integration.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/integration/test_opportunities_integration.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/integration/test_organizations_integration.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/integration/test_otas_otidvs_integration.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/integration/test_protests_integration.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/integration/test_reference_data_integration.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/integration/test_subawards_integration.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/integration/validation.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/production/__init__.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/production/conftest.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/test_models.py +0 -0
- {tango_python-0.4.4 → tango_python-0.6.0}/tests/test_shapes.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.0.3 2026-04-29T12:42:30Z /Users/vdavez/Sync/makegov/mg-tools
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.6.0] - 2026-05-07
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Vehicles: new top-level fields `program_acronym`, `idv_count`, `total_obligated`, `is_synthetic_solicitation`, `latest_award_date`, `description`, `opportunity_id`.
|
|
12
|
+
- Vehicles: new `metrics(*)` shape expansion bundling 12 computed metrics: `avg_offers_received`, `award_concentration_hhi`, `order_concentration_hhi`, `competed_rate`, `using_agency_count`, `avg_order_value`, `max_order_value`, `top_recipient_share`, `recent_obligations_24mo`, `recent_orders_24mo`, `days_since_last_order`, `obligation_to_ceiling_ratio`. Backed by a new `VehicleMetrics` schema.
|
|
13
|
+
- `list_vehicle_orders(uuid, ...)` for the new `/api/vehicles/{uuid}/orders/` endpoint, returning task orders under the vehicle's IDVs with two-phase pagination.
|
|
14
|
+
- `list_vehicles` gained 21 explicit filter parameters per API 4.3.0: `vehicle_type`, `type_of_idc`, `contract_type`, `set_aside` (multi-value via `|`), `who_can_use`, `naics_code`, `psc_code`, `program_acronym`, `agency`, `organization_id`, `total_obligated_min`/`max`, `idv_count_min`/`max`, `order_count_min`/`max`, `fiscal_year`, `award_date_after`/`before`, `last_date_to_order_after`/`before`.
|
|
15
|
+
- `list_vehicle_awardees` gained a `search` parameter for entity-aware full-text search across IDV fields and recipient entity details (API 4.3.0).
|
|
16
|
+
- `ordering` parameter on `list_vehicles` (whitelist: `vehicle_obligations`, `latest_award_date`, `total_obligated`, `award_date`, `last_date_to_order`, `fiscal_year`, `idv_count`, `order_count`) and on `list_vehicle_orders` (whitelist: `award_date`, `obligated`, `total_contract_value`). Prefix with `-` for descending.
|
|
17
|
+
- `ShapeConfig.VEHICLE_ORDERS_MINIMAL` default for the new orders endpoint.
|
|
18
|
+
- Shaping: New `organization(*)` expand on `Vehicle`, `Forecast`, `Grant`, `ITDashboardInvestment`, and `Protest` schemas — returns the canonical 7-key office payload (`organization_id`, `office_code`, `office_name`, `agency_code`, `agency_name`, `department_code`, `department_name`). Selectable as the bare leaf (`shape=...,organization`) or as a sub-selectable expansion (`shape=...,organization(office_code,...)`).
|
|
19
|
+
- Shaping: New `vehicle(*)` expand on `Contract` — request the parent vehicle inline from `/api/contracts/` (API 4.2.0).
|
|
20
|
+
- `Vehicle` and `VehicleMetrics` are now exported from the top-level `tango` package.
|
|
21
|
+
- `tango.webhooks` subpackage with HMAC-SHA256 signing helpers (`verify_signature`, `generate_signature`, `parse_signature_header`) that mirror the canonical Tango server scheme byte-for-byte. Importable from a default `pip install tango-python` (pure stdlib).
|
|
22
|
+
- `WebhookReceiver`: a stdlib-based local HTTP listener for development and integration tests. Verifies signatures, optionally forwards each delivery to a downstream URL, and records deliveries in memory for inspection. Usable as a context manager (`with WebhookReceiver(secret=...).run() as rx: ...`).
|
|
23
|
+
- `tango.webhooks.simulate.deliver(...)`: locally sign and POST a payload to any URL — no Tango involvement. Useful for offline iteration on receiver code.
|
|
24
|
+
- New `tango[webhooks]` extra (adds `click`) ships a `tango` console script covering the full webhook lifecycle for developer integrations:
|
|
25
|
+
- `listen` — local receiver
|
|
26
|
+
- `simulate` — sign a payload locally; with `--to`, also POST it
|
|
27
|
+
- `trigger` — ask Tango to send a real test delivery
|
|
28
|
+
- `fetch-sample` — print the canonical payload Tango emits for an event type
|
|
29
|
+
- `list-event-types` — discover what's subscribable
|
|
30
|
+
- `endpoints list|get|create|delete` — manage delivery endpoints
|
|
31
|
+
- `subscriptions list|get|create|delete` — manage what events you receive
|
|
32
|
+
Together these let a developer go from zero to receiving real Tango webhooks without leaving the shell or dropping into Python.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
- `ShapeConfig.VEHICLES_MINIMAL` and `VEHICLES_COMPREHENSIVE` now include the new top-level fields and the `organization` expansion. `VEHICLES_COMPREHENSIVE` defaults to `metrics(*)` and no longer pulls the deprecated `competition_details(*)` blob.
|
|
36
|
+
|
|
37
|
+
### Deprecated
|
|
38
|
+
- Vehicles shape fields `agency_details`, `competition_details`, and the `opportunity` expansion. The upstream API now sends a `Deprecation: true` header for these and recomputes them at request time. Explicit use in `shape=...` emits a Python `DeprecationWarning`. Sunset timeline TBD upstream.
|
|
39
|
+
|
|
40
|
+
### Notes
|
|
41
|
+
- Console script name `tango` may be revisited in a future release if it conflicts with sibling tooling (`tango-scripts` reuses the bare name).
|
|
42
|
+
|
|
43
|
+
### Documentation
|
|
44
|
+
- New `docs/WEBHOOKS.md` — comprehensive guide covering install, concepts, a zero-to-receiving quickstart, full CLI reference, and programmatic patterns for `WebhookReceiver` / `simulate.sign` / `simulate.deliver` in pytest fixtures.
|
|
45
|
+
- `docs/API_REFERENCE.md`: filled in `get_webhook_subscription`, replaced the hand-rolled signature-verification snippet with a pointer to `tango.webhooks.verify_signature`, and added a new "Webhook tooling (`tango.webhooks`)" section that documents every importable from the new subpackage.
|
|
46
|
+
- `README.md`: new "Webhook Tooling" section under Advanced Features, plus the new guide is linked from the Documentation index.
|
|
47
|
+
|
|
48
|
+
## [0.5.0] - 2026-04-08
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
- IT Dashboard investments: `list_itdashboard_investments`, `get_itdashboard_investment` (`/api/itdashboard/`) with shaping and filter params (`search`, `agency_code`, `agency_name`, `type_of_investment`, `updated_time_after`, `updated_time_before`, `cio_rating`, `cio_rating_max`, `performance_risk`). Tier-gated by the API: free tier gets `search`, pro adds structured filters, business+ adds CIO/performance analytics. New `ITDashboardInvestment` model and `ShapeConfig.ITDASHBOARD_INVESTMENTS_MINIMAL` / `ITDASHBOARD_INVESTMENTS_COMPREHENSIVE` defaults.
|
|
52
|
+
|
|
53
|
+
## [0.4.4] - 2026-03-25
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
- `parent_piid` filter parameter on `list_contracts` for filtering orders under a specific parent IDV PIID.
|
|
57
|
+
- `user_agent` and `extra_headers` parameters on `TangoClient` for custom request headers.
|
|
58
|
+
- `TangoClient.last_response_headers` property for accessing full HTTP headers from the most recent API response.
|
|
59
|
+
|
|
60
|
+
## [0.4.3] - 2026-03-21
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
- `TangoRateLimitError` now exposes `wait_in_seconds`, `detail`, and `limit_type` properties parsed from the API's 429 response body.
|
|
64
|
+
- `RateLimitInfo` dataclass for structured access to rate limit headers (`X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and per-window daily/burst variants).
|
|
65
|
+
- `TangoClient.rate_limit_info` property returns rate limit info from the most recent API response.
|
|
66
|
+
|
|
67
|
+
### Changed
|
|
68
|
+
- `_request` now passes the full 429 response body to `TangoRateLimitError` (previously discarded), enabling callers to access `wait_in_seconds` and the specific limit type that was exceeded.
|
|
69
|
+
|
|
70
|
+
## [0.4.2] - 2026-03-04
|
|
71
|
+
|
|
72
|
+
### Added
|
|
73
|
+
- Protests endpoints: `list_protests`, `get_protest` with shaping and filter params (`source_system`, `outcome`, `case_type`, `agency`, `case_number`, `solicitation_number`, `protester`, `filed_date_after`, `filed_date_before`, `decision_date_after`, `decision_date_before`, `search`).
|
|
74
|
+
|
|
75
|
+
### Changed
|
|
76
|
+
- Lint CI workflow disabled for push/PR (runs only on manual trigger) until the private `makegov/tango` repo is accessible to the workflow.
|
|
77
|
+
- Updated documents to reflect changes since v0.4.0
|
|
78
|
+
- Entities: `ENTITIES_COMPREHENSIVE` now uses `federal_obligations(*)` expansion; the API treats federal obligations as an expansion rather than a plain shape field.
|
|
79
|
+
- Docs: `SHAPES.md` documents `federal_obligations(*)` as an expansion for entity shaping.
|
|
80
|
+
- Integration tests: `test_parsing_nested_objects_with_missing_data` accepts award office fields (`office_code`, `agency_code`, `department_code`) and empty nested objects when the API returns partial data.
|
|
81
|
+
|
|
82
|
+
### Removed
|
|
83
|
+
- Assistance: `list_assistance` endpoint and all related tests, docs, and references.
|
|
84
|
+
- IDV summaries: `get_idv_summary` and `list_idv_summary_awards` endpoints and related integration tests, cassettes, and API reference section.
|
|
85
|
+
|
|
86
|
+
## [0.4.1] - 2026-03-03
|
|
87
|
+
|
|
88
|
+
### Added
|
|
89
|
+
- GSA eLibrary contracts: `list_gsa_elibrary_contracts`, `get_gsa_elibrary_contract` with shaping and filter params (`contract_number`, `key`, `piid`, `schedule`, `search`, `sin`, `uei`).
|
|
90
|
+
|
|
91
|
+
### Changed
|
|
92
|
+
- Conformance: replaced `**kwargs`/`**filters` with explicit filter parameters on `list_contracts`, `list_idvs`, `list_entities`, `list_forecasts`, `list_grants`, `list_notices`, `list_opportunities` for full filter/shape conformance. Backward compatibility preserved for `list_contracts(filters=SearchFilters(...))`.
|
|
93
|
+
|
|
94
|
+
## [0.4.0] - 2026-02-24
|
|
95
|
+
|
|
96
|
+
### Added
|
|
97
|
+
- Offices, Organizations, OTAs, OTIDVs, Subawards, NAICS, and Assistance endpoints.
|
|
98
|
+
- Filter/shape conformance tooling and documentation.
|
|
99
|
+
|
|
100
|
+
### Changed
|
|
101
|
+
- CI lint workflow runs filter/shape conformance when the manifest is available.
|
|
102
|
+
|
|
103
|
+
## [0.3.0] - 2026-02-09
|
|
104
|
+
|
|
105
|
+
### Added
|
|
106
|
+
- Vehicles endpoints: `list_vehicles`, `get_vehicle`, and `list_vehicle_awardees` (supports shaping + flattening). (refs `makegov/tango#1328`)
|
|
107
|
+
- IDV endpoints: `list_idvs`, `get_idv`, `list_idv_awards`, `list_idv_child_idvs`, `list_idv_transactions`, `get_idv_summary`, `list_idv_summary_awards`. (refs `makegov/tango#1328`)
|
|
108
|
+
- Webhooks v2 client support: event type discovery, subscription CRUD, endpoint management, test delivery, and sample payload helpers. (refs `makegov/tango#1274`)
|
|
109
|
+
|
|
110
|
+
### Changed
|
|
111
|
+
- Expanded explicit schemas to support common IDV shaping expansions (award offices, officers, period of performance, etc.).
|
|
112
|
+
- HTTP client now supports PATCH/DELETE helpers for webhook management endpoints.
|
|
113
|
+
|
|
114
|
+
## [0.2.0] - 2025-11-16
|
|
115
|
+
|
|
116
|
+
- Entirely refactored SDK
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# tango-python
|
|
2
|
+
|
|
3
|
+
<!--
|
|
4
|
+
This file is the AI-assistant context for this Python SDK. It's
|
|
5
|
+
**gitignored** — each developer regenerates it locally by running
|
|
6
|
+
`mg-tools install`, and may edit it freely without affecting the repo.
|
|
7
|
+
External contributors don't see it; they read README.md and any
|
|
8
|
+
committed CONTRIBUTING.md / docs/ for contribution guidance.
|
|
9
|
+
|
|
10
|
+
MakeGov team members: per-developer mg-tools plugin paths + rules table
|
|
11
|
+
live in `.claude/mg-tools-integration.md` (also gitignored).
|
|
12
|
+
-->
|
|
13
|
+
|
|
14
|
+
## Non-Negotiables
|
|
15
|
+
|
|
16
|
+
- **Python version is pinned.** Check `pyproject.toml` for `requires-python`; don't use features below that. CI matrix is the contract.
|
|
17
|
+
- **Public SDK — surface is contract.** Method names, parameter names, and return shapes are promises. Deprecate, don't break. If you rename a method, leave an alias + deprecation warning for at least one minor version.
|
|
18
|
+
- **Always update `CHANGELOG.md`** under `## [Unreleased]` when source files change. Follow [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) + [SemVer](https://semver.org/).
|
|
19
|
+
- **Type hints are required on public API.** Strict `mypy` catches drift; new code must type-check cleanly.
|
|
20
|
+
- **No `**kwargs` for filter / query parameters.** Every accepted filter must be an explicit named parameter. Opaque `**kwargs` is banned from the public surface because it can't be type-checked, documented, or validated against the API's filter contract.
|
|
21
|
+
- **Never `git commit` / `push` / `merge` without the user's explicit permission.**
|
|
22
|
+
- Use the `gh` CLI for GitHub operations.
|
|
23
|
+
|
|
24
|
+
## Project conventions
|
|
25
|
+
|
|
26
|
+
### Toolchain
|
|
27
|
+
|
|
28
|
+
- **Dependency / env manager:** `uv`. Always run tooling via `uv run ...` — never bare `pytest`/`ruff`/`mypy`.
|
|
29
|
+
- **Formatter + linter:** `ruff` — config in `pyproject.toml::[tool.ruff]`. Don't hand-format; let ruff do it.
|
|
30
|
+
- **Type checker:** `mypy` in strict mode. New code must type-check.
|
|
31
|
+
- **Base branch:** `main`. Release PRs target it directly.
|
|
32
|
+
|
|
33
|
+
### Commands
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# install
|
|
37
|
+
uv sync --all-extras
|
|
38
|
+
|
|
39
|
+
# format / lint / types
|
|
40
|
+
uv run ruff format .
|
|
41
|
+
uv run ruff check .
|
|
42
|
+
uv run mypy <package>/
|
|
43
|
+
|
|
44
|
+
# tests
|
|
45
|
+
uv run pytest # all
|
|
46
|
+
uv run pytest tests/ -m "not integration" # unit only
|
|
47
|
+
uv run pytest tests/integration/ # integration
|
|
48
|
+
|
|
49
|
+
# if this SDK uses cassette-based integration tests
|
|
50
|
+
# (VCR.py or similar), refresh via an env var when endpoints change
|
|
51
|
+
# — see repo-specific docs for the exact knob.
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Tests
|
|
55
|
+
|
|
56
|
+
- **Unit:** fast, no network. Should run in under ~10s total.
|
|
57
|
+
- **Integration:** gated behind a marker (`@pytest.mark.integration`); replayed via cassettes in CI.
|
|
58
|
+
- **Live / production smoke:** live-API-gated via env var (e.g. `<SDK>_API_KEY`) + a separate marker. Never run in regular CI.
|
|
59
|
+
- Pytest markers should be registered in `pyproject.toml::[tool.pytest.ini_options].markers`.
|
|
60
|
+
- When a cassette-driven test is affected by an API change, refresh cassettes and commit them in the same PR.
|
|
61
|
+
|
|
62
|
+
### Release flow
|
|
63
|
+
|
|
64
|
+
1. Bump `version` in `pyproject.toml` AND `__version__` in the package's `__init__.py` (keep them in sync — ruff lint should catch drift).
|
|
65
|
+
2. Promote `## [Unreleased]` → a dated version section in `CHANGELOG.md`.
|
|
66
|
+
3. Open a PR to `main`, titled as the version number (e.g. `v0.5.1`) — no `WIP:` prefix for release PRs.
|
|
67
|
+
4. A CI workflow handles PyPI publish on tag (check `.github/workflows/` for the exact trigger).
|
|
68
|
+
|
|
69
|
+
### Style
|
|
70
|
+
|
|
71
|
+
- **Double quotes** for Python strings (ruff-enforced via `quote-style = "double"`).
|
|
72
|
+
- American English everywhere.
|
|
73
|
+
|
|
74
|
+
## Where things live in this repo
|
|
75
|
+
|
|
76
|
+
<!--
|
|
77
|
+
Fill this in. Agents read `CLAUDE.md` first; the pointers below tell them where
|
|
78
|
+
this repo keeps its truth. If a concern isn't listed, an agent will fall back to
|
|
79
|
+
conventional paths — but it'll miss anything stashed in non-obvious locations.
|
|
80
|
+
|
|
81
|
+
Run `/mg-tools:mg-setup` to (re)populate this section with a guided first pass and
|
|
82
|
+
follow-up questions.
|
|
83
|
+
-->
|
|
84
|
+
|
|
85
|
+
| What | Where |
|
|
86
|
+
| ---- | ----- |
|
|
87
|
+
| Architecture / design notes | `<docs/ARCHITECTURE.md>` |
|
|
88
|
+
| Local rules (per-topic agent guidance) | `.claude/rules/*.md` |
|
|
89
|
+
| Local agents | `.claude/agents/*.md` |
|
|
90
|
+
| Local skills | `.claude/skills/*/SKILL.md` |
|
|
91
|
+
| User-facing docs | `docs/` |
|
|
92
|
+
| README | `README.md` |
|
|
93
|
+
| Contributing guide | `CONTRIBUTING.md` |
|
|
94
|
+
| SDK source | `<package>/` |
|
|
95
|
+
| Tests | `tests/` |
|
|
96
|
+
| Cassettes (if VCR-based integration tests) | `<tests/cassettes/>` |
|
|
97
|
+
| Other repo-specific sources of truth | *(add below)* |
|
|
98
|
+
|
|
99
|
+
## MakeGov sibling repos (read as local files via `../<name>/`)
|
|
100
|
+
|
|
101
|
+
Team machines check out MakeGov repos as siblings under a single root (default `~/Sites/`). Read sibling content as ordinary local paths — no `gh api`, no `WebFetch`.
|
|
102
|
+
|
|
103
|
+
- `../docs/` — public / editorial-split docs
|
|
104
|
+
- `../tango/` — primary API / backend this SDK targets
|
|
105
|
+
- `../mg-tools/` — shared AI tooling
|
|
106
|
+
- Other siblings: `../infra/`, `../scraper-service/`, `../crm/`, `../makegov.com/`, `../marcomms/`
|
|
107
|
+
|
|
108
|
+
Full list + conventions: `${CLAUDE_PLUGIN_ROOT}/rules/sibling-repos.md`.
|
|
109
|
+
|
|
110
|
+
## Context precedence (read order)
|
|
111
|
+
|
|
112
|
+
Local wins. Before any non-trivial task:
|
|
113
|
+
|
|
114
|
+
1. This `CLAUDE.md` (root + any scoped `**/CLAUDE.md`) — **start here**, then follow the pointers in "Where things live"
|
|
115
|
+
2. Files named in "Where things live" above
|
|
116
|
+
3. Sibling repos (above) when the task spans MakeGov surfaces (e.g. an SDK method wrapping a tango endpoint → read tango's view/serializer)
|
|
117
|
+
4. Shipped mg-tools rules under `${CLAUDE_PLUGIN_ROOT}/rules/`
|
|
118
|
+
5. Community / language defaults (last resort)
|
|
119
|
+
|
|
120
|
+
Don't fall back to shipped defaults while local pointers remain unread.
|
|
121
|
+
|
|
122
|
+
## Referenced docs
|
|
123
|
+
|
|
124
|
+
- `README.md` — public install + quickstart (user-facing).
|
|
125
|
+
- `docs/` — any user-facing guides (shape/filter DSLs, advanced usage, etc.).
|
|
126
|
+
- `CONTRIBUTING.md` (if present) — contribution flow for external contributors.
|
|
127
|
+
|
|
128
|
+
## Contributing
|
|
129
|
+
|
|
130
|
+
External contributors: see `CONTRIBUTING.md` and/or the repo's open issues. Tests and linters should pass locally before opening a PR.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tango-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Python SDK for the Tango API
|
|
5
5
|
Project-URL: Homepage, https://github.com/makegov/tango-python
|
|
6
6
|
Project-URL: Documentation, https://docs.makegov.com/tango-python
|
|
@@ -51,6 +51,8 @@ Requires-Dist: ruff>=0.3.0; extra == 'dev'
|
|
|
51
51
|
Provides-Extra: notebooks
|
|
52
52
|
Requires-Dist: ipykernel>=6.25.0; extra == 'notebooks'
|
|
53
53
|
Requires-Dist: jupyter>=1.0.0; extra == 'notebooks'
|
|
54
|
+
Provides-Extra: webhooks
|
|
55
|
+
Requires-Dist: click>=8.1; extra == 'webhooks'
|
|
54
56
|
Description-Content-Type: text/markdown
|
|
55
57
|
|
|
56
58
|
# Tango Python SDK
|
|
@@ -224,9 +226,14 @@ otidvs = client.list_otidvs(limit=25)
|
|
|
224
226
|
### Vehicles
|
|
225
227
|
|
|
226
228
|
```python
|
|
227
|
-
vehicles = client.list_vehicles(
|
|
229
|
+
vehicles = client.list_vehicles(
|
|
230
|
+
search="GSA schedule",
|
|
231
|
+
ordering="-vehicle_obligations",
|
|
232
|
+
shape=ShapeConfig.VEHICLES_MINIMAL,
|
|
233
|
+
)
|
|
228
234
|
vehicle = client.get_vehicle("UUID", shape=ShapeConfig.VEHICLES_COMPREHENSIVE)
|
|
229
235
|
awardees = client.list_vehicle_awardees("UUID")
|
|
236
|
+
orders = client.list_vehicle_orders("UUID", ordering="-obligated")
|
|
230
237
|
```
|
|
231
238
|
|
|
232
239
|
### Entities (Vendors/Recipients)
|
|
@@ -382,6 +389,46 @@ contracts = client.list_contracts(
|
|
|
382
389
|
# Returns: {"key": "...", "transactions.0.action_date": "...", "transactions.0.obligated": "..."}
|
|
383
390
|
```
|
|
384
391
|
|
|
392
|
+
### Webhook Tooling
|
|
393
|
+
|
|
394
|
+
The SDK ships first-class tooling for **building and testing webhook integrations against the Tango API** — including signing helpers, a local receiver, and a command-line tool covering the full lifecycle:
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
pip install 'tango-python[webhooks]'
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
This adds a `tango` console script with subcommands for the full webhook lifecycle:
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
# Discover what's available
|
|
404
|
+
tango webhooks list-event-types
|
|
405
|
+
tango webhooks fetch-sample --event-type entities.updated
|
|
406
|
+
|
|
407
|
+
# Local development
|
|
408
|
+
tango webhooks listen --port 8011 --secret $SECRET # receiver
|
|
409
|
+
tango webhooks simulate --secret $SECRET --event-type entities.updated # sign + print
|
|
410
|
+
tango webhooks simulate --secret $SECRET --event-type entities.updated \
|
|
411
|
+
--to http://127.0.0.1:8011/tango/webhooks # also POST
|
|
412
|
+
|
|
413
|
+
# Manage real subscriptions and endpoints
|
|
414
|
+
tango webhooks endpoints create|list|get|delete
|
|
415
|
+
tango webhooks subscriptions create|list|get|delete
|
|
416
|
+
|
|
417
|
+
# Force a real test delivery from Tango
|
|
418
|
+
tango webhooks trigger
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
The signing helpers (`verify_signature`, `generate_signature`) are pure stdlib and importable from the default install — your receiver code doesn't need the extra:
|
|
422
|
+
|
|
423
|
+
```python
|
|
424
|
+
from tango.webhooks import verify_signature
|
|
425
|
+
|
|
426
|
+
if not verify_signature(raw_body, secret, request.headers.get("X-Tango-Signature")):
|
|
427
|
+
return 401, "invalid signature"
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
For the full guide — workflow, CLI reference, and programmatic patterns for pytest fixtures — see [`docs/WEBHOOKS.md`](docs/WEBHOOKS.md).
|
|
431
|
+
|
|
385
432
|
### Type Hints with IDE Support
|
|
386
433
|
|
|
387
434
|
Import TypedDict types for IDE autocomplete:
|
|
@@ -531,6 +578,7 @@ tango-python/
|
|
|
531
578
|
- [Shape System Guide](docs/SHAPES.md) - Comprehensive guide to response shaping
|
|
532
579
|
- [API Reference](docs/API_REFERENCE.md) - Detailed API documentation
|
|
533
580
|
- [Developer Guide](docs/DEVELOPERS.md) - Technical documentation for developers
|
|
581
|
+
- [Webhooks Guide](docs/WEBHOOKS.md) - Workflow, CLI reference, and programmatic patterns for webhook integrations
|
|
534
582
|
- [Quick Start Notebook](docs/quick_start.ipynb) - Interactive Jupyter notebook with examples
|
|
535
583
|
|
|
536
584
|
## Requirements
|
|
@@ -169,9 +169,14 @@ otidvs = client.list_otidvs(limit=25)
|
|
|
169
169
|
### Vehicles
|
|
170
170
|
|
|
171
171
|
```python
|
|
172
|
-
vehicles = client.list_vehicles(
|
|
172
|
+
vehicles = client.list_vehicles(
|
|
173
|
+
search="GSA schedule",
|
|
174
|
+
ordering="-vehicle_obligations",
|
|
175
|
+
shape=ShapeConfig.VEHICLES_MINIMAL,
|
|
176
|
+
)
|
|
173
177
|
vehicle = client.get_vehicle("UUID", shape=ShapeConfig.VEHICLES_COMPREHENSIVE)
|
|
174
178
|
awardees = client.list_vehicle_awardees("UUID")
|
|
179
|
+
orders = client.list_vehicle_orders("UUID", ordering="-obligated")
|
|
175
180
|
```
|
|
176
181
|
|
|
177
182
|
### Entities (Vendors/Recipients)
|
|
@@ -327,6 +332,46 @@ contracts = client.list_contracts(
|
|
|
327
332
|
# Returns: {"key": "...", "transactions.0.action_date": "...", "transactions.0.obligated": "..."}
|
|
328
333
|
```
|
|
329
334
|
|
|
335
|
+
### Webhook Tooling
|
|
336
|
+
|
|
337
|
+
The SDK ships first-class tooling for **building and testing webhook integrations against the Tango API** — including signing helpers, a local receiver, and a command-line tool covering the full lifecycle:
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
pip install 'tango-python[webhooks]'
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
This adds a `tango` console script with subcommands for the full webhook lifecycle:
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
# Discover what's available
|
|
347
|
+
tango webhooks list-event-types
|
|
348
|
+
tango webhooks fetch-sample --event-type entities.updated
|
|
349
|
+
|
|
350
|
+
# Local development
|
|
351
|
+
tango webhooks listen --port 8011 --secret $SECRET # receiver
|
|
352
|
+
tango webhooks simulate --secret $SECRET --event-type entities.updated # sign + print
|
|
353
|
+
tango webhooks simulate --secret $SECRET --event-type entities.updated \
|
|
354
|
+
--to http://127.0.0.1:8011/tango/webhooks # also POST
|
|
355
|
+
|
|
356
|
+
# Manage real subscriptions and endpoints
|
|
357
|
+
tango webhooks endpoints create|list|get|delete
|
|
358
|
+
tango webhooks subscriptions create|list|get|delete
|
|
359
|
+
|
|
360
|
+
# Force a real test delivery from Tango
|
|
361
|
+
tango webhooks trigger
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
The signing helpers (`verify_signature`, `generate_signature`) are pure stdlib and importable from the default install — your receiver code doesn't need the extra:
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
from tango.webhooks import verify_signature
|
|
368
|
+
|
|
369
|
+
if not verify_signature(raw_body, secret, request.headers.get("X-Tango-Signature")):
|
|
370
|
+
return 401, "invalid signature"
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
For the full guide — workflow, CLI reference, and programmatic patterns for pytest fixtures — see [`docs/WEBHOOKS.md`](docs/WEBHOOKS.md).
|
|
374
|
+
|
|
330
375
|
### Type Hints with IDE Support
|
|
331
376
|
|
|
332
377
|
Import TypedDict types for IDE autocomplete:
|
|
@@ -476,6 +521,7 @@ tango-python/
|
|
|
476
521
|
- [Shape System Guide](docs/SHAPES.md) - Comprehensive guide to response shaping
|
|
477
522
|
- [API Reference](docs/API_REFERENCE.md) - Detailed API documentation
|
|
478
523
|
- [Developer Guide](docs/DEVELOPERS.md) - Technical documentation for developers
|
|
524
|
+
- [Webhooks Guide](docs/WEBHOOKS.md) - Workflow, CLI reference, and programmatic patterns for webhook integrations
|
|
479
525
|
- [Quick Start Notebook](docs/quick_start.ipynb) - Interactive Jupyter notebook with examples
|
|
480
526
|
|
|
481
527
|
## Requirements
|
|
@@ -503,13 +503,14 @@ Vehicles provide a solicitation-centric way to discover groups of related IDVs a
|
|
|
503
503
|
|
|
504
504
|
### list_vehicles()
|
|
505
505
|
|
|
506
|
-
List vehicles with optional vehicle-level full-text search.
|
|
506
|
+
List vehicles with optional vehicle-level full-text search and ordering.
|
|
507
507
|
|
|
508
508
|
```python
|
|
509
509
|
vehicles = client.list_vehicles(
|
|
510
510
|
page=1,
|
|
511
511
|
limit=25,
|
|
512
512
|
search="GSA schedule",
|
|
513
|
+
ordering="-vehicle_obligations",
|
|
513
514
|
shape=ShapeConfig.VEHICLES_MINIMAL,
|
|
514
515
|
flat=False,
|
|
515
516
|
flat_lists=False,
|
|
@@ -520,6 +521,7 @@ vehicles = client.list_vehicles(
|
|
|
520
521
|
- `page` (int): Page number (default: 1)
|
|
521
522
|
- `limit` (int): Results per page (default: 25, max: 100)
|
|
522
523
|
- `search` (str, optional): Vehicle-level search term
|
|
524
|
+
- `ordering` (str, optional): Server-side sort. Allowed: `vehicle_obligations`, `latest_award_date`. Prefix with `-` for descending.
|
|
523
525
|
- `shape` (str, optional): Shape string (defaults to `ShapeConfig.VEHICLES_MINIMAL`)
|
|
524
526
|
- `flat` (bool): Flatten nested objects in shaped response
|
|
525
527
|
- `flat_lists` (bool): Flatten arrays using indexed keys
|
|
@@ -552,6 +554,67 @@ awardees = client.list_vehicle_awardees(
|
|
|
552
554
|
)
|
|
553
555
|
```
|
|
554
556
|
|
|
557
|
+
### list_vehicle_orders()
|
|
558
|
+
|
|
559
|
+
List task orders under a vehicle's IDVs (`/api/vehicles/{uuid}/orders/`). Optimized for fast pagination over large vehicles.
|
|
560
|
+
|
|
561
|
+
```python
|
|
562
|
+
orders = client.list_vehicle_orders(
|
|
563
|
+
uuid="00000000-0000-0000-0000-000000000001",
|
|
564
|
+
limit=25,
|
|
565
|
+
ordering="-obligated",
|
|
566
|
+
shape=ShapeConfig.VEHICLE_ORDERS_MINIMAL,
|
|
567
|
+
)
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
**Parameters:**
|
|
571
|
+
- `uuid` (str): Vehicle UUID
|
|
572
|
+
- `page` (int): Page number (default: 1)
|
|
573
|
+
- `limit` (int): Results per page (default: 25, max: 100)
|
|
574
|
+
- `ordering` (str, optional): Server-side sort. Allowed: `award_date` (default), `obligated`, `total_contract_value`. Prefix with `-` for descending.
|
|
575
|
+
- `shape` (str, optional): Shape string (defaults to `ShapeConfig.VEHICLE_ORDERS_MINIMAL`)
|
|
576
|
+
- `flat`, `flat_lists`, `joiner`: as on other vehicles methods
|
|
577
|
+
|
|
578
|
+
**Returns:** [PaginatedResponse](#paginatedresponse) with order (Contract) dictionaries
|
|
579
|
+
|
|
580
|
+
### Vehicle response fields
|
|
581
|
+
|
|
582
|
+
The post-cutover (May 2026) vehicle response includes these top-level fields, all addressable via the `shape` parameter:
|
|
583
|
+
|
|
584
|
+
| Field | Type | Notes |
|
|
585
|
+
| ----- | ---- | ----- |
|
|
586
|
+
| `uuid` | str | Stable identifier. |
|
|
587
|
+
| `solicitation_identifier` | str | Solicitation shared by underlying IDVs. |
|
|
588
|
+
| `is_synthetic_solicitation` | bool | `True` for GWAC orphans recovered via `ACRO:` prefix. |
|
|
589
|
+
| `agency_id` | str | From IDV award-key suffix. |
|
|
590
|
+
| `program_acronym` | str \| None | New post-cutover field. |
|
|
591
|
+
| `organization_id` | str \| None | Awarding organization. |
|
|
592
|
+
| `organization` | dict \| None | Live awarding-org snapshot `{organization_id, office_code, office_name, agency_code, agency_name, department_code, department_name}`. Selected as a leaf field (`shape=...,organization`); not currently sub-selectable. |
|
|
593
|
+
| `vehicle_type`, `who_can_use`, `type_of_idc`, `contract_type` | dict \| None | Returned as `{code, description}`. |
|
|
594
|
+
| `description` | str \| None | Common text across IDV descriptions. |
|
|
595
|
+
| `descriptions` | list[str] \| None | Distinct IDV descriptions. |
|
|
596
|
+
| `idv_count`, `awardee_count`, `order_count` | int \| None | Denormalized rollups. |
|
|
597
|
+
| `total_obligated`, `vehicle_obligations`, `vehicle_contracts_value` | Decimal \| None | Denormalized rollups. |
|
|
598
|
+
| `award_date`, `latest_award_date`, `last_date_to_order` | date \| None | |
|
|
599
|
+
| `solicitation_title`, `solicitation_description`, `solicitation_date`, `opportunity_id` | str / date / None | From SAM.gov via the linked Opportunity. |
|
|
600
|
+
| `naics_code`, `psc_code`, `set_aside`, `fiscal_year` | int / str / None | |
|
|
601
|
+
|
|
602
|
+
### Vehicle shape expansions
|
|
603
|
+
|
|
604
|
+
- `awardees(...)` — underlying IDV awards. Supports nested `orders(...)`.
|
|
605
|
+
- `metrics(*)` — bundled computed metrics: `avg_offers_received`, `award_concentration_hhi`, `order_concentration_hhi`, `competed_rate`, `using_agency_count`, `avg_order_value`, `max_order_value`, `top_recipient_share`, `recent_obligations_24mo`, `recent_orders_24mo`, `days_since_last_order`, `obligation_to_ceiling_ratio`. Defaults included in `ShapeConfig.VEHICLES_COMPREHENSIVE`.
|
|
606
|
+
- `organization` — live awarding-org snapshot (selected as a leaf field; not sub-selectable).
|
|
607
|
+
|
|
608
|
+
### Deprecated shape fields
|
|
609
|
+
|
|
610
|
+
The following fields and expansions are still served by the API (recomputed at request time from the underlying IDVs) but the API now returns a `Deprecation: true` response header for them. They will be removed in a future tango API release.
|
|
611
|
+
|
|
612
|
+
- `agency_details` (top-level field and `agency_details(*)` expansion)
|
|
613
|
+
- `competition_details` (top-level field and `competition_details(*)` expansion)
|
|
614
|
+
- `opportunity(*)` expansion (use the new top-level `solicitation_*` and `opportunity_id` fields instead)
|
|
615
|
+
|
|
616
|
+
If you pass any of these in `shape=...`, the SDK will emit a Python `DeprecationWarning`. The default shapes (`VEHICLES_MINIMAL`, `VEHICLES_COMPREHENSIVE`) no longer include them.
|
|
617
|
+
|
|
555
618
|
---
|
|
556
619
|
|
|
557
620
|
## IDVs
|
|
@@ -1227,6 +1290,8 @@ for code in naics.results:
|
|
|
1227
1290
|
|
|
1228
1291
|
Webhook APIs let **Large / Enterprise** users manage subscription filters for outbound Tango webhooks.
|
|
1229
1292
|
|
|
1293
|
+
> **For testing, signing, and a CLI tool**, see [`docs/WEBHOOKS.md`](WEBHOOKS.md). This section covers SDK method signatures only.
|
|
1294
|
+
|
|
1230
1295
|
### list_webhook_event_types()
|
|
1231
1296
|
|
|
1232
1297
|
Discover supported `event_type` values and subject types.
|
|
@@ -1246,6 +1311,12 @@ Notes:
|
|
|
1246
1311
|
|
|
1247
1312
|
- This endpoint uses `page` + `page_size` (tier-capped) rather than `limit`.
|
|
1248
1313
|
|
|
1314
|
+
### get_webhook_subscription()
|
|
1315
|
+
|
|
1316
|
+
```python
|
|
1317
|
+
sub = client.get_webhook_subscription("SUBSCRIPTION_UUID")
|
|
1318
|
+
```
|
|
1319
|
+
|
|
1249
1320
|
### create_webhook_subscription()
|
|
1250
1321
|
|
|
1251
1322
|
```python
|
|
@@ -1335,21 +1406,89 @@ Every delivery includes an HMAC signature header:
|
|
|
1335
1406
|
|
|
1336
1407
|
Compute the digest over the **raw request body bytes** using your shared secret.
|
|
1337
1408
|
|
|
1409
|
+
The SDK ships a stdlib-only verifier that mirrors the Tango server's signing scheme byte-for-byte. Use it instead of hand-rolling — it's importable from a default install (no extras needed):
|
|
1410
|
+
|
|
1338
1411
|
```python
|
|
1339
|
-
import
|
|
1340
|
-
import hmac
|
|
1412
|
+
from tango.webhooks import verify_signature
|
|
1341
1413
|
|
|
1414
|
+
if not verify_signature(raw_body, secret, request.headers.get("X-Tango-Signature")):
|
|
1415
|
+
return 401
|
|
1416
|
+
```
|
|
1417
|
+
|
|
1418
|
+
`verify_signature` returns `False` for missing/empty/malformed headers — it never raises. Comparison is constant-time.
|
|
1419
|
+
|
|
1420
|
+
---
|
|
1421
|
+
|
|
1422
|
+
## Webhook tooling (`tango.webhooks`)
|
|
1423
|
+
|
|
1424
|
+
The `tango.webhooks` subpackage adds testing and developer-tooling primitives on top of the API methods above. Signing helpers ship with the default install; the receiver and CLI ship with `pip install 'tango-python[webhooks]'`. See [`docs/WEBHOOKS.md`](WEBHOOKS.md) for usage guides; this section is the import-level reference.
|
|
1425
|
+
|
|
1426
|
+
### Signing (default install)
|
|
1342
1427
|
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1428
|
+
```python
|
|
1429
|
+
from tango.webhooks import (
|
|
1430
|
+
verify_signature, # (body: bytes, secret: str, header: str | None) -> bool
|
|
1431
|
+
generate_signature, # (body: bytes, secret: str) -> str (lowercase hex)
|
|
1432
|
+
parse_signature_header, # (header: str | None) -> str | None (strips "sha256=")
|
|
1433
|
+
SIGNATURE_HEADER, # "X-Tango-Signature"
|
|
1434
|
+
SIGNATURE_PREFIX, # "sha256="
|
|
1435
|
+
)
|
|
1351
1436
|
```
|
|
1352
1437
|
|
|
1438
|
+
### `WebhookReceiver` (with `[webhooks]` extra)
|
|
1439
|
+
|
|
1440
|
+
A stdlib-based local HTTP receiver, useful in tests and during local development.
|
|
1441
|
+
|
|
1442
|
+
```python
|
|
1443
|
+
from tango.webhooks import WebhookReceiver, Delivery
|
|
1444
|
+
|
|
1445
|
+
with WebhookReceiver(secret="dev").run() as rx:
|
|
1446
|
+
# ... cause something to POST to rx.url ...
|
|
1447
|
+
deliveries: list[Delivery] = rx.deliveries
|
|
1448
|
+
```
|
|
1449
|
+
|
|
1450
|
+
Constructor (all keyword arguments):
|
|
1451
|
+
|
|
1452
|
+
| Arg | Default | Meaning |
|
|
1453
|
+
|---|---|---|
|
|
1454
|
+
| `secret` | `""` | Shared secret. Empty means signatures are not verified. |
|
|
1455
|
+
| `path` | `/tango/webhooks` | URL path to accept POSTs on. |
|
|
1456
|
+
| `host` | `127.0.0.1` | Bind address. |
|
|
1457
|
+
| `port` | `0` | TCP port. `0` = OS picks a free port. |
|
|
1458
|
+
| `forward_to` | `None` | Optional URL to mirror each delivery to. |
|
|
1459
|
+
| `max_history` | `256` | Cap on the in-memory `deliveries` deque. |
|
|
1460
|
+
| `on_delivery` | `None` | Callback fired for every delivery (verified or not). |
|
|
1461
|
+
| `require_signature` | `None` | Override default (require iff `secret` is set). |
|
|
1462
|
+
|
|
1463
|
+
Each `Delivery` is a dataclass: `received_at`, `path`, `signature_header`, `body_bytes`, `body_json`, `verified`, `remote_addr`, `forward_status`, `forward_error`.
|
|
1464
|
+
|
|
1465
|
+
### `simulate.sign` and `simulate.deliver`
|
|
1466
|
+
|
|
1467
|
+
```python
|
|
1468
|
+
from tango.webhooks import sign, SignedRequest
|
|
1469
|
+
from tango.webhooks import simulate
|
|
1470
|
+
|
|
1471
|
+
# Offline — produce the signed wire form without POSTing:
|
|
1472
|
+
signed: SignedRequest = sign({"events": [{"event_type": "..."}]}, secret="s")
|
|
1473
|
+
signed.body # bytes you would put on the wire
|
|
1474
|
+
signed.signature # bare lowercase hex
|
|
1475
|
+
signed.headers # {"Content-Type": ..., "X-Tango-Signature": "sha256=..."}
|
|
1476
|
+
|
|
1477
|
+
# With delivery — sign and POST to a target URL:
|
|
1478
|
+
result = simulate.deliver(target_url="http://localhost:8011/tango/webhooks",
|
|
1479
|
+
payload={...}, secret="s")
|
|
1480
|
+
result.status_code # status from the receiver
|
|
1481
|
+
result.signature # bare hex
|
|
1482
|
+
result.sent_bytes # exact bytes that were POSTed
|
|
1483
|
+
result.response_body # body the receiver returned
|
|
1484
|
+
```
|
|
1485
|
+
|
|
1486
|
+
`simulate.deliver` and `simulate.sign` accept payloads as `dict`, `list`, `str`, or raw `bytes`. Dicts/lists are serialized via `json.dumps(..., sort_keys=True, separators=(",", ":"))` so signatures are reproducible across runs.
|
|
1487
|
+
|
|
1488
|
+
### CLI entry point
|
|
1489
|
+
|
|
1490
|
+
The `tango[webhooks]` extra also installs a `tango` console script. See [`docs/WEBHOOKS.md` § CLI reference](WEBHOOKS.md#cli-reference) for the full command list.
|
|
1491
|
+
|
|
1353
1492
|
---
|
|
1354
1493
|
|
|
1355
1494
|
## Response Objects
|
|
@@ -57,7 +57,9 @@ idvs = client.list_idvs(shape=ShapeConfig.IDVS_MINIMAL)
|
|
|
57
57
|
grants = client.list_grants(shape=ShapeConfig.GRANTS_MINIMAL)
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
**Available constants:** Contracts (`CONTRACTS_MINIMAL`), Entities (`ENTITIES_MINIMAL`, `ENTITIES_COMPREHENSIVE`), Forecasts, Opportunities, Notices, Grants, IDVs, Vehicles, Organizations, OTAs, OTIDVs, Subawards. See [API Reference – ShapeConfig](API_REFERENCE.md#shapeconfig-predefined-shapes) for the full table and which method uses which constant.
|
|
60
|
+
**Available constants:** Contracts (`CONTRACTS_MINIMAL`), Entities (`ENTITIES_MINIMAL`, `ENTITIES_COMPREHENSIVE`), Forecasts, Opportunities, Notices, Grants, IDVs, Vehicles (`VEHICLES_MINIMAL`, `VEHICLES_COMPREHENSIVE`, `VEHICLE_AWARDEES_MINIMAL`, `VEHICLE_ORDERS_MINIMAL`), Organizations, OTAs, OTIDVs, Subawards. See [API Reference – ShapeConfig](API_REFERENCE.md#shapeconfig-predefined-shapes) for the full table and which method uses which constant.
|
|
61
|
+
|
|
62
|
+
> **Vehicles `metrics(*)` expansion:** The vehicles surface bundles 12 computed metrics under a single `metrics(*)` expansion (e.g. `award_concentration_hhi`, `competed_rate`, `top_recipient_share`). It is included in `VEHICLES_COMPREHENSIVE` by default. The `agency_details`, `competition_details`, and `opportunity` shape entries are deprecated and emit `DeprecationWarning` if requested explicitly.
|
|
61
63
|
|
|
62
64
|
## Basic Shaping
|
|
63
65
|
|