tango-python 1.2.0__tar.gz → 1.4.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 (208) hide show
  1. tango_python-1.4.0/.github/workflows/lint.yml +110 -0
  2. {tango_python-1.2.0 → tango_python-1.4.0}/CHANGELOG.md +127 -0
  3. {tango_python-1.2.0 → tango_python-1.4.0}/PKG-INFO +1 -1
  4. {tango_python-1.2.0 → tango_python-1.4.0}/docs/WEBHOOKS.md +17 -0
  5. {tango_python-1.2.0 → tango_python-1.4.0}/pyproject.toml +1 -1
  6. tango_python-1.4.0/scripts/README.md +114 -0
  7. tango_python-1.4.0/scripts/check_filter_shape_conformance.py +601 -0
  8. tango_python-1.4.0/scripts/check_shape_coverage.py +357 -0
  9. tango_python-1.4.0/scripts/generate_shape_overlay.py +301 -0
  10. {tango_python-1.2.0 → tango_python-1.4.0}/scripts/pr_review.py +4 -3
  11. tango_python-1.4.0/scripts/probe_shape_types.py +170 -0
  12. tango_python-1.4.0/scripts/refresh_contract.py +103 -0
  13. {tango_python-1.2.0 → tango_python-1.4.0}/tango/__init__.py +13 -1
  14. {tango_python-1.2.0 → tango_python-1.4.0}/tango/client.py +811 -1
  15. {tango_python-1.2.0 → tango_python-1.4.0}/tango/exceptions.py +18 -1
  16. {tango_python-1.2.0 → tango_python-1.4.0}/tango/models.py +225 -0
  17. tango_python-1.4.0/tango/shapes/generated_overlay.py +2430 -0
  18. {tango_python-1.2.0 → tango_python-1.4.0}/tango/shapes/schema.py +16 -0
  19. {tango_python-1.2.0 → tango_python-1.4.0}/tests/production/test_production_smoke.py +20 -0
  20. {tango_python-1.2.0 → tango_python-1.4.0}/tests/test_client.py +137 -0
  21. tango_python-1.4.0/tests/test_dibbs_exclusions_sbir.py +203 -0
  22. tango_python-1.4.0/tests/test_shape_coverage_gate.py +108 -0
  23. {tango_python-1.2.0 → tango_python-1.4.0}/tests/test_shapes.py +1 -3
  24. tango_python-1.2.0/.github/workflows/lint.yml +0 -94
  25. tango_python-1.2.0/scripts/README.md +0 -100
  26. tango_python-1.2.0/scripts/check_filter_shape_conformance.py +0 -286
  27. {tango_python-1.2.0 → tango_python-1.4.0}/.github/workflows/docs-dispatch.yml +0 -0
  28. {tango_python-1.2.0 → tango_python-1.4.0}/.github/workflows/publish.yml +0 -0
  29. {tango_python-1.2.0 → tango_python-1.4.0}/.github/workflows/test.yml +0 -0
  30. {tango_python-1.2.0 → tango_python-1.4.0}/.gitignore +0 -0
  31. {tango_python-1.2.0 → tango_python-1.4.0}/LICENSE +0 -0
  32. {tango_python-1.2.0 → tango_python-1.4.0}/README.md +0 -0
  33. {tango_python-1.2.0 → tango_python-1.4.0}/docs/API_REFERENCE.md +0 -0
  34. {tango_python-1.2.0 → tango_python-1.4.0}/docs/CLIENT.md +0 -0
  35. {tango_python-1.2.0 → tango_python-1.4.0}/docs/DYNAMIC_MODELS.md +0 -0
  36. {tango_python-1.2.0 → tango_python-1.4.0}/docs/ERRORS.md +0 -0
  37. {tango_python-1.2.0 → tango_python-1.4.0}/docs/PAGINATION.md +0 -0
  38. {tango_python-1.2.0 → tango_python-1.4.0}/docs/SHAPES.md +0 -0
  39. {tango_python-1.2.0 → tango_python-1.4.0}/scripts/fetch_api_schema.py +0 -0
  40. {tango_python-1.2.0 → tango_python-1.4.0}/scripts/generate_schemas_from_api.py +0 -0
  41. {tango_python-1.2.0 → tango_python-1.4.0}/scripts/smoke_api_parity.py +0 -0
  42. {tango_python-1.2.0 → tango_python-1.4.0}/scripts/test_production.py +0 -0
  43. {tango_python-1.2.0 → tango_python-1.4.0}/tango/shapes/__init__.py +0 -0
  44. {tango_python-1.2.0 → tango_python-1.4.0}/tango/shapes/explicit_schemas.py +0 -0
  45. {tango_python-1.2.0 → tango_python-1.4.0}/tango/shapes/factory.py +0 -0
  46. {tango_python-1.2.0 → tango_python-1.4.0}/tango/shapes/generator.py +0 -0
  47. {tango_python-1.2.0 → tango_python-1.4.0}/tango/shapes/models.py +0 -0
  48. {tango_python-1.2.0 → tango_python-1.4.0}/tango/shapes/parser.py +0 -0
  49. {tango_python-1.2.0 → tango_python-1.4.0}/tango/shapes/types.py +0 -0
  50. {tango_python-1.2.0 → tango_python-1.4.0}/tango/webhooks/__init__.py +0 -0
  51. {tango_python-1.2.0 → tango_python-1.4.0}/tango/webhooks/cli.py +0 -0
  52. {tango_python-1.2.0 → tango_python-1.4.0}/tango/webhooks/receiver.py +0 -0
  53. {tango_python-1.2.0 → tango_python-1.4.0}/tango/webhooks/signing.py +0 -0
  54. {tango_python-1.2.0 → tango_python-1.4.0}/tango/webhooks/simulate.py +0 -0
  55. {tango_python-1.2.0 → tango_python-1.4.0}/tests/__init__.py +0 -0
  56. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestAgenciesIntegration.test_get_agency +0 -0
  57. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestAgenciesIntegration.test_list_agencies +0 -0
  58. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_field_type_validation +0 -0
  59. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_parsing_consistency +0 -0
  60. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestBusinessTypesIntegration.test_list_business_types +0 -0
  61. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_combined_filters_work_together +0 -0
  62. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_contract_cursor_pagination +0 -0
  63. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_contract_data_object_parsing +0 -0
  64. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_contract_field_types +0 -0
  65. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[keyword-software] +0 -0
  66. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[psc_code-R425] +0 -0
  67. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_awarding_agency_filter +0 -0
  68. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_date_range_filter +0 -0
  69. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_flat +0 -0
  70. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_naics_code_filter +0 -0
  71. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[custom-key,piid,recipient(display_name),total_contract_value,award_date] +0 -0
  72. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[default-None] +0 -0
  73. {tango_python-1.2.0 → tango_python-1.4.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
  74. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[minimal-key,piid,award_date,recipient(display_name),description,total_contract_value] +0 -0
  75. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_new_expiring_filters +0 -0
  76. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_new_fiscal_year_range_filters +0 -0
  77. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_new_identifier_filters +0 -0
  78. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_search_contracts_with_filters +0 -0
  79. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_search_filters_object_with_new_parameters +0 -0
  80. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[asc-] +0 -0
  81. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[desc--] +0 -0
  82. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_contracts +0 -0
  83. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_entities +0 -0
  84. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_date_field_parsing_edge_cases +0 -0
  85. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_decimal_field_parsing_edge_cases +0 -0
  86. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_empty_list_responses +0 -0
  87. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_entity_parsing_with_various_address_formats +0 -0
  88. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_flattened_responses_with_flat_lists +0 -0
  89. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_list_field_parsing_consistency +0 -0
  90. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_nested_objects_with_missing_data +0 -0
  91. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_null_missing_fields_in_contracts +0 -0
  92. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_with_minimal_shape_sparse_data +0 -0
  93. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEntitiesIntegration.test_entity_field_types +0 -0
  94. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEntitiesIntegration.test_entity_location_parsing +0 -0
  95. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEntitiesIntegration.test_entity_parsing_with_business_types +0 -0
  96. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEntitiesIntegration.test_entity_with_various_identifiers +0 -0
  97. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_budget_flows +0 -0
  98. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_by_uei +0 -0
  99. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_flat +0 -0
  100. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_search +0 -0
  101. {tango_python-1.2.0 → tango_python-1.4.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
  102. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[custom-uei,legal_business_name,cage_code] +0 -0
  103. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
  104. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[with_address-uei,legal_business_name,cage_code,business_types,physical_address] +0 -0
  105. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestForecastsIntegration.test_forecast_field_types +0 -0
  106. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[custom-id,title,anticipated_award_date] +0 -0
  107. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[default-None] +0 -0
  108. {tango_python-1.2.0 → tango_python-1.4.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
  109. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[minimal-id,title,anticipated_award_date,fiscal_year,naics_code,status] +0 -0
  110. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestGrantsIntegration.test_grant_field_types +0 -0
  111. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestGrantsIntegration.test_grant_pagination +0 -0
  112. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[custom-grant_id,title,opportunity_number] +0 -0
  113. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[default-None] +0 -0
  114. {tango_python-1.2.0 → tango_python-1.4.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
  115. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[minimal-grant_id,opportunity_number,title,status(-),agency_code] +0 -0
  116. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestIDVsIntegration.test_get_idv_uses_default_shape +0 -0
  117. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_awards_uses_default_shape +0 -0
  118. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_child_idvs_uses_default_shape +0 -0
  119. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_transactions +0 -0
  120. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestIDVsIntegration.test_list_idvs_uses_default_shape_and_keyset_params +0 -0
  121. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_code +0 -0
  122. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_name_text +0 -0
  123. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating +0 -0
  124. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating_max +0 -0
  125. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_performance_risk +0 -0
  126. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_type_of_investment +0 -0
  127. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_updated_time_range +0 -0
  128. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_funding_and_cio_evaluation_expansions +0 -0
  129. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_get_itdashboard_investment_by_uii +0 -0
  130. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_itdashboard_pagination +0 -0
  131. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_search +0 -0
  132. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[custom-uii,agency_name,investment_title,updated_time] +0 -0
  133. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[default-None] +0 -0
  134. {tango_python-1.2.0 → tango_python-1.4.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
  135. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestNaicsIntegration.test_list_naics +0 -0
  136. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[custom-notice_id,title,solicitation_number] +0 -0
  137. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[default-None] +0 -0
  138. {tango_python-1.2.0 → tango_python-1.4.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
  139. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
  140. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestNoticesIntegration.test_notice_field_types +0 -0
  141. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestNoticesIntegration.test_notice_pagination +0 -0
  142. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestNoticesIntegration.test_notice_with_meta_fields +0 -0
  143. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestOTAsIntegration.test_get_ota +0 -0
  144. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestOTAsIntegration.test_list_otas +0 -0
  145. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestOTIDVsIntegration.test_get_otidv +0 -0
  146. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestOTIDVsIntegration.test_list_otidvs +0 -0
  147. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestOfficesIntegration.test_get_office +0 -0
  148. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestOfficesIntegration.test_list_offices +0 -0
  149. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[custom-opportunity_id,title,solicitation_number] +0 -0
  150. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[default-None] +0 -0
  151. {tango_python-1.2.0 → tango_python-1.4.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
  152. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
  153. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestOpportunitiesIntegration.test_opportunity_field_types +0 -0
  154. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestOrganizationsIntegration.test_get_organization +0 -0
  155. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestOrganizationsIntegration.test_list_organizations +0 -0
  156. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestProtestsIntegration.test_get_protest_by_case_id +0 -0
  157. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_filter +0 -0
  158. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[custom-case_id,title,source_system,outcome] +0 -0
  159. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[default-None] +0 -0
  160. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[minimal-case_id,case_number,title,source_system,outcome,filed_date] +0 -0
  161. {tango_python-1.2.0 → tango_python-1.4.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
  162. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestProtestsIntegration.test_protest_pagination +0 -0
  163. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestSubawardsIntegration.test_list_subawards +0 -0
  164. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[custom-key,piid,description] +0 -0
  165. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[minimal-key,piid,award_date,recipient(display_name),description,total_contract_value] +0 -0
  166. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[ultra_minimal-key,piid,recipient(display_name),total_contract_value] +0 -0
  167. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
  168. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[with_address-uei,legal_business_name,cage_code,business_types,physical_address] +0 -0
  169. {tango_python-1.2.0 → tango_python-1.4.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
  170. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestTypeHintsIntegration.test_notices_dict_access[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
  171. {tango_python-1.2.0 → tango_python-1.4.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
  172. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestTypeHintsIntegration.test_opportunities_dict_access[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
  173. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists +0 -0
  174. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_with_metrics_expansion +0 -0
  175. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape +0 -0
  176. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_orders_uses_default_shape +0 -0
  177. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search +0 -0
  178. {tango_python-1.2.0 → tango_python-1.4.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_with_ordering +0 -0
  179. {tango_python-1.2.0 → tango_python-1.4.0}/tests/conftest.py +0 -0
  180. {tango_python-1.2.0 → tango_python-1.4.0}/tests/integration/README.md +0 -0
  181. {tango_python-1.2.0 → tango_python-1.4.0}/tests/integration/__init__.py +0 -0
  182. {tango_python-1.2.0 → tango_python-1.4.0}/tests/integration/conftest.py +0 -0
  183. {tango_python-1.2.0 → tango_python-1.4.0}/tests/integration/test_agencies_integration.py +0 -0
  184. {tango_python-1.2.0 → tango_python-1.4.0}/tests/integration/test_contracts_integration.py +0 -0
  185. {tango_python-1.2.0 → tango_python-1.4.0}/tests/integration/test_edge_cases_integration.py +0 -0
  186. {tango_python-1.2.0 → tango_python-1.4.0}/tests/integration/test_entities_integration.py +0 -0
  187. {tango_python-1.2.0 → tango_python-1.4.0}/tests/integration/test_forecasts_integration.py +0 -0
  188. {tango_python-1.2.0 → tango_python-1.4.0}/tests/integration/test_grants_integration.py +0 -0
  189. {tango_python-1.2.0 → tango_python-1.4.0}/tests/integration/test_itdashboard_integration.py +0 -0
  190. {tango_python-1.2.0 → tango_python-1.4.0}/tests/integration/test_naics_integration.py +0 -0
  191. {tango_python-1.2.0 → tango_python-1.4.0}/tests/integration/test_notices_integration.py +0 -0
  192. {tango_python-1.2.0 → tango_python-1.4.0}/tests/integration/test_offices_integration.py +0 -0
  193. {tango_python-1.2.0 → tango_python-1.4.0}/tests/integration/test_opportunities_integration.py +0 -0
  194. {tango_python-1.2.0 → tango_python-1.4.0}/tests/integration/test_organizations_integration.py +0 -0
  195. {tango_python-1.2.0 → tango_python-1.4.0}/tests/integration/test_otas_otidvs_integration.py +0 -0
  196. {tango_python-1.2.0 → tango_python-1.4.0}/tests/integration/test_protests_integration.py +0 -0
  197. {tango_python-1.2.0 → tango_python-1.4.0}/tests/integration/test_reference_data_integration.py +0 -0
  198. {tango_python-1.2.0 → tango_python-1.4.0}/tests/integration/test_subawards_integration.py +0 -0
  199. {tango_python-1.2.0 → tango_python-1.4.0}/tests/integration/test_vehicles_idvs_integration.py +0 -0
  200. {tango_python-1.2.0 → tango_python-1.4.0}/tests/integration/validation.py +0 -0
  201. {tango_python-1.2.0 → tango_python-1.4.0}/tests/production/__init__.py +0 -0
  202. {tango_python-1.2.0 → tango_python-1.4.0}/tests/production/conftest.py +0 -0
  203. {tango_python-1.2.0 → tango_python-1.4.0}/tests/test_api_parity.py +0 -0
  204. {tango_python-1.2.0 → tango_python-1.4.0}/tests/test_models.py +0 -0
  205. {tango_python-1.2.0 → tango_python-1.4.0}/tests/test_webhooks_cli.py +0 -0
  206. {tango_python-1.2.0 → tango_python-1.4.0}/tests/test_webhooks_receiver.py +0 -0
  207. {tango_python-1.2.0 → tango_python-1.4.0}/tests/test_webhooks_signing.py +0 -0
  208. {tango_python-1.2.0 → tango_python-1.4.0}/tests/test_webhooks_simulate.py +0 -0
@@ -0,0 +1,110 @@
1
+ name: Linting
2
+
3
+ # Lint gate runs on every PR and push to main.
4
+ #
5
+ # - ruff format + ruff check are HARD gates (block the PR).
6
+ # - mypy is a HARD gate: the package type-checks cleanly under strict mypy.
7
+ # (The earlier ~28-error burn-down is complete.)
8
+ # - The SDK filter/shape conformance check is a HARD gate: it runs against the
9
+ # vendored contract at contracts/filter_shape_contract.json on every run (no
10
+ # secrets needed, works on forks). A second, token-gated step compares the
11
+ # vendored contract against the tango repo's HEAD and emits a staleness
12
+ # notice (never a failure — tango HEAD may carry unreleased changes).
13
+ # Refresh the vendored contract with scripts/refresh_contract.py.
14
+ # - Reverse shape-coverage is a HARD gate: check_shape_coverage.py fails when
15
+ # Tango's shape trees expose a field/expand the SDK schema doesn't capture and
16
+ # it isn't in contracts/shape_coverage_baseline.json. Also offline (vendored
17
+ # contract), so it runs on forks. Burn the baseline down with the generator.
18
+ on:
19
+ workflow_dispatch:
20
+ push:
21
+ branches: [ main ]
22
+ pull_request:
23
+ branches: [ main ]
24
+
25
+ jobs:
26
+ lint:
27
+ runs-on: ubuntu-latest
28
+
29
+ steps:
30
+ - uses: actions/checkout@v6
31
+
32
+ - name: Install uv
33
+ uses: astral-sh/setup-uv@v8.1.0
34
+ with:
35
+ version: "latest"
36
+
37
+ - name: Set up Python
38
+ run: uv python install 3.12
39
+
40
+ - name: Install dependencies
41
+ run: uv sync --all-extras
42
+
43
+ - name: Check formatting with ruff
44
+ run: uv run ruff format --check tango/
45
+
46
+ - name: Lint with ruff
47
+ run: uv run ruff check tango/
48
+
49
+ - name: Type check with mypy
50
+ run: uv run mypy tango/
51
+
52
+ conformance:
53
+ # Hard gate against the vendored contract (contracts/filter_shape_contract.json).
54
+ # Runs unconditionally — no secrets required, so forks and tokenless runs
55
+ # get the full check instead of a silent skip.
56
+ runs-on: ubuntu-latest
57
+
58
+ steps:
59
+ - uses: actions/checkout@v6
60
+
61
+ - name: Install uv
62
+ uses: astral-sh/setup-uv@v8.1.0
63
+ with:
64
+ version: "latest"
65
+
66
+ - name: Set up Python
67
+ run: uv python install 3.12
68
+
69
+ - name: Install dependencies
70
+ run: uv sync --all-extras
71
+
72
+ - name: Check SDK filter/shape conformance (vendored contract)
73
+ run: uv run python scripts/check_filter_shape_conformance.py
74
+
75
+ - name: Check reverse shape coverage (Tango exposes -> SDK captures)
76
+ # Complements the conformance check with the OTHER direction: fails when
77
+ # Tango's shape trees expose a field/expand the SDK schema doesn't capture
78
+ # and it isn't in contracts/shape_coverage_baseline.json. Also offline
79
+ # against the vendored contract — no secrets, works on forks.
80
+ run: uv run python scripts/check_shape_coverage.py
81
+
82
+ # --- Staleness notice (best-effort, never fails the job) ---------------
83
+ - name: Determine token availability
84
+ id: gate
85
+ env:
86
+ TANGO_API_REPO_ACCESS_TOKEN: ${{ secrets.TANGO_API_REPO_ACCESS_TOKEN }}
87
+ run: |
88
+ if [ -n "$TANGO_API_REPO_ACCESS_TOKEN" ]; then
89
+ echo "ready=true" >> "$GITHUB_OUTPUT"
90
+ else
91
+ echo "ready=false" >> "$GITHUB_OUTPUT"
92
+ echo "::notice::Contract staleness check skipped — TANGO_API_REPO_ACCESS_TOKEN not configured."
93
+ fi
94
+
95
+ - name: Checkout tango API repo (contract source)
96
+ if: steps.gate.outputs.ready == 'true'
97
+ uses: actions/checkout@v6
98
+ with:
99
+ repository: makegov/tango
100
+ path: tango-api
101
+ token: ${{ secrets.TANGO_API_REPO_ACCESS_TOKEN }}
102
+
103
+ - name: Compare vendored contract against tango HEAD
104
+ if: steps.gate.outputs.ready == 'true'
105
+ run: |
106
+ if ! diff -q contracts/filter_shape_contract.json tango-api/contracts/filter_shape_contract.json >/dev/null; then
107
+ echo "::warning::Vendored contract differs from makegov/tango HEAD. Refresh with: uv run python scripts/refresh_contract.py"
108
+ else
109
+ echo "Vendored contract matches makegov/tango HEAD."
110
+ fi
@@ -7,6 +7,133 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.4.0] - 2026-07-20
11
+
12
+ ### Added
13
+ - `list_protests()` now accepts `naics_code`, filtering protests by the solicitation's NAICS code. The API has always supported it (verified live: 15,027 protests unfiltered, 12 for `541519`, 2 for `336411`), but `protests` was absent from the conformance checker's resource-to-method map, so the gap was never reported.
14
+ - `list_psc()` now accepts `has_awards`, restricting results to codes with contract award history (2,526 PSC codes unfiltered, 2,243 with awards). `False` is sent explicitly rather than dropped, since the API distinguishes the two. This param has always worked but was invisible to the contract until [makegov/tango#2948](https://github.com/makegov/tango/pull/2948) added the `view_handled_params` declaration for params a viewset reads straight from `query_params`. That PR also fixed a truthiness bug where `has_awards=false` filtered identically to `true`, and is deployed — verified live at 2,526 for `False` and 2,243 for `True`.
15
+
16
+ One caveat that is not an SDK issue: `/api/psc/` responses are cached for 30 days per query-param key, so any `?has_awards=` key requested *before* that deploy still serves the old awards-only result until its entry expires or is invalidated.
17
+
18
+ ### Fixed
19
+ - **The reverse shape-coverage gate was blind to seven resources, and the SDK's schemas had silently drifted behind four of them.** The gate skipped any resource whose contract shape tree was falsy (`if not shape: continue`). Tango published `"shape": null` for `entities`, `opportunities`, `notices`, `protests`, `itdashboard`, `events`, and `news` — five of them because its contract generator crashed on tier-aware viewsets ([makegov/tango#2944](https://github.com/makegov/tango/pull/2944)) — so the gate reported full coverage while checking nothing for them. Re-vendoring the fixed contract surfaced **69 real gaps** (36 fields, 20 expands, 13 flattened expands) across entities, notices, protests, and itdashboard, all now closed by a regenerated overlay.
20
+
21
+ The user-visible consequence was worse than missing schema entries. Because `ShapeParser` validates client-side, the SDK *rejected* 13 fields the API accepts, before ever issuing a request — `list_entities(shape="display_name")` raised `ShapeValidationError` against an endpoint that returns 200 for it. Affected: `Entity`'s `display_name`, `entity_type`, `entity_structure`, `organization_structure`, `profit_structure`, `purpose_of_registration`, `country_of_incorporation`, `past_performance`, `registered`, `sba_business_types`, and `Protest`'s `decisions`, `resolved_agency`, `resolved_protester`. All 13 now validate. Note that `registered` and `sba_business_types` regressed *in 1.3.0* — they validated under 1.1.1 — so the overlay generation had narrowed `Entity` while nothing was watching.
22
+
23
+ The gate no longer skips silently: a resource declaring `shape_supported: true` with no shape tree is now a hard finding (`contract_missing_shape`) reported as an upstream contract defect rather than an SDK one. Contracts predating `schema_version: 2` omit the key, where a null tree stays genuinely ambiguous and skipping remains correct.
24
+ - **Seven resources with working SDK methods were never conformance-checked at all.** `RESOURCE_TO_METHOD` omitted `protests`, `psc`, `mas_sins`, `departments`, `business_types`, and `assistance_listings` entirely, and mapped `offices` to `None` as "not yet implemented" even though `list_offices` exists. Their filter coverage had never been validated. Wiring them up found one real gap (`protests.naics_code`, above); the other six were already complete. `events` and `news` stay mapped to `None` — genuinely absent from the SDK, now with a note explaining that `list_webhook_event_types` is unrelated.
25
+ - **`refresh_contract.py` + `probe_shape_types.py` re-run against the corrected contract.** `contracts/observed_shape_types.json` gained live-sampled types for the five newly-visible resources (entities alone contributes 136 observed paths), so the regenerated overlay resolves their types from real API responses rather than name heuristics.
26
+
27
+ ### Changed
28
+ - `CONTRACT_OMITTED_PARAMS` now warns when an entry is no longer needed, mirroring how baseline entries are burned down, and the list is now **empty** — which is the goal state. It briefly held `mas_sins: search`, added because the API applies it (330 results unfiltered, 0 for a nonsense term, 32 for `office`) while the contract recorded `filter_params: []`, so the checker would otherwise have flagged a working parameter as a silent no-op and invited its removal. Both that entry and `budget/accounts: search` were retired by the new warning once [makegov/tango#2944](https://github.com/makegov/tango/pull/2944) and [#2948](https://github.com/makegov/tango/pull/2948) taught the contract generator to publish them. The carve-out mechanism stays for the next such case.
29
+
30
+ ## [1.3.0] - 2026-07-19
31
+
32
+ ### Added
33
+ - **DIBBS, exclusions, and SBIR/STTR endpoint support.** Six endpoint families
34
+ Tango shipped in v4.16–v4.18 had no SDK support at all — no models, no methods.
35
+ Added `list_dibbs_rfqs`/`get_dibbs_rfq`, `list_dibbs_rfps`/`get_dibbs_rfp`,
36
+ `list_dibbs_awards`/`get_dibbs_award`, `list_exclusions`/`get_exclusion`,
37
+ `list_sbir_topics`/`get_sbir_topic`, and
38
+ `list_sbir_solicitations`/`get_sbir_solicitation`, with all 85 filter params,
39
+ shape schemas, and `ShapeConfig` defaults. New models: `DibbsRfq`, `DibbsRfp`,
40
+ `DibbsAward`, `Exclusion`, `SbirTopic`, `SbirSolicitation`.
41
+
42
+ Two API behaviors are worth knowing. `is_open` (DIBBS) and
43
+ `is_currently_excluded` (exclusions) are derived at query time, so filter with
44
+ the `open` / `active` kwargs rather than shaping on those fields. And DIBBS
45
+ `total_contract_price` is the *order* total repeated on every line item —
46
+ never sum it across rows; deduplicate on award + delivery-order number first.
47
+ - **Reverse shape-coverage: the SDK now captures every field and expand the API
48
+ returns.** The conformance check only validated one direction — that the SDK's
49
+ shape constants reference *allowed* fields. Nothing checked the reverse, so the
50
+ hand-maintained `tango/shapes/explicit_schemas.py` had silently fallen ~200+
51
+ fields behind the API: 209 leaf fields, 41 whole nested expand branches, and 3
52
+ resources (naics, psc, mas_sins) the typed shape API could not request at all.
53
+ A new generated overlay (`tango/shapes/generated_overlay.py`, produced by
54
+ `scripts/generate_shape_overlay.py` and merged over the base by `SchemaRegistry`)
55
+ closes all of them, with types resolved from live-API sampling rather than
56
+ guessed. Notable now-shapeable data: contract/IDV acquisition attributes
57
+ (`fair_opportunity_limited_sources`, `subcontracting_plan`, …), `contracts.officers`
58
+ and `period_of_performance`, the full `organizations` hierarchy
59
+ (`obligation_rank`, `l1..l8_fh_key`, `budget_appropriation`, `children`/`parent`),
60
+ `otas`/`otidvs` `transactions`, and the deep `vehicles.awardees[.orders]` tree.
61
+ Code-object expands (`set_aside`, `award_type`, `idv_type`, …), previously
62
+ modeled inconsistently as `str`/bare `dict`, now uniformly resolve to
63
+ `{code, description}`.
64
+ - **Reverse shape-coverage gate.** `scripts/check_shape_coverage.py` walks Tango's
65
+ shape trees (from the vendored contract) against the SDK schemas and fails when
66
+ the SDK misses anything the API exposes and it isn't in
67
+ `contracts/shape_coverage_baseline.json`. Offline against the vendored contract —
68
+ no token, runs on forks — and wired into the `lint.yml` conformance job.
69
+ Regenerate the overlay with `scripts/generate_shape_overlay.py` (from the vendored
70
+ contract + `contracts/observed_shape_types.json`, no API key); refresh the type
71
+ observations with `scripts/probe_shape_types.py` (maintainer-run, needs a key).
72
+ - **Contract-first conformance system.** The canonical API filter/shape
73
+ contract is now vendored at `contracts/filter_shape_contract.json` (refresh
74
+ with the new `scripts/refresh_contract.py`), so the conformance check runs
75
+ out of the box — locally, in CI, and on forks — with no tango checkout or
76
+ access token. `scripts/check_filter_shape_conformance.py` gained three new
77
+ checks on top of filter coverage: **staleness** (an SDK filter argument the
78
+ API no longer accepts is an error — it would silently no-op), **types**
79
+ (each argument's annotation is validated against the contract's
80
+ `schema_version: 2` per-filter type metadata), and a **known-gaps baseline**
81
+ (`contracts/conformance_baseline.json`) that downgrades accepted missing
82
+ params from errors to warnings so backlog is tracked instead of silent. A
83
+ new `--suggest` flag prints ready-to-paste typed parameter scaffolds for
84
+ any missing filters. The first run against the current API surface found 7
85
+ real coverage gaps, now baselined: `key` on contracts/IDVs/OTAs/OTIDVs,
86
+ `cage` on entities, `id` on forecasts, and `opportunity_id` on
87
+ opportunities.
88
+ - `TangoValidationError` now exposes the API's structured validation details
89
+ directly: `.issues` (the list of `{"path": ..., "reason": ...}` entries the
90
+ server returns for shape errors) and `.available_fields` (the endpoint's
91
+ valid field set, when included). Both were previously reachable only by
92
+ digging through `.response_data`. ([#45](https://github.com/makegov/tango-python/issues/45))
93
+
94
+ ### Changed
95
+ - The CI conformance job (`lint.yml`) now runs unconditionally against the
96
+ vendored contract instead of silently skipping when
97
+ `TANGO_API_REPO_ACCESS_TOKEN` is absent; the token is only used for a
98
+ best-effort staleness notice comparing the vendored contract to tango HEAD.
99
+ `scripts/pr_review.py` likewise defaults its conformance step to the
100
+ vendored contract (override with `TANGO_CONTRACT_MANIFEST`).
101
+ - 400 error messages now name the rejected field(s) and reason when the API
102
+ returns structured `issues` — e.g.
103
+ `Invalid request parameters: Invalid shape: tradeoff_process (unknown_field)`
104
+ instead of just `Invalid request parameters: Invalid shape`. ([#45](https://github.com/makegov/tango-python/issues/45))
105
+
106
+ ### Fixed
107
+ - **`docs/WEBHOOKS.md` listed only five of the eight webhook alert types.** It
108
+ was missing `alerts.exclusion.match`, `alerts.dibbs_rfq.match`, and
109
+ `alerts.dibbs_rfp.match`. No SDK change was needed — event types are served by
110
+ the API and never hardcoded, so the new types already worked — but the
111
+ hand-written list had gone stale. Also documents two things that surprise
112
+ people: DIBBS *awards* are not alertable, and nothing fires when a record
113
+ merely lapses (an exclusion hitting its termination date, or an RFQ/RFP
114
+ passing its close date, emits no event). A production smoke test now asserts
115
+ every live event type appears in the doc, so the list cannot silently fall
116
+ behind again.
117
+ - **Refreshed the vendored API contract, which had gone stale by seven
118
+ resources.** It tracked 25 resources against Tango's current 32, so the
119
+ conformance and coverage checks were validating against out-of-date truth and
120
+ could not see DIBBS, exclusions, SBIR, or `budget/accounts` at all. Refreshing
121
+ it also surfaced 72 additional fields on existing resources, now covered.
122
+ Nested routes are keyed with a slash (`budget/accounts`), which silently broke
123
+ the old `budget_accounts` mapping — both keys are now accepted.
124
+ - **`check_filter_shape_conformance.py` no longer reports false stale params.**
125
+ It resolved SDK arguments to API params only through an explicit
126
+ `api_param_mapping` dict, so methods that express the translation as tuple
127
+ tables — `("account_title__icontains", account_title)` and
128
+ `range_filters = (("apportioned", apportioned, apportioned_gte, ...))` — looked
129
+ like they exposed dozens of params the API rejects. They do not:
130
+ `list_budget_accounts` correctly sends `apportioned__gte`, `fiscal_year__lte`,
131
+ and `account_title__icontains`. The checker now understands both tuple forms
132
+ (in `Assign`, `AnnAssign`, and inline `for` iterables), with an explicit
133
+ `api_param_mapping` still taking precedence. This let `budget/accounts` be
134
+ conformance-checked for the first time; its genuinely missing lookup variants
135
+ (`agency_code__in`, `bureau_name__icontains`, …) are now baselined.
136
+
10
137
  ## [1.2.0] - 2026-06-05
11
138
 
12
139
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tango-python
3
- Version: 1.2.0
3
+ Version: 1.4.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
@@ -84,8 +84,25 @@ tango webhooks list-event-types
84
84
  # alerts.entity.match Entity matched a saved alert
85
85
  # alerts.grant.match Grant matched a saved alert
86
86
  # alerts.forecast.match Forecast matched a saved alert
87
+ # alerts.exclusion.match SAM.gov exclusion matched a saved alert
88
+ # alerts.dibbs_rfq.match DLA DIBBS RFQ matched a saved alert
89
+ # alerts.dibbs_rfp.match DLA DIBBS RFP matched a saved alert
87
90
  ```
88
91
 
92
+ This list is served by the API, so it is always current — the SDK does not hardcode
93
+ event types anywhere. Always trust `list-event-types` over any list written down
94
+ here.
95
+
96
+ Two things that surprise people:
97
+
98
+ - **DIBBS awards are not alertable.** There is no `alerts.dibbs_award.match`;
99
+ only RFQs and RFPs emit alerts.
100
+ - **Nothing fires when a record merely lapses.** An exclusion reaching its
101
+ termination date, or an RFQ/RFP passing its close date, emits no event.
102
+ Alerts fire on a record matching your saved search, not on the passage of
103
+ time. Poll with `list_exclusions(active=...)` / `list_dibbs_rfqs(open=...)`
104
+ if you need to observe expiry.
105
+
89
106
  ### 2. See what a payload looks like
90
107
 
91
108
  ```bash
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "tango-python"
7
- version = "1.2.0"
7
+ version = "1.4.0"
8
8
  description = "Python SDK for the Tango API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -0,0 +1,114 @@
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, conformance)
16
+ - **`check_filter_shape_conformance.py`** - Validates SDK filter and shape conformance (see [Filter and shape conformance](#filter-and-shape-conformance))
17
+ - **`check_shape_coverage.py`** - Reverse shape-coverage gate: fails when Tango's shape trees expose a field/expand the SDK schema doesn't capture and it isn't in `contracts/shape_coverage_baseline.json`. Offline against the vendored contract (no API key; runs on forks)
18
+ - **`generate_shape_overlay.py`** - Regenerates `tango/shapes/generated_overlay.py` (the schema additions that close the coverage gaps) from the vendored contract + `contracts/observed_shape_types.json`. No API key needed; `--report` prints the gaps without writing
19
+ - **`probe_shape_types.py`** - Samples the live API to derive ground-truth shape types (types + list-ness the contract can't express) into `contracts/observed_shape_types.json`. Maintainer-run; requires `TANGO_API_KEY`
20
+
21
+ ## Usage
22
+
23
+ These scripts are primarily for maintainers and are not part of the public API.
24
+
25
+ ### Production API Testing
26
+
27
+ Run smoke tests against the production API:
28
+
29
+ ```bash
30
+ # Requires TANGO_API_KEY environment variable
31
+ uv run python scripts/test_production.py
32
+ ```
33
+
34
+ This runs fast smoke tests (~30-60 seconds) that validate core SDK functionality against the live production API.
35
+
36
+ ### PR Review Validation
37
+
38
+ Run validation checks for PR review. **Automatically detects PR context** from GitHub Actions, GitHub CLI, or git branch.
39
+
40
+ ```bash
41
+ # Auto-detect PR and run validation (default: production mode)
42
+ uv run python scripts/pr_review.py
43
+
44
+ # Review a specific PR number
45
+ uv run python scripts/pr_review.py --pr-number 123
46
+
47
+ # Smoke tests only
48
+ uv run python scripts/pr_review.py --mode smoke
49
+
50
+ # Quick validation (linting + type checking, no tests)
51
+ uv run python scripts/pr_review.py --mode quick
52
+
53
+ # Full validation (all checks including all tests)
54
+ uv run python scripts/pr_review.py --mode full
55
+
56
+ # Check only changed files (auto-enabled when PR is detected)
57
+ uv run python scripts/pr_review.py --mode production --changed-files-only
58
+ ```
59
+
60
+ **Validation Modes:**
61
+ - `smoke` - Production API smoke tests only
62
+ - `quick` - Linting + type checking (no tests)
63
+ - `full` - All checks (linting + type checking + filter/shape conformance + all tests)
64
+ - `production` - Linting + type checking + filter/shape conformance + production API smoke tests (default)
65
+
66
+ Both `full` and `production` run the [filter and shape conformance](#filter-and-shape-conformance) check against the vendored contract (`contracts/filter_shape_contract.json`, overridable via `TANGO_CONTRACT_MANIFEST`). If the contract is missing, that step is skipped with a warning.
67
+
68
+ **PR Detection:**
69
+ The script automatically detects PR information from:
70
+ - GitHub Actions environment variables (`GITHUB_EVENT_PATH`, `GITHUB_PR_NUMBER`)
71
+ - GitHub CLI (`gh pr view` - requires `gh auth login`)
72
+ - Current git branch
73
+
74
+ When a PR is detected, the script displays PR information and automatically:
75
+ - Detects the base branch from the PR
76
+ - Checks only changed files
77
+ - Shows PR URL in summary
78
+
79
+ ### Filter and shape conformance
80
+
81
+ The SDK is validated against the canonical API contract — vendored at
82
+ `contracts/filter_shape_contract.json` and refreshed with
83
+ `scripts/refresh_contract.py` — for **filter conformance**, and against its own
84
+ schemas for **shape conformance**:
85
+
86
+ 1. **Filter coverage** – Every list/get method exposes the filter parameters defined in the contract (explicit argument or `api_param_mapping`). Missing params are errors unless accepted in `contracts/conformance_baseline.json`, where each entry is tracked backlog (remove the entry in the same PR that adds the param). Methods that use `**kwargs` for filters are reported as warnings because their filter names cannot be verified.
87
+ 2. **Staleness** – Every filter-like SDK argument resolves to a param the API actually accepts. A stale param silently no-ops for users, so this is always an error.
88
+ 3. **Types** – With a `schema_version >= 2` contract, each SDK argument's annotation must be compatible with the contract value type (e.g. a `date` param must be `str`, a `boolean` must be `bool`).
89
+ 4. **Shape conformance** – Every `ShapeConfig` constant (e.g. `CONTRACTS_MINIMAL`, `IDVS_MINIMAL`) parses correctly and only references fields that exist in the SDK’s explicit schemas for that model. Invalid or unknown fields in default shapes are reported as errors.
90
+
91
+ This script runs in CI on every push/PR (see [Lint workflow](.github/workflows/lint.yml)) against the vendored contract — no secrets or sibling checkout needed.
92
+
93
+ ```bash
94
+ # Full check (filter + shape) against the vendored contract.
95
+ uv run python scripts/check_filter_shape_conformance.py
96
+
97
+ # Ready-to-paste typed parameter scaffolds for any missing filters
98
+ uv run python scripts/check_filter_shape_conformance.py --suggest
99
+
100
+ # List resources that have no matching SDK method (for implementation checklist)
101
+ uv run python scripts/check_filter_shape_conformance.py --list-unmapped
102
+
103
+ # Refresh the vendored contract (sibling ../tango checkout, or GitHub via gh)
104
+ uv run python scripts/refresh_contract.py
105
+ ```
106
+
107
+ **Output:** JSON with `manifest`, `errors`, and `warnings`. Exit code 1 if there are any errors (missing or stale filters, type mismatches, invalid shapes, or missing SDK methods for manifest resources).
108
+
109
+ **Local runs:** The vendored contract makes the full check work out of the box. To check against a different contract (e.g. an unreleased tango branch), pass `--manifest /path/to/filter_shape_contract.json` or set `TANGO_CONTRACT_MANIFEST` (honored by `pr_review.py`).
110
+
111
+ ## Requirements
112
+
113
+ - `TANGO_API_KEY` environment variable (required for production API tests)
114
+ - All dependencies installed: `uv sync --all-extras`