tango-python 0.5.0__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.
Files changed (204) hide show
  1. tango_python-0.6.0/.claude-plugin/plugin-pointer.json +5 -0
  2. tango_python-0.6.0/.mg-tools/DIARY.md +5 -0
  3. tango_python-0.6.0/.mg-tools/config.json +8 -0
  4. tango_python-0.6.0/.mg-tools/version +1 -0
  5. tango_python-0.6.0/CHANGELOG.md +116 -0
  6. tango_python-0.6.0/CLAUDE.md +130 -0
  7. {tango_python-0.5.0 → tango_python-0.6.0}/PKG-INFO +50 -2
  8. {tango_python-0.5.0 → tango_python-0.6.0}/README.md +47 -1
  9. {tango_python-0.5.0 → tango_python-0.6.0}/docs/API_REFERENCE.md +150 -11
  10. {tango_python-0.5.0 → tango_python-0.6.0}/docs/SHAPES.md +3 -1
  11. tango_python-0.6.0/docs/WEBHOOKS.md +430 -0
  12. {tango_python-0.5.0 → tango_python-0.6.0}/pyproject.toml +11 -1
  13. {tango_python-0.5.0 → tango_python-0.6.0}/tango/__init__.py +16 -1
  14. {tango_python-0.5.0 → tango_python-0.6.0}/tango/client.py +162 -4
  15. {tango_python-0.5.0 → tango_python-0.6.0}/tango/models.py +43 -6
  16. {tango_python-0.5.0 → tango_python-0.6.0}/tango/shapes/explicit_schemas.py +145 -21
  17. tango_python-0.6.0/tango/webhooks/__init__.py +27 -0
  18. tango_python-0.6.0/tango/webhooks/cli.py +519 -0
  19. tango_python-0.6.0/tango/webhooks/receiver.py +232 -0
  20. tango_python-0.6.0/tango/webhooks/signing.py +50 -0
  21. tango_python-0.6.0/tango/webhooks/simulate.py +102 -0
  22. tango_python-0.6.0/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists +161 -0
  23. tango_python-0.6.0/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_with_metrics_expansion +158 -0
  24. tango_python-0.6.0/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape +161 -0
  25. tango_python-0.6.0/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_orders_uses_default_shape +184 -0
  26. tango_python-0.6.0/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search +150 -0
  27. tango_python-0.6.0/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_with_ordering +78 -0
  28. {tango_python-0.5.0 → tango_python-0.6.0}/tests/integration/test_itdashboard_integration.py +6 -18
  29. {tango_python-0.5.0 → tango_python-0.6.0}/tests/integration/test_vehicles_idvs_integration.py +156 -11
  30. {tango_python-0.5.0 → tango_python-0.6.0}/tests/production/test_production_smoke.py +19 -0
  31. {tango_python-0.5.0 → tango_python-0.6.0}/tests/test_client.py +2 -7
  32. tango_python-0.6.0/tests/test_webhooks_cli.py +348 -0
  33. tango_python-0.6.0/tests/test_webhooks_receiver.py +127 -0
  34. tango_python-0.6.0/tests/test_webhooks_signing.py +73 -0
  35. tango_python-0.6.0/tests/test_webhooks_simulate.py +62 -0
  36. {tango_python-0.5.0 → tango_python-0.6.0}/uv.lock +18 -2
  37. tango_python-0.5.0/CHANGELOG.md +0 -78
  38. tango_python-0.5.0/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists +0 -152
  39. tango_python-0.5.0/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape +0 -169
  40. tango_python-0.5.0/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search +0 -86
  41. {tango_python-0.5.0 → tango_python-0.6.0}/.env.example +0 -0
  42. {tango_python-0.5.0 → tango_python-0.6.0}/.github/workflows/lint.yml +0 -0
  43. {tango_python-0.5.0 → tango_python-0.6.0}/.github/workflows/publish.yml +0 -0
  44. {tango_python-0.5.0 → tango_python-0.6.0}/.github/workflows/test.yml +0 -0
  45. {tango_python-0.5.0 → tango_python-0.6.0}/.gitignore +0 -0
  46. {tango_python-0.5.0 → tango_python-0.6.0}/LICENSE +0 -0
  47. {tango_python-0.5.0 → tango_python-0.6.0}/ROADMAP.md +0 -0
  48. {tango_python-0.5.0 → tango_python-0.6.0}/docs/DEVELOPERS.md +0 -0
  49. {tango_python-0.5.0 → tango_python-0.6.0}/docs/quick_start.ipynb +0 -0
  50. {tango_python-0.5.0 → tango_python-0.6.0}/scripts/README.md +0 -0
  51. {tango_python-0.5.0 → tango_python-0.6.0}/scripts/check_filter_shape_conformance.py +0 -0
  52. {tango_python-0.5.0 → tango_python-0.6.0}/scripts/fetch_api_schema.py +0 -0
  53. {tango_python-0.5.0 → tango_python-0.6.0}/scripts/generate_schemas_from_api.py +0 -0
  54. {tango_python-0.5.0 → tango_python-0.6.0}/scripts/pr_review.py +0 -0
  55. {tango_python-0.5.0 → tango_python-0.6.0}/scripts/test_production.py +0 -0
  56. {tango_python-0.5.0 → tango_python-0.6.0}/tango/exceptions.py +0 -0
  57. {tango_python-0.5.0 → tango_python-0.6.0}/tango/shapes/__init__.py +0 -0
  58. {tango_python-0.5.0 → tango_python-0.6.0}/tango/shapes/factory.py +0 -0
  59. {tango_python-0.5.0 → tango_python-0.6.0}/tango/shapes/generator.py +0 -0
  60. {tango_python-0.5.0 → tango_python-0.6.0}/tango/shapes/models.py +0 -0
  61. {tango_python-0.5.0 → tango_python-0.6.0}/tango/shapes/parser.py +0 -0
  62. {tango_python-0.5.0 → tango_python-0.6.0}/tango/shapes/schema.py +0 -0
  63. {tango_python-0.5.0 → tango_python-0.6.0}/tango/shapes/types.py +0 -0
  64. {tango_python-0.5.0 → tango_python-0.6.0}/tests/__init__.py +0 -0
  65. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestAgenciesIntegration.test_get_agency +0 -0
  66. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestAgenciesIntegration.test_list_agencies +0 -0
  67. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_field_type_validation +0 -0
  68. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_parsing_consistency +0 -0
  69. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestBusinessTypesIntegration.test_list_business_types +0 -0
  70. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_combined_filters_work_together +0 -0
  71. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_contract_cursor_pagination +0 -0
  72. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_contract_data_object_parsing +0 -0
  73. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_contract_field_types +0 -0
  74. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[keyword-software] +0 -0
  75. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[psc_code-R425] +0 -0
  76. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_awarding_agency_filter +0 -0
  77. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_date_range_filter +0 -0
  78. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_flat +0 -0
  79. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_naics_code_filter +0 -0
  80. {tango_python-0.5.0 → 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
  81. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[default-None] +0 -0
  82. {tango_python-0.5.0 → 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
  83. {tango_python-0.5.0 → 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
  84. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_new_expiring_filters +0 -0
  85. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_new_fiscal_year_range_filters +0 -0
  86. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_new_identifier_filters +0 -0
  87. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_search_contracts_with_filters +0 -0
  88. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_search_filters_object_with_new_parameters +0 -0
  89. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[asc-] +0 -0
  90. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[desc--] +0 -0
  91. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_contracts +0 -0
  92. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_entities +0 -0
  93. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_date_field_parsing_edge_cases +0 -0
  94. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_decimal_field_parsing_edge_cases +0 -0
  95. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_empty_list_responses +0 -0
  96. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_entity_parsing_with_various_address_formats +0 -0
  97. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_flattened_responses_with_flat_lists +0 -0
  98. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_list_field_parsing_consistency +0 -0
  99. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_nested_objects_with_missing_data +0 -0
  100. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_null_missing_fields_in_contracts +0 -0
  101. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_with_minimal_shape_sparse_data +0 -0
  102. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestEntitiesIntegration.test_entity_field_types +0 -0
  103. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestEntitiesIntegration.test_entity_location_parsing +0 -0
  104. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestEntitiesIntegration.test_entity_parsing_with_business_types +0 -0
  105. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestEntitiesIntegration.test_entity_with_various_identifiers +0 -0
  106. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_by_uei +0 -0
  107. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_flat +0 -0
  108. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_search +0 -0
  109. {tango_python-0.5.0 → 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
  110. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[custom-uei,legal_business_name,cage_code] +0 -0
  111. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
  112. {tango_python-0.5.0 → 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
  113. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestForecastsIntegration.test_forecast_field_types +0 -0
  114. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[custom-id,title,anticipated_award_date] +0 -0
  115. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[default-None] +0 -0
  116. {tango_python-0.5.0 → 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
  117. {tango_python-0.5.0 → 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
  118. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestGrantsIntegration.test_grant_field_types +0 -0
  119. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestGrantsIntegration.test_grant_pagination +0 -0
  120. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[custom-grant_id,title,opportunity_number] +0 -0
  121. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[default-None] +0 -0
  122. {tango_python-0.5.0 → 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
  123. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[minimal-grant_id,opportunity_number,title,status(-),agency_code] +0 -0
  124. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestIDVsIntegration.test_get_idv_uses_default_shape +0 -0
  125. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_awards_uses_default_shape +0 -0
  126. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_child_idvs_uses_default_shape +0 -0
  127. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_transactions +0 -0
  128. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestIDVsIntegration.test_list_idvs_uses_default_shape_and_keyset_params +0 -0
  129. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_code +0 -0
  130. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_name_text +0 -0
  131. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating +0 -0
  132. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating_max +0 -0
  133. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_performance_risk +0 -0
  134. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_type_of_investment +0 -0
  135. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_updated_time_range +0 -0
  136. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestITDashboardIntegration.test_funding_and_cio_evaluation_expansions +0 -0
  137. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestITDashboardIntegration.test_get_itdashboard_investment_by_uii +0 -0
  138. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestITDashboardIntegration.test_itdashboard_pagination +0 -0
  139. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_search +0 -0
  140. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[custom-uii,agency_name,investment_title,updated_time] +0 -0
  141. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[default-None] +0 -0
  142. {tango_python-0.5.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] +0 -0
  143. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestNaicsIntegration.test_list_naics +0 -0
  144. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[custom-notice_id,title,solicitation_number] +0 -0
  145. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[default-None] +0 -0
  146. {tango_python-0.5.0 → 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
  147. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
  148. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestNoticesIntegration.test_notice_field_types +0 -0
  149. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestNoticesIntegration.test_notice_pagination +0 -0
  150. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestNoticesIntegration.test_notice_with_meta_fields +0 -0
  151. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestOTAsIntegration.test_get_ota +0 -0
  152. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestOTAsIntegration.test_list_otas +0 -0
  153. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestOTIDVsIntegration.test_get_otidv +0 -0
  154. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestOTIDVsIntegration.test_list_otidvs +0 -0
  155. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestOfficesIntegration.test_get_office +0 -0
  156. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestOfficesIntegration.test_list_offices +0 -0
  157. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[custom-opportunity_id,title,solicitation_number] +0 -0
  158. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[default-None] +0 -0
  159. {tango_python-0.5.0 → 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
  160. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
  161. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestOpportunitiesIntegration.test_opportunity_field_types +0 -0
  162. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestOrganizationsIntegration.test_get_organization +0 -0
  163. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestOrganizationsIntegration.test_list_organizations +0 -0
  164. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestProtestsIntegration.test_get_protest_by_case_id +0 -0
  165. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_filter +0 -0
  166. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[custom-case_id,title,source_system,outcome] +0 -0
  167. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[default-None] +0 -0
  168. {tango_python-0.5.0 → 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
  169. {tango_python-0.5.0 → 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
  170. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestProtestsIntegration.test_protest_pagination +0 -0
  171. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestSubawardsIntegration.test_list_subawards +0 -0
  172. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[custom-key,piid,description] +0 -0
  173. {tango_python-0.5.0 → 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
  174. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[ultra_minimal-key,piid,recipient(display_name),total_contract_value] +0 -0
  175. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
  176. {tango_python-0.5.0 → 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
  177. {tango_python-0.5.0 → 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
  178. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestTypeHintsIntegration.test_notices_dict_access[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
  179. {tango_python-0.5.0 → 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
  180. {tango_python-0.5.0 → tango_python-0.6.0}/tests/cassettes/TestTypeHintsIntegration.test_opportunities_dict_access[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
  181. {tango_python-0.5.0 → tango_python-0.6.0}/tests/conftest.py +0 -0
  182. {tango_python-0.5.0 → tango_python-0.6.0}/tests/integration/README.md +0 -0
  183. {tango_python-0.5.0 → tango_python-0.6.0}/tests/integration/__init__.py +0 -0
  184. {tango_python-0.5.0 → tango_python-0.6.0}/tests/integration/conftest.py +0 -0
  185. {tango_python-0.5.0 → tango_python-0.6.0}/tests/integration/test_agencies_integration.py +0 -0
  186. {tango_python-0.5.0 → tango_python-0.6.0}/tests/integration/test_contracts_integration.py +0 -0
  187. {tango_python-0.5.0 → tango_python-0.6.0}/tests/integration/test_edge_cases_integration.py +0 -0
  188. {tango_python-0.5.0 → tango_python-0.6.0}/tests/integration/test_entities_integration.py +0 -0
  189. {tango_python-0.5.0 → tango_python-0.6.0}/tests/integration/test_forecasts_integration.py +0 -0
  190. {tango_python-0.5.0 → tango_python-0.6.0}/tests/integration/test_grants_integration.py +0 -0
  191. {tango_python-0.5.0 → tango_python-0.6.0}/tests/integration/test_naics_integration.py +0 -0
  192. {tango_python-0.5.0 → tango_python-0.6.0}/tests/integration/test_notices_integration.py +0 -0
  193. {tango_python-0.5.0 → tango_python-0.6.0}/tests/integration/test_offices_integration.py +0 -0
  194. {tango_python-0.5.0 → tango_python-0.6.0}/tests/integration/test_opportunities_integration.py +0 -0
  195. {tango_python-0.5.0 → tango_python-0.6.0}/tests/integration/test_organizations_integration.py +0 -0
  196. {tango_python-0.5.0 → tango_python-0.6.0}/tests/integration/test_otas_otidvs_integration.py +0 -0
  197. {tango_python-0.5.0 → tango_python-0.6.0}/tests/integration/test_protests_integration.py +0 -0
  198. {tango_python-0.5.0 → tango_python-0.6.0}/tests/integration/test_reference_data_integration.py +0 -0
  199. {tango_python-0.5.0 → tango_python-0.6.0}/tests/integration/test_subawards_integration.py +0 -0
  200. {tango_python-0.5.0 → tango_python-0.6.0}/tests/integration/validation.py +0 -0
  201. {tango_python-0.5.0 → tango_python-0.6.0}/tests/production/__init__.py +0 -0
  202. {tango_python-0.5.0 → tango_python-0.6.0}/tests/production/conftest.py +0 -0
  203. {tango_python-0.5.0 → tango_python-0.6.0}/tests/test_models.py +0 -0
  204. {tango_python-0.5.0 → tango_python-0.6.0}/tests/test_shapes.py +0 -0
@@ -0,0 +1,5 @@
1
+ {
2
+ "source": "/Users/vdavez/Sync/makegov/mg-tools",
3
+ "installed_by": "mg-tools/install.py",
4
+ "note": "Load via: claude --plugin-dir $source (or add to your shell's claude wrapper)."
5
+ }
@@ -0,0 +1,5 @@
1
+ # Diary
2
+
3
+ Append-only log of notable sessions. Newest at bottom. See mg-tools' `diary` skill
4
+ for the invocation protocol.
5
+
@@ -0,0 +1,8 @@
1
+ {
2
+ "profile": "sdk",
3
+ "toolchain": "both",
4
+ "baseBranch": "main",
5
+ "changelogPath": "CHANGELOG.md",
6
+ "appsRoot": null,
7
+ "ruleMap": {}
8
+ }
@@ -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.5.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(search="GSA schedule", shape=ShapeConfig.VEHICLES_MINIMAL)
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(search="GSA schedule", shape=ShapeConfig.VEHICLES_MINIMAL)
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 hashlib
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
- def verify_tango_webhook_signature(secret: str, raw_body: bytes, signature_header: str | None) -> bool:
1344
- if not signature_header:
1345
- return False
1346
- sig = signature_header.strip()
1347
- if sig.startswith("sha256="):
1348
- sig = sig[len("sha256=") :]
1349
- expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
1350
- return hmac.compare_digest(expected, sig)
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