tango-python 0.2.0__tar.gz → 0.3.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 (148) hide show
  1. {tango_python-0.2.0 → tango_python-0.3.0}/.github/workflows/publish.yml +2 -1
  2. {tango_python-0.2.0 → tango_python-0.3.0}/.gitignore +3 -1
  3. tango_python-0.3.0/CHANGELOG.md +27 -0
  4. {tango_python-0.2.0 → tango_python-0.3.0}/PKG-INFO +2 -2
  5. {tango_python-0.2.0 → tango_python-0.3.0}/README.md +1 -1
  6. {tango_python-0.2.0 → tango_python-0.3.0}/docs/API_REFERENCE.md +245 -0
  7. {tango_python-0.2.0 → tango_python-0.3.0}/pyproject.toml +2 -1
  8. tango_python-0.3.0/scripts/README.md +76 -0
  9. tango_python-0.3.0/scripts/pr_review.py +473 -0
  10. tango_python-0.3.0/scripts/test_production.py +86 -0
  11. {tango_python-0.2.0 → tango_python-0.3.0}/tango/__init__.py +13 -1
  12. {tango_python-0.2.0 → tango_python-0.3.0}/tango/client.py +681 -14
  13. {tango_python-0.2.0 → tango_python-0.3.0}/tango/models.py +119 -1
  14. {tango_python-0.2.0 → tango_python-0.3.0}/tango/shapes/explicit_schemas.py +302 -0
  15. tango_python-0.3.0/tests/cassettes/TestContractsIntegration.test_contract_cursor_pagination +169 -0
  16. tango_python-0.3.0/tests/cassettes/TestIDVsIntegration.test_get_idv_summary +252 -0
  17. tango_python-0.3.0/tests/cassettes/TestIDVsIntegration.test_get_idv_uses_default_shape +494 -0
  18. tango_python-0.3.0/tests/cassettes/TestIDVsIntegration.test_list_idv_awards_uses_default_shape +302 -0
  19. tango_python-0.3.0/tests/cassettes/TestIDVsIntegration.test_list_idv_child_idvs_uses_default_shape +302 -0
  20. tango_python-0.3.0/tests/cassettes/TestIDVsIntegration.test_list_idv_summary_awards +256 -0
  21. tango_python-0.3.0/tests/cassettes/TestIDVsIntegration.test_list_idv_transactions +304 -0
  22. tango_python-0.3.0/tests/cassettes/TestIDVsIntegration.test_list_idvs_uses_default_shape_and_keyset_params +284 -0
  23. tango_python-0.3.0/tests/cassettes/TestProductionSmokeWithCassettes.test_contract_cursor_pagination +169 -0
  24. tango_python-0.3.0/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists +1352 -0
  25. tango_python-0.3.0/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape +1505 -0
  26. tango_python-0.3.0/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search +812 -0
  27. {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/test_contracts_integration.py +65 -14
  28. tango_python-0.3.0/tests/integration/test_vehicles_idvs_integration.py +467 -0
  29. {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/validation.py +2 -0
  30. tango_python-0.3.0/tests/production/__init__.py +5 -0
  31. tango_python-0.3.0/tests/production/conftest.py +79 -0
  32. tango_python-0.3.0/tests/production/test_production_smoke.py +314 -0
  33. {tango_python-0.2.0 → tango_python-0.3.0}/tests/test_client.py +254 -0
  34. {tango_python-0.2.0 → tango_python-0.3.0}/uv.lock +1 -1
  35. tango_python-0.2.0/CHANGELOG.md +0 -10
  36. tango_python-0.2.0/scripts/README.md +0 -13
  37. {tango_python-0.2.0 → tango_python-0.3.0}/.env.example +0 -0
  38. {tango_python-0.2.0 → tango_python-0.3.0}/.github/workflows/lint.yml +0 -0
  39. {tango_python-0.2.0 → tango_python-0.3.0}/.github/workflows/test.yml +0 -0
  40. {tango_python-0.2.0 → tango_python-0.3.0}/LICENSE +0 -0
  41. {tango_python-0.2.0 → tango_python-0.3.0}/ROADMAP.md +0 -0
  42. {tango_python-0.2.0 → tango_python-0.3.0}/docs/DEVELOPERS.md +0 -0
  43. {tango_python-0.2.0 → tango_python-0.3.0}/docs/SHAPES.md +0 -0
  44. {tango_python-0.2.0 → tango_python-0.3.0}/docs/quick_start.ipynb +0 -0
  45. {tango_python-0.2.0 → tango_python-0.3.0}/scripts/fetch_api_schema.py +0 -0
  46. {tango_python-0.2.0 → tango_python-0.3.0}/scripts/generate_schemas_from_api.py +0 -0
  47. {tango_python-0.2.0 → tango_python-0.3.0}/tango/exceptions.py +0 -0
  48. {tango_python-0.2.0 → tango_python-0.3.0}/tango/shapes/__init__.py +0 -0
  49. {tango_python-0.2.0 → tango_python-0.3.0}/tango/shapes/factory.py +0 -0
  50. {tango_python-0.2.0 → tango_python-0.3.0}/tango/shapes/generator.py +0 -0
  51. {tango_python-0.2.0 → tango_python-0.3.0}/tango/shapes/models.py +0 -0
  52. {tango_python-0.2.0 → tango_python-0.3.0}/tango/shapes/parser.py +0 -0
  53. {tango_python-0.2.0 → tango_python-0.3.0}/tango/shapes/schema.py +0 -0
  54. {tango_python-0.2.0 → tango_python-0.3.0}/tango/shapes/types.py +0 -0
  55. {tango_python-0.2.0 → tango_python-0.3.0}/tests/__init__.py +0 -0
  56. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestAgenciesIntegration.test_get_agency +0 -0
  57. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestAgenciesIntegration.test_list_agencies +0 -0
  58. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_field_type_validation +0 -0
  59. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_parsing_consistency +0 -0
  60. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestBusinessTypesIntegration.test_list_business_types +0 -0
  61. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_combined_filters_work_together +0 -0
  62. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_contract_data_object_parsing +0 -0
  63. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_contract_field_types +0 -0
  64. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[keyword-software] +0 -0
  65. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[psc_code-R425] +0 -0
  66. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_awarding_agency_filter +0 -0
  67. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_date_range_filter +0 -0
  68. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_flat +0 -0
  69. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_naics_code_filter +0 -0
  70. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[custom-key,piid,recipient(display_name),total_contract_value,award_date] +0 -0
  71. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[default-None] +0 -0
  72. {tango_python-0.2.0 → tango_python-0.3.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
  73. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[minimal-key,piid,award_date,recipient(display_name),description,total_contract_value] +0 -0
  74. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_new_expiring_filters +0 -0
  75. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_new_fiscal_year_range_filters +0 -0
  76. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_new_identifier_filters +0 -0
  77. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_search_contracts_with_filters +0 -0
  78. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_search_filters_object_with_new_parameters +0 -0
  79. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[asc-] +0 -0
  80. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[desc--] +0 -0
  81. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_contracts +0 -0
  82. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_entities +0 -0
  83. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_date_field_parsing_edge_cases +0 -0
  84. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_decimal_field_parsing_edge_cases +0 -0
  85. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_empty_list_responses +0 -0
  86. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_entity_parsing_with_various_address_formats +0 -0
  87. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_flattened_responses_with_flat_lists +0 -0
  88. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_list_field_parsing_consistency +0 -0
  89. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_nested_objects_with_missing_data +0 -0
  90. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_null_missing_fields_in_contracts +0 -0
  91. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_with_minimal_shape_sparse_data +0 -0
  92. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEntitiesIntegration.test_entity_field_types +0 -0
  93. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEntitiesIntegration.test_entity_location_parsing +0 -0
  94. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEntitiesIntegration.test_entity_parsing_with_business_types +0 -0
  95. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEntitiesIntegration.test_entity_with_various_identifiers +0 -0
  96. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_by_uei +0 -0
  97. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_flat +0 -0
  98. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_search +0 -0
  99. {tango_python-0.2.0 → tango_python-0.3.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...95fcff7efcf320ecc846393dd484321d +0 -0
  100. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[custom-uei,legal_business_name,cage_code] +0 -0
  101. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
  102. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[with_address-uei,legal_business_name,cage_code,business_types,physical_address] +0 -0
  103. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestForecastsIntegration.test_forecast_field_types +0 -0
  104. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[custom-id,title,anticipated_award_date] +0 -0
  105. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[default-None] +0 -0
  106. {tango_python-0.2.0 → tango_python-0.3.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
  107. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[minimal-id,title,anticipated_award_date,fiscal_year,naics_code,status] +0 -0
  108. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestGrantsIntegration.test_grant_field_types +0 -0
  109. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestGrantsIntegration.test_grant_pagination +0 -0
  110. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[custom-grant_id,title,opportunity_number] +0 -0
  111. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[default-None] +0 -0
  112. {tango_python-0.2.0 → tango_python-0.3.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
  113. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[minimal-grant_id,opportunity_number,title,status(-),agency_code] +0 -0
  114. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[custom-notice_id,title,solicitation_number] +0 -0
  115. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[default-None] +0 -0
  116. {tango_python-0.2.0 → tango_python-0.3.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
  117. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
  118. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestNoticesIntegration.test_notice_field_types +0 -0
  119. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestNoticesIntegration.test_notice_pagination +0 -0
  120. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestNoticesIntegration.test_notice_with_meta_fields +0 -0
  121. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[custom-opportunity_id,title,solicitation_number] +0 -0
  122. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[default-None] +0 -0
  123. {tango_python-0.2.0 → tango_python-0.3.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
  124. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
  125. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestOpportunitiesIntegration.test_opportunity_field_types +0 -0
  126. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[custom-key,piid,description] +0 -0
  127. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[minimal-key,piid,award_date,recipient(display_name),description,total_contract_value] +0 -0
  128. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[ultra_minimal-key,piid,recipient(display_name),total_contract_value] +0 -0
  129. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
  130. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[with_address-uei,legal_business_name,cage_code,business_types,physical_address] +0 -0
  131. {tango_python-0.2.0 → tango_python-0.3.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
  132. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestTypeHintsIntegration.test_notices_dict_access[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
  133. {tango_python-0.2.0 → tango_python-0.3.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
  134. {tango_python-0.2.0 → tango_python-0.3.0}/tests/cassettes/TestTypeHintsIntegration.test_opportunities_dict_access[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
  135. {tango_python-0.2.0 → tango_python-0.3.0}/tests/conftest.py +0 -0
  136. {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/README.md +0 -0
  137. {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/__init__.py +0 -0
  138. {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/conftest.py +0 -0
  139. {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/test_agencies_integration.py +0 -0
  140. {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/test_edge_cases_integration.py +0 -0
  141. {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/test_entities_integration.py +0 -0
  142. {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/test_forecasts_integration.py +0 -0
  143. {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/test_grants_integration.py +0 -0
  144. {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/test_notices_integration.py +0 -0
  145. {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/test_opportunities_integration.py +0 -0
  146. {tango_python-0.2.0 → tango_python-0.3.0}/tests/integration/test_reference_data_integration.py +0 -0
  147. {tango_python-0.2.0 → tango_python-0.3.0}/tests/test_models.py +0 -0
  148. {tango_python-0.2.0 → tango_python-0.3.0}/tests/test_shapes.py +0 -0
@@ -3,6 +3,7 @@ name: Publish to PyPI
3
3
  on:
4
4
  release:
5
5
  types: [published]
6
+ workflow_dispatch:
6
7
 
7
8
  jobs:
8
9
  build-and-publish:
@@ -24,5 +25,5 @@ jobs:
24
25
 
25
26
  - name: Publish to PyPI
26
27
  env:
27
- UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
28
+ UV_PUBLISH_TOKEN: ${{ secrets.TANGO_PYPI_TOKEN }}
28
29
  run: uv publish
@@ -145,4 +145,6 @@ dmypy.json
145
145
  .DS_Store
146
146
  Thumbs.db
147
147
 
148
- .cursor/*
148
+ # Other
149
+ yoni/
150
+ .cursor/*
@@ -0,0 +1,27 @@
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
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ ### Changed
13
+
14
+ ## [0.3.0] - 2026-02-09
15
+
16
+ ### Added
17
+ - Vehicles endpoints: `list_vehicles`, `get_vehicle`, and `list_vehicle_awardees` (supports shaping + flattening). (refs `makegov/tango#1328`)
18
+ - 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`)
19
+ - Webhooks v2 client support: event type discovery, subscription CRUD, endpoint management, test delivery, and sample payload helpers. (refs `makegov/tango#1274`)
20
+
21
+ ### Changed
22
+ - Expanded explicit schemas to support common IDV shaping expansions (award offices, officers, period of performance, etc.).
23
+ - HTTP client now supports PATCH/DELETE helpers for webhook management endpoints.
24
+
25
+ ## [0.2.0] - 2025-11-16
26
+
27
+ - Entirely refactored SDK
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tango-python
3
- Version: 0.2.0
3
+ Version: 0.3.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
@@ -61,7 +61,7 @@ A modern Python SDK for the [Tango API](https://tango.makegov.com) by MakeGov, f
61
61
 
62
62
  - **Dynamic Response Shaping** - Request only the fields you need, reducing payload sizes by 60-80%
63
63
  - **Full Type Safety** - Runtime-generated TypedDict types with accurate type hints for IDE autocomplete
64
- - **Comprehensive API Coverage** - All major Tango API endpoints (contracts, entities, forecasts, opportunities, notices, grants) [Note: the current version does NOT implement all endpoints, we will be adding them incrementally]
64
+ - **Comprehensive API Coverage** - All major Tango API endpoints (contracts, entities, forecasts, opportunities, notices, grants, webhooks) [Note: the current version does NOT implement all endpoints, we will be adding them incrementally]
65
65
  - **Flexible Data Access** - Dictionary-based response objects with validation
66
66
  - **Modern Python** - Built for Python 3.12+ using modern async-ready patterns
67
67
  - **Production-Ready** - Comprehensive test suite with VCR.py-based integration tests
@@ -6,7 +6,7 @@ A modern Python SDK for the [Tango API](https://tango.makegov.com) by MakeGov, f
6
6
 
7
7
  - **Dynamic Response Shaping** - Request only the fields you need, reducing payload sizes by 60-80%
8
8
  - **Full Type Safety** - Runtime-generated TypedDict types with accurate type hints for IDE autocomplete
9
- - **Comprehensive API Coverage** - All major Tango API endpoints (contracts, entities, forecasts, opportunities, notices, grants) [Note: the current version does NOT implement all endpoints, we will be adding them incrementally]
9
+ - **Comprehensive API Coverage** - All major Tango API endpoints (contracts, entities, forecasts, opportunities, notices, grants, webhooks) [Note: the current version does NOT implement all endpoints, we will be adding them incrementally]
10
10
  - **Flexible Data Access** - Dictionary-based response objects with validation
11
11
  - **Modern Python** - Built for Python 3.12+ using modern async-ready patterns
12
12
  - **Production-Ready** - Comprehensive test suite with VCR.py-based integration tests
@@ -7,12 +7,15 @@ Complete reference for all Tango Python SDK methods and functionality.
7
7
  - [Client Initialization](#client-initialization)
8
8
  - [Agencies](#agencies)
9
9
  - [Contracts](#contracts)
10
+ - [IDVs](#idvs)
11
+ - [Vehicles](#vehicles)
10
12
  - [Entities](#entities)
11
13
  - [Forecasts](#forecasts)
12
14
  - [Opportunities](#opportunities)
13
15
  - [Notices](#notices)
14
16
  - [Grants](#grants)
15
17
  - [Business Types](#business-types)
18
+ - [Webhooks](#webhooks)
16
19
  - [Response Objects](#response-objects)
17
20
  - [Error Handling](#error-handling)
18
21
 
@@ -271,6 +274,119 @@ contracts = client.list_contracts(
271
274
 
272
275
  ---
273
276
 
277
+ ## Vehicles
278
+
279
+ Vehicles provide a solicitation-centric way to discover groups of related IDVs and (optionally) expand into the underlying awards via shaping.
280
+
281
+ ### list_vehicles()
282
+
283
+ List vehicles with optional vehicle-level full-text search.
284
+
285
+ ```python
286
+ vehicles = client.list_vehicles(
287
+ page=1,
288
+ limit=25,
289
+ search="GSA schedule",
290
+ shape=ShapeConfig.VEHICLES_MINIMAL,
291
+ flat=False,
292
+ flat_lists=False,
293
+ )
294
+ ```
295
+
296
+ **Parameters:**
297
+ - `page` (int): Page number (default: 1)
298
+ - `limit` (int): Results per page (default: 25, max: 100)
299
+ - `search` (str, optional): Vehicle-level search term
300
+ - `shape` (str, optional): Shape string (defaults to `ShapeConfig.VEHICLES_MINIMAL`)
301
+ - `flat` (bool): Flatten nested objects in shaped response
302
+ - `flat_lists` (bool): Flatten arrays using indexed keys
303
+ - `joiner` (str): Joiner used when `flat=True` (default: `"."`)
304
+
305
+ **Returns:** [PaginatedResponse](#paginatedresponse) with vehicle dictionaries
306
+
307
+ ### get_vehicle()
308
+
309
+ Get a single vehicle by UUID.
310
+
311
+ ```python
312
+ vehicle = client.get_vehicle(
313
+ uuid="00000000-0000-0000-0000-000000000001",
314
+ shape=ShapeConfig.VEHICLES_COMPREHENSIVE,
315
+ )
316
+ ```
317
+
318
+ **Notes:**
319
+ - On the vehicle detail endpoint, `search` filters **expanded awardees** when your `shape` includes `awardees(...)` (it does not filter the vehicle itself).
320
+
321
+ ### list_vehicle_awardees()
322
+
323
+ List the IDV awardees for a vehicle.
324
+
325
+ ```python
326
+ awardees = client.list_vehicle_awardees(
327
+ uuid="00000000-0000-0000-0000-000000000001",
328
+ shape=ShapeConfig.VEHICLE_AWARDEES_MINIMAL,
329
+ )
330
+ ```
331
+
332
+ ---
333
+
334
+ ## IDVs
335
+
336
+ IDVs (indefinite delivery vehicles) are the parent “vehicle award” records that can have child awards/orders under them.
337
+
338
+ ### list_idvs()
339
+
340
+ ```python
341
+ idvs = client.list_idvs(
342
+ limit=25,
343
+ cursor=None,
344
+ shape=ShapeConfig.IDVS_MINIMAL,
345
+ awarding_agency="4700",
346
+ )
347
+ ```
348
+
349
+ Notes:
350
+
351
+ - This endpoint uses **keyset pagination** (`cursor` + `limit`) rather than page numbers.
352
+
353
+ ### get_idv()
354
+
355
+ ```python
356
+ idv = client.get_idv("SOME_IDV_KEY", shape=ShapeConfig.IDVS_COMPREHENSIVE)
357
+ ```
358
+
359
+ ### list_idv_awards()
360
+
361
+ Lists child awards (contracts) under an IDV.
362
+
363
+ ```python
364
+ awards = client.list_idv_awards("SOME_IDV_KEY", limit=25)
365
+ ```
366
+
367
+ ### list_idv_child_idvs()
368
+
369
+ Lists child IDVs under an IDV.
370
+
371
+ ```python
372
+ children = client.list_idv_child_idvs("SOME_IDV_KEY", limit=25)
373
+ ```
374
+
375
+ ### list_idv_transactions()
376
+
377
+ ```python
378
+ tx = client.list_idv_transactions("SOME_IDV_KEY", limit=100)
379
+ ```
380
+
381
+ ### get_idv_summary() / list_idv_summary_awards()
382
+
383
+ ```python
384
+ summary = client.get_idv_summary("SOLICITATION_IDENTIFIER")
385
+ awards = client.list_idv_summary_awards("SOLICITATION_IDENTIFIER", limit=25)
386
+ ```
387
+
388
+ ---
389
+
274
390
  ## Entities
275
391
 
276
392
  Vendors, recipients, and organizations doing business with the government.
@@ -606,6 +722,135 @@ for biz_type in business_types.results:
606
722
 
607
723
  ---
608
724
 
725
+ ## Webhooks
726
+
727
+ Webhook APIs let **Large / Enterprise** users manage subscription filters for outbound Tango webhooks.
728
+
729
+ ### list_webhook_event_types()
730
+
731
+ Discover supported `event_type` values and subject types.
732
+
733
+ ```python
734
+ info = client.list_webhook_event_types()
735
+ print(info.event_types[0].event_type)
736
+ ```
737
+
738
+ ### list_webhook_subscriptions()
739
+
740
+ ```python
741
+ subs = client.list_webhook_subscriptions(page=1, page_size=25)
742
+ ```
743
+
744
+ Notes:
745
+
746
+ - This endpoint uses `page` + `page_size` (tier-capped) rather than `limit`.
747
+
748
+ ### create_webhook_subscription()
749
+
750
+ ```python
751
+ sub = client.create_webhook_subscription(
752
+ "Track specific vendors",
753
+ {
754
+ "records": [
755
+ {"event_type": "awards.new_award", "subject_type": "entity", "subject_ids": ["UEI123ABC"]},
756
+ {"event_type": "awards.new_transaction", "subject_type": "entity", "subject_ids": ["UEI123ABC"]},
757
+ ]
758
+ },
759
+ )
760
+ ```
761
+
762
+ Notes:
763
+
764
+ - Prefer v2 fields: `subject_type` + `subject_ids`.
765
+ - Legacy compatibility: `resource_ids` is accepted as an alias for `subject_ids` (don’t send both).
766
+ - Catch-all: `subject_ids: []` means “all subjects” for that record and is **Enterprise-only**. Large tier users must list specific IDs.
767
+
768
+ ### update_webhook_subscription()
769
+
770
+ ```python
771
+ sub = client.update_webhook_subscription("SUBSCRIPTION_UUID", subscription_name="Updated name")
772
+ ```
773
+
774
+ ### delete_webhook_subscription()
775
+
776
+ ```python
777
+ client.delete_webhook_subscription("SUBSCRIPTION_UUID")
778
+ ```
779
+
780
+ ### list_webhook_endpoints()
781
+
782
+ List your webhook endpoint(s).
783
+
784
+ ```python
785
+ endpoints = client.list_webhook_endpoints(page=1, limit=25)
786
+ ```
787
+
788
+ ### get_webhook_endpoint()
789
+
790
+ ```python
791
+ endpoint = client.get_webhook_endpoint("ENDPOINT_UUID")
792
+ ```
793
+
794
+ ### create_webhook_endpoint() / update_webhook_endpoint() / delete_webhook_endpoint()
795
+
796
+ In production, MakeGov provisions the initial endpoint for you. These are most useful for dev/self-service.
797
+
798
+ ```python
799
+ endpoint = client.create_webhook_endpoint("https://example.com/tango/webhooks")
800
+ endpoint = client.update_webhook_endpoint(endpoint.id, is_active=False)
801
+ client.delete_webhook_endpoint(endpoint.id)
802
+ ```
803
+
804
+ ### test_webhook_delivery()
805
+
806
+ Send an immediate test webhook to your configured endpoint.
807
+
808
+ ```python
809
+ result = client.test_webhook_delivery()
810
+ print(result.success, result.status_code)
811
+ ```
812
+
813
+ ### get_webhook_sample_payload()
814
+
815
+ Fetch Tango-shaped sample deliveries (and sample subscription request bodies).
816
+
817
+ ```python
818
+ sample = client.get_webhook_sample_payload(event_type="awards.new_award")
819
+ print(sample["event_type"])
820
+ ```
821
+
822
+ ### Deliveries / redelivery
823
+
824
+ The API does not currently expose a public `/api/webhooks/deliveries/` or redelivery endpoint. Use:
825
+
826
+ - `test_webhook_delivery()` for connectivity checks
827
+ - `get_webhook_sample_payload()` for building handlers + subscription payloads
828
+
829
+ ### Receiving webhooks (signature verification)
830
+
831
+ Every delivery includes an HMAC signature header:
832
+
833
+ - `X-Tango-Signature: sha256=<hex digest>`
834
+
835
+ Compute the digest over the **raw request body bytes** using your shared secret.
836
+
837
+ ```python
838
+ import hashlib
839
+ import hmac
840
+
841
+
842
+ def verify_tango_webhook_signature(secret: str, raw_body: bytes, signature_header: str | None) -> bool:
843
+ if not signature_header:
844
+ return False
845
+ sig = signature_header.strip()
846
+ if sig.startswith("sha256="):
847
+ sig = sig[len("sha256=") :]
848
+ expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
849
+ return hmac.compare_digest(expected, sig)
850
+ ```
851
+
852
+ ---
853
+
609
854
  ## Response Objects
610
855
 
611
856
  ### PaginatedResponse
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "tango-python"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "Python SDK for the Tango API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -97,6 +97,7 @@ markers = [
97
97
  "live: Tests that always use live API (skip cassettes)",
98
98
  "cached: Tests that only run with cached responses",
99
99
  "slow: Tests that are slow to execute",
100
+ "production: Production API smoke tests that run against live API",
100
101
  ]
101
102
 
102
103
  [tool.coverage.run]
@@ -0,0 +1,76 @@
1
+ # Development Scripts
2
+
3
+ This directory contains utility scripts used during development and maintenance of the tango-python library.
4
+
5
+ ## Scripts
6
+
7
+ ### Schema and API Tools
8
+
9
+ - **`fetch_api_schema.py`** - Fetches the OpenAPI schema from the Tango API and saves it locally
10
+ - **`generate_schemas_from_api.py`** - Generates schema definitions from the API reference (outputs to stdout)
11
+
12
+ ### Testing and Validation
13
+
14
+ - **`test_production.py`** - Runs production API smoke tests against the live API
15
+ - **`pr_review.py`** - Runs configurable validation checks for PR review (linting, type checking, tests)
16
+
17
+ ## Usage
18
+
19
+ These scripts are primarily for maintainers and are not part of the public API.
20
+
21
+ ### Production API Testing
22
+
23
+ Run smoke tests against the production API:
24
+
25
+ ```bash
26
+ # Requires TANGO_API_KEY environment variable
27
+ uv run python scripts/test_production.py
28
+ ```
29
+
30
+ This runs fast smoke tests (~30-60 seconds) that validate core SDK functionality against the live production API.
31
+
32
+ ### PR Review Validation
33
+
34
+ Run validation checks for PR review. **Automatically detects PR context** from GitHub Actions, GitHub CLI, or git branch.
35
+
36
+ ```bash
37
+ # Auto-detect PR and run validation (default: production mode)
38
+ uv run python scripts/pr_review.py
39
+
40
+ # Review a specific PR number
41
+ uv run python scripts/pr_review.py --pr-number 123
42
+
43
+ # Smoke tests only
44
+ uv run python scripts/pr_review.py --mode smoke
45
+
46
+ # Quick validation (linting + type checking, no tests)
47
+ uv run python scripts/pr_review.py --mode quick
48
+
49
+ # Full validation (all checks including all tests)
50
+ uv run python scripts/pr_review.py --mode full
51
+
52
+ # Check only changed files (auto-enabled when PR is detected)
53
+ uv run python scripts/pr_review.py --mode production --changed-files-only
54
+ ```
55
+
56
+ **Validation Modes:**
57
+ - `smoke` - Production API smoke tests only
58
+ - `quick` - Linting + type checking (no tests)
59
+ - `full` - All checks (linting + type checking + all tests)
60
+ - `production` - Production API smoke tests + linting + type checking (default)
61
+
62
+ **PR Detection:**
63
+ The script automatically detects PR information from:
64
+ - GitHub Actions environment variables (`GITHUB_EVENT_PATH`, `GITHUB_PR_NUMBER`)
65
+ - GitHub CLI (`gh pr view` - requires `gh auth login`)
66
+ - Current git branch
67
+
68
+ When a PR is detected, the script displays PR information and automatically:
69
+ - Detects the base branch from the PR
70
+ - Checks only changed files
71
+ - Shows PR URL in summary
72
+
73
+ ## Requirements
74
+
75
+ - `TANGO_API_KEY` environment variable (required for production API tests)
76
+ - All dependencies installed: `uv sync --all-extras`