tango-python 1.2.0__tar.gz → 1.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 (207) hide show
  1. tango_python-1.3.0/.github/workflows/lint.yml +110 -0
  2. {tango_python-1.2.0 → tango_python-1.3.0}/CHANGELOG.md +107 -0
  3. {tango_python-1.2.0 → tango_python-1.3.0}/PKG-INFO +1 -1
  4. {tango_python-1.2.0 → tango_python-1.3.0}/docs/WEBHOOKS.md +17 -0
  5. {tango_python-1.2.0 → tango_python-1.3.0}/pyproject.toml +1 -1
  6. tango_python-1.3.0/scripts/README.md +114 -0
  7. tango_python-1.3.0/scripts/check_filter_shape_conformance.py +576 -0
  8. tango_python-1.3.0/scripts/check_shape_coverage.py +323 -0
  9. tango_python-1.3.0/scripts/generate_shape_overlay.py +301 -0
  10. {tango_python-1.2.0 → tango_python-1.3.0}/scripts/pr_review.py +4 -3
  11. tango_python-1.3.0/scripts/probe_shape_types.py +170 -0
  12. tango_python-1.3.0/scripts/refresh_contract.py +103 -0
  13. {tango_python-1.2.0 → tango_python-1.3.0}/tango/__init__.py +13 -1
  14. {tango_python-1.2.0 → tango_python-1.3.0}/tango/client.py +800 -0
  15. {tango_python-1.2.0 → tango_python-1.3.0}/tango/exceptions.py +18 -1
  16. {tango_python-1.2.0 → tango_python-1.3.0}/tango/models.py +225 -0
  17. tango_python-1.3.0/tango/shapes/generated_overlay.py +1874 -0
  18. {tango_python-1.2.0 → tango_python-1.3.0}/tango/shapes/schema.py +16 -0
  19. {tango_python-1.2.0 → tango_python-1.3.0}/tests/production/test_production_smoke.py +20 -0
  20. {tango_python-1.2.0 → tango_python-1.3.0}/tests/test_client.py +49 -0
  21. tango_python-1.3.0/tests/test_dibbs_exclusions_sbir.py +203 -0
  22. {tango_python-1.2.0 → tango_python-1.3.0}/tests/test_shapes.py +1 -3
  23. tango_python-1.2.0/.github/workflows/lint.yml +0 -94
  24. tango_python-1.2.0/scripts/README.md +0 -100
  25. tango_python-1.2.0/scripts/check_filter_shape_conformance.py +0 -286
  26. {tango_python-1.2.0 → tango_python-1.3.0}/.github/workflows/docs-dispatch.yml +0 -0
  27. {tango_python-1.2.0 → tango_python-1.3.0}/.github/workflows/publish.yml +0 -0
  28. {tango_python-1.2.0 → tango_python-1.3.0}/.github/workflows/test.yml +0 -0
  29. {tango_python-1.2.0 → tango_python-1.3.0}/.gitignore +0 -0
  30. {tango_python-1.2.0 → tango_python-1.3.0}/LICENSE +0 -0
  31. {tango_python-1.2.0 → tango_python-1.3.0}/README.md +0 -0
  32. {tango_python-1.2.0 → tango_python-1.3.0}/docs/API_REFERENCE.md +0 -0
  33. {tango_python-1.2.0 → tango_python-1.3.0}/docs/CLIENT.md +0 -0
  34. {tango_python-1.2.0 → tango_python-1.3.0}/docs/DYNAMIC_MODELS.md +0 -0
  35. {tango_python-1.2.0 → tango_python-1.3.0}/docs/ERRORS.md +0 -0
  36. {tango_python-1.2.0 → tango_python-1.3.0}/docs/PAGINATION.md +0 -0
  37. {tango_python-1.2.0 → tango_python-1.3.0}/docs/SHAPES.md +0 -0
  38. {tango_python-1.2.0 → tango_python-1.3.0}/scripts/fetch_api_schema.py +0 -0
  39. {tango_python-1.2.0 → tango_python-1.3.0}/scripts/generate_schemas_from_api.py +0 -0
  40. {tango_python-1.2.0 → tango_python-1.3.0}/scripts/smoke_api_parity.py +0 -0
  41. {tango_python-1.2.0 → tango_python-1.3.0}/scripts/test_production.py +0 -0
  42. {tango_python-1.2.0 → tango_python-1.3.0}/tango/shapes/__init__.py +0 -0
  43. {tango_python-1.2.0 → tango_python-1.3.0}/tango/shapes/explicit_schemas.py +0 -0
  44. {tango_python-1.2.0 → tango_python-1.3.0}/tango/shapes/factory.py +0 -0
  45. {tango_python-1.2.0 → tango_python-1.3.0}/tango/shapes/generator.py +0 -0
  46. {tango_python-1.2.0 → tango_python-1.3.0}/tango/shapes/models.py +0 -0
  47. {tango_python-1.2.0 → tango_python-1.3.0}/tango/shapes/parser.py +0 -0
  48. {tango_python-1.2.0 → tango_python-1.3.0}/tango/shapes/types.py +0 -0
  49. {tango_python-1.2.0 → tango_python-1.3.0}/tango/webhooks/__init__.py +0 -0
  50. {tango_python-1.2.0 → tango_python-1.3.0}/tango/webhooks/cli.py +0 -0
  51. {tango_python-1.2.0 → tango_python-1.3.0}/tango/webhooks/receiver.py +0 -0
  52. {tango_python-1.2.0 → tango_python-1.3.0}/tango/webhooks/signing.py +0 -0
  53. {tango_python-1.2.0 → tango_python-1.3.0}/tango/webhooks/simulate.py +0 -0
  54. {tango_python-1.2.0 → tango_python-1.3.0}/tests/__init__.py +0 -0
  55. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestAgenciesIntegration.test_get_agency +0 -0
  56. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestAgenciesIntegration.test_list_agencies +0 -0
  57. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_field_type_validation +0 -0
  58. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_parsing_consistency +0 -0
  59. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestBusinessTypesIntegration.test_list_business_types +0 -0
  60. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestContractsIntegration.test_combined_filters_work_together +0 -0
  61. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestContractsIntegration.test_contract_cursor_pagination +0 -0
  62. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestContractsIntegration.test_contract_data_object_parsing +0 -0
  63. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestContractsIntegration.test_contract_field_types +0 -0
  64. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[keyword-software] +0 -0
  65. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[psc_code-R425] +0 -0
  66. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_awarding_agency_filter +0 -0
  67. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_date_range_filter +0 -0
  68. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_flat +0 -0
  69. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_naics_code_filter +0 -0
  70. {tango_python-1.2.0 → tango_python-1.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-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[default-None] +0 -0
  72. {tango_python-1.2.0 → tango_python-1.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-1.2.0 → tango_python-1.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-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestContractsIntegration.test_new_expiring_filters +0 -0
  75. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestContractsIntegration.test_new_fiscal_year_range_filters +0 -0
  76. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestContractsIntegration.test_new_identifier_filters +0 -0
  77. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestContractsIntegration.test_search_contracts_with_filters +0 -0
  78. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestContractsIntegration.test_search_filters_object_with_new_parameters +0 -0
  79. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[asc-] +0 -0
  80. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[desc--] +0 -0
  81. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_contracts +0 -0
  82. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_entities +0 -0
  83. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_date_field_parsing_edge_cases +0 -0
  84. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_decimal_field_parsing_edge_cases +0 -0
  85. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_empty_list_responses +0 -0
  86. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_entity_parsing_with_various_address_formats +0 -0
  87. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_flattened_responses_with_flat_lists +0 -0
  88. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_list_field_parsing_consistency +0 -0
  89. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_nested_objects_with_missing_data +0 -0
  90. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_null_missing_fields_in_contracts +0 -0
  91. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_with_minimal_shape_sparse_data +0 -0
  92. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEntitiesIntegration.test_entity_field_types +0 -0
  93. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEntitiesIntegration.test_entity_location_parsing +0 -0
  94. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEntitiesIntegration.test_entity_parsing_with_business_types +0 -0
  95. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEntitiesIntegration.test_entity_with_various_identifiers +0 -0
  96. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_budget_flows +0 -0
  97. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_by_uei +0 -0
  98. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_flat +0 -0
  99. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_search +0 -0
  100. {tango_python-1.2.0 → tango_python-1.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...1603a7d52e211cf2b3bc7d32080238aa +0 -0
  101. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[custom-uei,legal_business_name,cage_code] +0 -0
  102. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
  103. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[with_address-uei,legal_business_name,cage_code,business_types,physical_address] +0 -0
  104. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestForecastsIntegration.test_forecast_field_types +0 -0
  105. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[custom-id,title,anticipated_award_date] +0 -0
  106. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[default-None] +0 -0
  107. {tango_python-1.2.0 → tango_python-1.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
  108. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[minimal-id,title,anticipated_award_date,fiscal_year,naics_code,status] +0 -0
  109. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestGrantsIntegration.test_grant_field_types +0 -0
  110. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestGrantsIntegration.test_grant_pagination +0 -0
  111. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[custom-grant_id,title,opportunity_number] +0 -0
  112. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[default-None] +0 -0
  113. {tango_python-1.2.0 → tango_python-1.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
  114. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[minimal-grant_id,opportunity_number,title,status(-),agency_code] +0 -0
  115. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestIDVsIntegration.test_get_idv_uses_default_shape +0 -0
  116. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_awards_uses_default_shape +0 -0
  117. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_child_idvs_uses_default_shape +0 -0
  118. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_transactions +0 -0
  119. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestIDVsIntegration.test_list_idvs_uses_default_shape_and_keyset_params +0 -0
  120. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_code +0 -0
  121. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_name_text +0 -0
  122. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating +0 -0
  123. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating_max +0 -0
  124. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_performance_risk +0 -0
  125. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_type_of_investment +0 -0
  126. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_updated_time_range +0 -0
  127. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestITDashboardIntegration.test_funding_and_cio_evaluation_expansions +0 -0
  128. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestITDashboardIntegration.test_get_itdashboard_investment_by_uii +0 -0
  129. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestITDashboardIntegration.test_itdashboard_pagination +0 -0
  130. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_search +0 -0
  131. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[custom-uii,agency_name,investment_title,updated_time] +0 -0
  132. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[default-None] +0 -0
  133. {tango_python-1.2.0 → tango_python-1.3.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
  134. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestNaicsIntegration.test_list_naics +0 -0
  135. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[custom-notice_id,title,solicitation_number] +0 -0
  136. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[default-None] +0 -0
  137. {tango_python-1.2.0 → tango_python-1.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
  138. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
  139. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestNoticesIntegration.test_notice_field_types +0 -0
  140. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestNoticesIntegration.test_notice_pagination +0 -0
  141. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestNoticesIntegration.test_notice_with_meta_fields +0 -0
  142. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestOTAsIntegration.test_get_ota +0 -0
  143. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestOTAsIntegration.test_list_otas +0 -0
  144. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestOTIDVsIntegration.test_get_otidv +0 -0
  145. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestOTIDVsIntegration.test_list_otidvs +0 -0
  146. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestOfficesIntegration.test_get_office +0 -0
  147. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestOfficesIntegration.test_list_offices +0 -0
  148. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[custom-opportunity_id,title,solicitation_number] +0 -0
  149. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[default-None] +0 -0
  150. {tango_python-1.2.0 → tango_python-1.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
  151. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
  152. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestOpportunitiesIntegration.test_opportunity_field_types +0 -0
  153. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestOrganizationsIntegration.test_get_organization +0 -0
  154. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestOrganizationsIntegration.test_list_organizations +0 -0
  155. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestProtestsIntegration.test_get_protest_by_case_id +0 -0
  156. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_filter +0 -0
  157. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[custom-case_id,title,source_system,outcome] +0 -0
  158. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[default-None] +0 -0
  159. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[minimal-case_id,case_number,title,source_system,outcome,filed_date] +0 -0
  160. {tango_python-1.2.0 → tango_python-1.3.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
  161. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestProtestsIntegration.test_protest_pagination +0 -0
  162. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestSubawardsIntegration.test_list_subawards +0 -0
  163. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[custom-key,piid,description] +0 -0
  164. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[minimal-key,piid,award_date,recipient(display_name),description,total_contract_value] +0 -0
  165. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[ultra_minimal-key,piid,recipient(display_name),total_contract_value] +0 -0
  166. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
  167. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[with_address-uei,legal_business_name,cage_code,business_types,physical_address] +0 -0
  168. {tango_python-1.2.0 → tango_python-1.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
  169. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestTypeHintsIntegration.test_notices_dict_access[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
  170. {tango_python-1.2.0 → tango_python-1.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
  171. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestTypeHintsIntegration.test_opportunities_dict_access[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
  172. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists +0 -0
  173. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_with_metrics_expansion +0 -0
  174. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape +0 -0
  175. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_orders_uses_default_shape +0 -0
  176. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search +0 -0
  177. {tango_python-1.2.0 → tango_python-1.3.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_with_ordering +0 -0
  178. {tango_python-1.2.0 → tango_python-1.3.0}/tests/conftest.py +0 -0
  179. {tango_python-1.2.0 → tango_python-1.3.0}/tests/integration/README.md +0 -0
  180. {tango_python-1.2.0 → tango_python-1.3.0}/tests/integration/__init__.py +0 -0
  181. {tango_python-1.2.0 → tango_python-1.3.0}/tests/integration/conftest.py +0 -0
  182. {tango_python-1.2.0 → tango_python-1.3.0}/tests/integration/test_agencies_integration.py +0 -0
  183. {tango_python-1.2.0 → tango_python-1.3.0}/tests/integration/test_contracts_integration.py +0 -0
  184. {tango_python-1.2.0 → tango_python-1.3.0}/tests/integration/test_edge_cases_integration.py +0 -0
  185. {tango_python-1.2.0 → tango_python-1.3.0}/tests/integration/test_entities_integration.py +0 -0
  186. {tango_python-1.2.0 → tango_python-1.3.0}/tests/integration/test_forecasts_integration.py +0 -0
  187. {tango_python-1.2.0 → tango_python-1.3.0}/tests/integration/test_grants_integration.py +0 -0
  188. {tango_python-1.2.0 → tango_python-1.3.0}/tests/integration/test_itdashboard_integration.py +0 -0
  189. {tango_python-1.2.0 → tango_python-1.3.0}/tests/integration/test_naics_integration.py +0 -0
  190. {tango_python-1.2.0 → tango_python-1.3.0}/tests/integration/test_notices_integration.py +0 -0
  191. {tango_python-1.2.0 → tango_python-1.3.0}/tests/integration/test_offices_integration.py +0 -0
  192. {tango_python-1.2.0 → tango_python-1.3.0}/tests/integration/test_opportunities_integration.py +0 -0
  193. {tango_python-1.2.0 → tango_python-1.3.0}/tests/integration/test_organizations_integration.py +0 -0
  194. {tango_python-1.2.0 → tango_python-1.3.0}/tests/integration/test_otas_otidvs_integration.py +0 -0
  195. {tango_python-1.2.0 → tango_python-1.3.0}/tests/integration/test_protests_integration.py +0 -0
  196. {tango_python-1.2.0 → tango_python-1.3.0}/tests/integration/test_reference_data_integration.py +0 -0
  197. {tango_python-1.2.0 → tango_python-1.3.0}/tests/integration/test_subawards_integration.py +0 -0
  198. {tango_python-1.2.0 → tango_python-1.3.0}/tests/integration/test_vehicles_idvs_integration.py +0 -0
  199. {tango_python-1.2.0 → tango_python-1.3.0}/tests/integration/validation.py +0 -0
  200. {tango_python-1.2.0 → tango_python-1.3.0}/tests/production/__init__.py +0 -0
  201. {tango_python-1.2.0 → tango_python-1.3.0}/tests/production/conftest.py +0 -0
  202. {tango_python-1.2.0 → tango_python-1.3.0}/tests/test_api_parity.py +0 -0
  203. {tango_python-1.2.0 → tango_python-1.3.0}/tests/test_models.py +0 -0
  204. {tango_python-1.2.0 → tango_python-1.3.0}/tests/test_webhooks_cli.py +0 -0
  205. {tango_python-1.2.0 → tango_python-1.3.0}/tests/test_webhooks_receiver.py +0 -0
  206. {tango_python-1.2.0 → tango_python-1.3.0}/tests/test_webhooks_signing.py +0 -0
  207. {tango_python-1.2.0 → tango_python-1.3.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,113 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.3.0] - 2026-07-19
11
+
12
+ ### Added
13
+ - **DIBBS, exclusions, and SBIR/STTR endpoint support.** Six endpoint families
14
+ Tango shipped in v4.16–v4.18 had no SDK support at all — no models, no methods.
15
+ Added `list_dibbs_rfqs`/`get_dibbs_rfq`, `list_dibbs_rfps`/`get_dibbs_rfp`,
16
+ `list_dibbs_awards`/`get_dibbs_award`, `list_exclusions`/`get_exclusion`,
17
+ `list_sbir_topics`/`get_sbir_topic`, and
18
+ `list_sbir_solicitations`/`get_sbir_solicitation`, with all 85 filter params,
19
+ shape schemas, and `ShapeConfig` defaults. New models: `DibbsRfq`, `DibbsRfp`,
20
+ `DibbsAward`, `Exclusion`, `SbirTopic`, `SbirSolicitation`.
21
+
22
+ Two API behaviors are worth knowing. `is_open` (DIBBS) and
23
+ `is_currently_excluded` (exclusions) are derived at query time, so filter with
24
+ the `open` / `active` kwargs rather than shaping on those fields. And DIBBS
25
+ `total_contract_price` is the *order* total repeated on every line item —
26
+ never sum it across rows; deduplicate on award + delivery-order number first.
27
+ - **Reverse shape-coverage: the SDK now captures every field and expand the API
28
+ returns.** The conformance check only validated one direction — that the SDK's
29
+ shape constants reference *allowed* fields. Nothing checked the reverse, so the
30
+ hand-maintained `tango/shapes/explicit_schemas.py` had silently fallen ~200+
31
+ fields behind the API: 209 leaf fields, 41 whole nested expand branches, and 3
32
+ resources (naics, psc, mas_sins) the typed shape API could not request at all.
33
+ A new generated overlay (`tango/shapes/generated_overlay.py`, produced by
34
+ `scripts/generate_shape_overlay.py` and merged over the base by `SchemaRegistry`)
35
+ closes all of them, with types resolved from live-API sampling rather than
36
+ guessed. Notable now-shapeable data: contract/IDV acquisition attributes
37
+ (`fair_opportunity_limited_sources`, `subcontracting_plan`, …), `contracts.officers`
38
+ and `period_of_performance`, the full `organizations` hierarchy
39
+ (`obligation_rank`, `l1..l8_fh_key`, `budget_appropriation`, `children`/`parent`),
40
+ `otas`/`otidvs` `transactions`, and the deep `vehicles.awardees[.orders]` tree.
41
+ Code-object expands (`set_aside`, `award_type`, `idv_type`, …), previously
42
+ modeled inconsistently as `str`/bare `dict`, now uniformly resolve to
43
+ `{code, description}`.
44
+ - **Reverse shape-coverage gate.** `scripts/check_shape_coverage.py` walks Tango's
45
+ shape trees (from the vendored contract) against the SDK schemas and fails when
46
+ the SDK misses anything the API exposes and it isn't in
47
+ `contracts/shape_coverage_baseline.json`. Offline against the vendored contract —
48
+ no token, runs on forks — and wired into the `lint.yml` conformance job.
49
+ Regenerate the overlay with `scripts/generate_shape_overlay.py` (from the vendored
50
+ contract + `contracts/observed_shape_types.json`, no API key); refresh the type
51
+ observations with `scripts/probe_shape_types.py` (maintainer-run, needs a key).
52
+ - **Contract-first conformance system.** The canonical API filter/shape
53
+ contract is now vendored at `contracts/filter_shape_contract.json` (refresh
54
+ with the new `scripts/refresh_contract.py`), so the conformance check runs
55
+ out of the box — locally, in CI, and on forks — with no tango checkout or
56
+ access token. `scripts/check_filter_shape_conformance.py` gained three new
57
+ checks on top of filter coverage: **staleness** (an SDK filter argument the
58
+ API no longer accepts is an error — it would silently no-op), **types**
59
+ (each argument's annotation is validated against the contract's
60
+ `schema_version: 2` per-filter type metadata), and a **known-gaps baseline**
61
+ (`contracts/conformance_baseline.json`) that downgrades accepted missing
62
+ params from errors to warnings so backlog is tracked instead of silent. A
63
+ new `--suggest` flag prints ready-to-paste typed parameter scaffolds for
64
+ any missing filters. The first run against the current API surface found 7
65
+ real coverage gaps, now baselined: `key` on contracts/IDVs/OTAs/OTIDVs,
66
+ `cage` on entities, `id` on forecasts, and `opportunity_id` on
67
+ opportunities.
68
+ - `TangoValidationError` now exposes the API's structured validation details
69
+ directly: `.issues` (the list of `{"path": ..., "reason": ...}` entries the
70
+ server returns for shape errors) and `.available_fields` (the endpoint's
71
+ valid field set, when included). Both were previously reachable only by
72
+ digging through `.response_data`. ([#45](https://github.com/makegov/tango-python/issues/45))
73
+
74
+ ### Changed
75
+ - The CI conformance job (`lint.yml`) now runs unconditionally against the
76
+ vendored contract instead of silently skipping when
77
+ `TANGO_API_REPO_ACCESS_TOKEN` is absent; the token is only used for a
78
+ best-effort staleness notice comparing the vendored contract to tango HEAD.
79
+ `scripts/pr_review.py` likewise defaults its conformance step to the
80
+ vendored contract (override with `TANGO_CONTRACT_MANIFEST`).
81
+ - 400 error messages now name the rejected field(s) and reason when the API
82
+ returns structured `issues` — e.g.
83
+ `Invalid request parameters: Invalid shape: tradeoff_process (unknown_field)`
84
+ instead of just `Invalid request parameters: Invalid shape`. ([#45](https://github.com/makegov/tango-python/issues/45))
85
+
86
+ ### Fixed
87
+ - **`docs/WEBHOOKS.md` listed only five of the eight webhook alert types.** It
88
+ was missing `alerts.exclusion.match`, `alerts.dibbs_rfq.match`, and
89
+ `alerts.dibbs_rfp.match`. No SDK change was needed — event types are served by
90
+ the API and never hardcoded, so the new types already worked — but the
91
+ hand-written list had gone stale. Also documents two things that surprise
92
+ people: DIBBS *awards* are not alertable, and nothing fires when a record
93
+ merely lapses (an exclusion hitting its termination date, or an RFQ/RFP
94
+ passing its close date, emits no event). A production smoke test now asserts
95
+ every live event type appears in the doc, so the list cannot silently fall
96
+ behind again.
97
+ - **Refreshed the vendored API contract, which had gone stale by seven
98
+ resources.** It tracked 25 resources against Tango's current 32, so the
99
+ conformance and coverage checks were validating against out-of-date truth and
100
+ could not see DIBBS, exclusions, SBIR, or `budget/accounts` at all. Refreshing
101
+ it also surfaced 72 additional fields on existing resources, now covered.
102
+ Nested routes are keyed with a slash (`budget/accounts`), which silently broke
103
+ the old `budget_accounts` mapping — both keys are now accepted.
104
+ - **`check_filter_shape_conformance.py` no longer reports false stale params.**
105
+ It resolved SDK arguments to API params only through an explicit
106
+ `api_param_mapping` dict, so methods that express the translation as tuple
107
+ tables — `("account_title__icontains", account_title)` and
108
+ `range_filters = (("apportioned", apportioned, apportioned_gte, ...))` — looked
109
+ like they exposed dozens of params the API rejects. They do not:
110
+ `list_budget_accounts` correctly sends `apportioned__gte`, `fiscal_year__lte`,
111
+ and `account_title__icontains`. The checker now understands both tuple forms
112
+ (in `Assign`, `AnnAssign`, and inline `for` iterables), with an explicit
113
+ `api_param_mapping` still taking precedence. This let `budget/accounts` be
114
+ conformance-checked for the first time; its genuinely missing lookup variants
115
+ (`agency_code__in`, `bureau_name__icontains`, …) are now baselined.
116
+
10
117
  ## [1.2.0] - 2026-06-05
11
118
 
12
119
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tango-python
3
- Version: 1.2.0
3
+ Version: 1.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
@@ -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.3.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`