tango-python 1.3.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 (205) hide show
  1. {tango_python-1.3.0 → tango_python-1.4.0}/CHANGELOG.md +20 -0
  2. {tango_python-1.3.0 → tango_python-1.4.0}/PKG-INFO +1 -1
  3. {tango_python-1.3.0 → tango_python-1.4.0}/pyproject.toml +1 -1
  4. {tango_python-1.3.0 → tango_python-1.4.0}/scripts/check_filter_shape_conformance.py +38 -13
  5. {tango_python-1.3.0 → tango_python-1.4.0}/scripts/check_shape_coverage.py +35 -1
  6. {tango_python-1.3.0 → tango_python-1.4.0}/tango/__init__.py +1 -1
  7. {tango_python-1.3.0 → tango_python-1.4.0}/tango/client.py +11 -1
  8. {tango_python-1.3.0 → tango_python-1.4.0}/tango/shapes/generated_overlay.py +601 -45
  9. {tango_python-1.3.0 → tango_python-1.4.0}/tests/test_client.py +88 -0
  10. tango_python-1.4.0/tests/test_shape_coverage_gate.py +108 -0
  11. {tango_python-1.3.0 → tango_python-1.4.0}/.github/workflows/docs-dispatch.yml +0 -0
  12. {tango_python-1.3.0 → tango_python-1.4.0}/.github/workflows/lint.yml +0 -0
  13. {tango_python-1.3.0 → tango_python-1.4.0}/.github/workflows/publish.yml +0 -0
  14. {tango_python-1.3.0 → tango_python-1.4.0}/.github/workflows/test.yml +0 -0
  15. {tango_python-1.3.0 → tango_python-1.4.0}/.gitignore +0 -0
  16. {tango_python-1.3.0 → tango_python-1.4.0}/LICENSE +0 -0
  17. {tango_python-1.3.0 → tango_python-1.4.0}/README.md +0 -0
  18. {tango_python-1.3.0 → tango_python-1.4.0}/docs/API_REFERENCE.md +0 -0
  19. {tango_python-1.3.0 → tango_python-1.4.0}/docs/CLIENT.md +0 -0
  20. {tango_python-1.3.0 → tango_python-1.4.0}/docs/DYNAMIC_MODELS.md +0 -0
  21. {tango_python-1.3.0 → tango_python-1.4.0}/docs/ERRORS.md +0 -0
  22. {tango_python-1.3.0 → tango_python-1.4.0}/docs/PAGINATION.md +0 -0
  23. {tango_python-1.3.0 → tango_python-1.4.0}/docs/SHAPES.md +0 -0
  24. {tango_python-1.3.0 → tango_python-1.4.0}/docs/WEBHOOKS.md +0 -0
  25. {tango_python-1.3.0 → tango_python-1.4.0}/scripts/README.md +0 -0
  26. {tango_python-1.3.0 → tango_python-1.4.0}/scripts/fetch_api_schema.py +0 -0
  27. {tango_python-1.3.0 → tango_python-1.4.0}/scripts/generate_schemas_from_api.py +0 -0
  28. {tango_python-1.3.0 → tango_python-1.4.0}/scripts/generate_shape_overlay.py +0 -0
  29. {tango_python-1.3.0 → tango_python-1.4.0}/scripts/pr_review.py +0 -0
  30. {tango_python-1.3.0 → tango_python-1.4.0}/scripts/probe_shape_types.py +0 -0
  31. {tango_python-1.3.0 → tango_python-1.4.0}/scripts/refresh_contract.py +0 -0
  32. {tango_python-1.3.0 → tango_python-1.4.0}/scripts/smoke_api_parity.py +0 -0
  33. {tango_python-1.3.0 → tango_python-1.4.0}/scripts/test_production.py +0 -0
  34. {tango_python-1.3.0 → tango_python-1.4.0}/tango/exceptions.py +0 -0
  35. {tango_python-1.3.0 → tango_python-1.4.0}/tango/models.py +0 -0
  36. {tango_python-1.3.0 → tango_python-1.4.0}/tango/shapes/__init__.py +0 -0
  37. {tango_python-1.3.0 → tango_python-1.4.0}/tango/shapes/explicit_schemas.py +0 -0
  38. {tango_python-1.3.0 → tango_python-1.4.0}/tango/shapes/factory.py +0 -0
  39. {tango_python-1.3.0 → tango_python-1.4.0}/tango/shapes/generator.py +0 -0
  40. {tango_python-1.3.0 → tango_python-1.4.0}/tango/shapes/models.py +0 -0
  41. {tango_python-1.3.0 → tango_python-1.4.0}/tango/shapes/parser.py +0 -0
  42. {tango_python-1.3.0 → tango_python-1.4.0}/tango/shapes/schema.py +0 -0
  43. {tango_python-1.3.0 → tango_python-1.4.0}/tango/shapes/types.py +0 -0
  44. {tango_python-1.3.0 → tango_python-1.4.0}/tango/webhooks/__init__.py +0 -0
  45. {tango_python-1.3.0 → tango_python-1.4.0}/tango/webhooks/cli.py +0 -0
  46. {tango_python-1.3.0 → tango_python-1.4.0}/tango/webhooks/receiver.py +0 -0
  47. {tango_python-1.3.0 → tango_python-1.4.0}/tango/webhooks/signing.py +0 -0
  48. {tango_python-1.3.0 → tango_python-1.4.0}/tango/webhooks/simulate.py +0 -0
  49. {tango_python-1.3.0 → tango_python-1.4.0}/tests/__init__.py +0 -0
  50. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestAgenciesIntegration.test_get_agency +0 -0
  51. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestAgenciesIntegration.test_list_agencies +0 -0
  52. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_field_type_validation +0 -0
  53. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_parsing_consistency +0 -0
  54. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestBusinessTypesIntegration.test_list_business_types +0 -0
  55. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_combined_filters_work_together +0 -0
  56. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_contract_cursor_pagination +0 -0
  57. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_contract_data_object_parsing +0 -0
  58. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_contract_field_types +0 -0
  59. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[keyword-software] +0 -0
  60. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[psc_code-R425] +0 -0
  61. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_awarding_agency_filter +0 -0
  62. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_date_range_filter +0 -0
  63. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_flat +0 -0
  64. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_naics_code_filter +0 -0
  65. {tango_python-1.3.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
  66. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[default-None] +0 -0
  67. {tango_python-1.3.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
  68. {tango_python-1.3.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
  69. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_new_expiring_filters +0 -0
  70. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_new_fiscal_year_range_filters +0 -0
  71. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_new_identifier_filters +0 -0
  72. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_search_contracts_with_filters +0 -0
  73. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_search_filters_object_with_new_parameters +0 -0
  74. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[asc-] +0 -0
  75. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[desc--] +0 -0
  76. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_contracts +0 -0
  77. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_entities +0 -0
  78. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_date_field_parsing_edge_cases +0 -0
  79. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_decimal_field_parsing_edge_cases +0 -0
  80. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_empty_list_responses +0 -0
  81. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_entity_parsing_with_various_address_formats +0 -0
  82. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_flattened_responses_with_flat_lists +0 -0
  83. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_list_field_parsing_consistency +0 -0
  84. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_nested_objects_with_missing_data +0 -0
  85. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_null_missing_fields_in_contracts +0 -0
  86. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_with_minimal_shape_sparse_data +0 -0
  87. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestEntitiesIntegration.test_entity_field_types +0 -0
  88. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestEntitiesIntegration.test_entity_location_parsing +0 -0
  89. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestEntitiesIntegration.test_entity_parsing_with_business_types +0 -0
  90. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestEntitiesIntegration.test_entity_with_various_identifiers +0 -0
  91. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_budget_flows +0 -0
  92. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_by_uei +0 -0
  93. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_flat +0 -0
  94. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_search +0 -0
  95. {tango_python-1.3.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
  96. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[custom-uei,legal_business_name,cage_code] +0 -0
  97. {tango_python-1.3.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
  98. {tango_python-1.3.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
  99. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestForecastsIntegration.test_forecast_field_types +0 -0
  100. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[custom-id,title,anticipated_award_date] +0 -0
  101. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[default-None] +0 -0
  102. {tango_python-1.3.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
  103. {tango_python-1.3.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
  104. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestGrantsIntegration.test_grant_field_types +0 -0
  105. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestGrantsIntegration.test_grant_pagination +0 -0
  106. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[custom-grant_id,title,opportunity_number] +0 -0
  107. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[default-None] +0 -0
  108. {tango_python-1.3.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
  109. {tango_python-1.3.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
  110. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestIDVsIntegration.test_get_idv_uses_default_shape +0 -0
  111. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_awards_uses_default_shape +0 -0
  112. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_child_idvs_uses_default_shape +0 -0
  113. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_transactions +0 -0
  114. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestIDVsIntegration.test_list_idvs_uses_default_shape_and_keyset_params +0 -0
  115. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_code +0 -0
  116. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_name_text +0 -0
  117. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating +0 -0
  118. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating_max +0 -0
  119. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_performance_risk +0 -0
  120. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_type_of_investment +0 -0
  121. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_updated_time_range +0 -0
  122. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_funding_and_cio_evaluation_expansions +0 -0
  123. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_get_itdashboard_investment_by_uii +0 -0
  124. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_itdashboard_pagination +0 -0
  125. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_search +0 -0
  126. {tango_python-1.3.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
  127. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[default-None] +0 -0
  128. {tango_python-1.3.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
  129. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestNaicsIntegration.test_list_naics +0 -0
  130. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[custom-notice_id,title,solicitation_number] +0 -0
  131. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[default-None] +0 -0
  132. {tango_python-1.3.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
  133. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
  134. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestNoticesIntegration.test_notice_field_types +0 -0
  135. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestNoticesIntegration.test_notice_pagination +0 -0
  136. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestNoticesIntegration.test_notice_with_meta_fields +0 -0
  137. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestOTAsIntegration.test_get_ota +0 -0
  138. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestOTAsIntegration.test_list_otas +0 -0
  139. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestOTIDVsIntegration.test_get_otidv +0 -0
  140. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestOTIDVsIntegration.test_list_otidvs +0 -0
  141. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestOfficesIntegration.test_get_office +0 -0
  142. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestOfficesIntegration.test_list_offices +0 -0
  143. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[custom-opportunity_id,title,solicitation_number] +0 -0
  144. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[default-None] +0 -0
  145. {tango_python-1.3.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
  146. {tango_python-1.3.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
  147. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestOpportunitiesIntegration.test_opportunity_field_types +0 -0
  148. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestOrganizationsIntegration.test_get_organization +0 -0
  149. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestOrganizationsIntegration.test_list_organizations +0 -0
  150. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestProtestsIntegration.test_get_protest_by_case_id +0 -0
  151. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_filter +0 -0
  152. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[custom-case_id,title,source_system,outcome] +0 -0
  153. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[default-None] +0 -0
  154. {tango_python-1.3.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
  155. {tango_python-1.3.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
  156. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestProtestsIntegration.test_protest_pagination +0 -0
  157. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestSubawardsIntegration.test_list_subawards +0 -0
  158. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[custom-key,piid,description] +0 -0
  159. {tango_python-1.3.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
  160. {tango_python-1.3.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
  161. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
  162. {tango_python-1.3.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
  163. {tango_python-1.3.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
  164. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestTypeHintsIntegration.test_notices_dict_access[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
  165. {tango_python-1.3.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
  166. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestTypeHintsIntegration.test_opportunities_dict_access[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
  167. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists +0 -0
  168. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_with_metrics_expansion +0 -0
  169. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape +0 -0
  170. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_orders_uses_default_shape +0 -0
  171. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search +0 -0
  172. {tango_python-1.3.0 → tango_python-1.4.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_with_ordering +0 -0
  173. {tango_python-1.3.0 → tango_python-1.4.0}/tests/conftest.py +0 -0
  174. {tango_python-1.3.0 → tango_python-1.4.0}/tests/integration/README.md +0 -0
  175. {tango_python-1.3.0 → tango_python-1.4.0}/tests/integration/__init__.py +0 -0
  176. {tango_python-1.3.0 → tango_python-1.4.0}/tests/integration/conftest.py +0 -0
  177. {tango_python-1.3.0 → tango_python-1.4.0}/tests/integration/test_agencies_integration.py +0 -0
  178. {tango_python-1.3.0 → tango_python-1.4.0}/tests/integration/test_contracts_integration.py +0 -0
  179. {tango_python-1.3.0 → tango_python-1.4.0}/tests/integration/test_edge_cases_integration.py +0 -0
  180. {tango_python-1.3.0 → tango_python-1.4.0}/tests/integration/test_entities_integration.py +0 -0
  181. {tango_python-1.3.0 → tango_python-1.4.0}/tests/integration/test_forecasts_integration.py +0 -0
  182. {tango_python-1.3.0 → tango_python-1.4.0}/tests/integration/test_grants_integration.py +0 -0
  183. {tango_python-1.3.0 → tango_python-1.4.0}/tests/integration/test_itdashboard_integration.py +0 -0
  184. {tango_python-1.3.0 → tango_python-1.4.0}/tests/integration/test_naics_integration.py +0 -0
  185. {tango_python-1.3.0 → tango_python-1.4.0}/tests/integration/test_notices_integration.py +0 -0
  186. {tango_python-1.3.0 → tango_python-1.4.0}/tests/integration/test_offices_integration.py +0 -0
  187. {tango_python-1.3.0 → tango_python-1.4.0}/tests/integration/test_opportunities_integration.py +0 -0
  188. {tango_python-1.3.0 → tango_python-1.4.0}/tests/integration/test_organizations_integration.py +0 -0
  189. {tango_python-1.3.0 → tango_python-1.4.0}/tests/integration/test_otas_otidvs_integration.py +0 -0
  190. {tango_python-1.3.0 → tango_python-1.4.0}/tests/integration/test_protests_integration.py +0 -0
  191. {tango_python-1.3.0 → tango_python-1.4.0}/tests/integration/test_reference_data_integration.py +0 -0
  192. {tango_python-1.3.0 → tango_python-1.4.0}/tests/integration/test_subawards_integration.py +0 -0
  193. {tango_python-1.3.0 → tango_python-1.4.0}/tests/integration/test_vehicles_idvs_integration.py +0 -0
  194. {tango_python-1.3.0 → tango_python-1.4.0}/tests/integration/validation.py +0 -0
  195. {tango_python-1.3.0 → tango_python-1.4.0}/tests/production/__init__.py +0 -0
  196. {tango_python-1.3.0 → tango_python-1.4.0}/tests/production/conftest.py +0 -0
  197. {tango_python-1.3.0 → tango_python-1.4.0}/tests/production/test_production_smoke.py +0 -0
  198. {tango_python-1.3.0 → tango_python-1.4.0}/tests/test_api_parity.py +0 -0
  199. {tango_python-1.3.0 → tango_python-1.4.0}/tests/test_dibbs_exclusions_sbir.py +0 -0
  200. {tango_python-1.3.0 → tango_python-1.4.0}/tests/test_models.py +0 -0
  201. {tango_python-1.3.0 → tango_python-1.4.0}/tests/test_shapes.py +0 -0
  202. {tango_python-1.3.0 → tango_python-1.4.0}/tests/test_webhooks_cli.py +0 -0
  203. {tango_python-1.3.0 → tango_python-1.4.0}/tests/test_webhooks_receiver.py +0 -0
  204. {tango_python-1.3.0 → tango_python-1.4.0}/tests/test_webhooks_signing.py +0 -0
  205. {tango_python-1.3.0 → tango_python-1.4.0}/tests/test_webhooks_simulate.py +0 -0
@@ -7,6 +7,26 @@ 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
+
10
30
  ## [1.3.0] - 2026-07-19
11
31
 
12
32
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tango-python
3
- Version: 1.3.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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "tango-python"
7
- version = "1.3.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"
@@ -74,21 +74,35 @@ RESOURCE_TO_METHOD: dict[str, str | None] = {
74
74
  "exclusions": "list_exclusions",
75
75
  "sbir/topics": "list_sbir_topics",
76
76
  "sbir/solicitations": "list_sbir_solicitations",
77
- # Resources not yet implemented in SDK
78
- "offices": None,
77
+ "protests": "list_protests",
78
+ "offices": "list_offices",
79
+ "psc": "list_psc",
80
+ "mas_sins": "list_mas_sins",
81
+ "departments": "list_departments",
82
+ "business_types": "list_business_types",
83
+ "assistance_listings": "list_assistance_listings",
84
+ # Genuinely absent from the SDK. Both are content endpoints with no shaping
85
+ # and no list method; `list_webhook_event_types` is unrelated to `events`.
86
+ "events": None,
87
+ "news": None,
79
88
  }
80
89
 
81
90
  # Params the API genuinely accepts but the contract omits, so they would read as
82
- # stale (a silent no-op) when they in fact work. Tango surfaces view-handled
83
- # search params for filtersets that declare `min_length_params`; where it does
84
- # not, the param is absent from the contract even though the endpoint honors it.
91
+ # stale (a silent no-op) when they in fact work. Tango's generator surfaces
92
+ # view-handled search params only for filtersets declaring `min_length_params`
93
+ # and DRF `SearchFilter` backends declaring `search_fields`; a resource that
94
+ # implements search another way is absent from the contract even though the
95
+ # endpoint honors it.
96
+ #
85
97
  # Each entry must be verified against the live API before being added, and
86
- # removed once Tango's contract generator surfaces it.
87
- # budget/accounts `search`: verified 2026-07-19 21,170 results unfiltered vs
88
- # 0 for a nonsense term, so the endpoint clearly applies it.
89
- CONTRACT_OMITTED_PARAMS: dict[str, frozenset[str]] = {
90
- "budget/accounts": frozenset({"search"}),
91
- }
98
+ # removed once Tango's generator surfaces it — `check_conformance` warns when an
99
+ # entry is no longer needed, so this list cannot quietly rot.
100
+ #
101
+ # Currently empty, and that is the goal state. Two entries have come and gone:
102
+ # budget/accounts `search` (published by makegov/tango#2944's SearchFilter
103
+ # extraction) and mas_sins `search` (published by makegov/tango#2948's
104
+ # `view_handled_params` declaration). Both were retired by the warning above.
105
+ CONTRACT_OMITTED_PARAMS: dict[str, frozenset[str]] = {}
92
106
 
93
107
  # SDK-level conveniences that never correspond to API filter params.
94
108
  # `sort`/`order` compose into the API's `ordering`; `filters` is the legacy
@@ -395,11 +409,22 @@ def run_check(manifest_path: Path) -> tuple[list[str], list[str]]:
395
409
  )
396
410
 
397
411
  # Direction 2 — staleness: SDK sends it, API no longer accepts it.
412
+ omitted = CONTRACT_OMITTED_PARAMS.get(resource_name, frozenset())
413
+
414
+ # A carve-out is only justified while the contract still omits the param.
415
+ # Once Tango publishes it, the entry hides real staleness — warn so it
416
+ # gets removed, mirroring how baseline entries are burned down above.
417
+ redundant = sorted(omitted & runtime_filters)
418
+ if redundant:
419
+ warnings.append(
420
+ f"{resource_name}: CONTRACT_OMITTED_PARAMS entries no longer needed "
421
+ f"(the contract now publishes them): {', '.join(redundant)}"
422
+ )
423
+
398
424
  stale = sorted(
399
425
  f"{arg} (sends `{api_param}`)" if arg != api_param else arg
400
426
  for arg, api_param in exposed.items()
401
- if api_param not in runtime_filters
402
- and api_param not in CONTRACT_OMITTED_PARAMS.get(resource_name, frozenset())
427
+ if api_param not in runtime_filters and api_param not in omitted
403
428
  )
404
429
  if stale:
405
430
  errors.append(
@@ -171,8 +171,29 @@ def collect_gaps(contract: dict[str, Any], registry: Any, models: Any) -> list[d
171
171
  walk(resource, child_path, enode, child_schema)
172
172
 
173
173
  for rkey, r in contract.get("resources", {}).items():
174
- shape = (r.get("runtime") or {}).get("shape")
174
+ runtime = r.get("runtime") or {}
175
+ shape = runtime.get("shape")
175
176
  if not shape:
177
+ # A null tree used to be an unconditional skip, which made this gate
178
+ # blind to exactly the resources it most needed to check. Tango
179
+ # published null for entities/opportunities/notices/protests/
180
+ # itdashboard because its generator crashed on tier-aware viewsets
181
+ # (makegov/tango#2944), and this gate reported full coverage while
182
+ # the SDK's Entity and Protest schemas drifted 13 fields behind.
183
+ #
184
+ # Contracts at schema_version >= 2 declare shape_supported, so a
185
+ # null tree on a shaping resource is now a hard finding. Older
186
+ # contracts omit the key; there `None` is genuinely ambiguous and
187
+ # skipping stays the only safe read.
188
+ if runtime.get("shape_supported"):
189
+ findings.append(
190
+ {
191
+ "kind": "contract_missing_shape",
192
+ "resource": rkey,
193
+ "path": "(root)",
194
+ "name": runtime.get("shape_error") or "no shape tree published",
195
+ }
196
+ )
176
197
  continue
177
198
  model_name = RESOURCE_TO_MODEL.get(rkey)
178
199
  model = getattr(models, model_name, None) if model_name else None
@@ -305,6 +326,19 @@ def main() -> int:
305
326
  print("\nNo new shape-coverage drift. ✓")
306
327
  return 0
307
328
 
329
+ contract_gaps = [f for f in new if f["kind"] == "contract_missing_shape"]
330
+ if contract_gaps:
331
+ print(
332
+ "\n*** CONTRACT DEFECT — these resources support shaping but publish no shape tree ***"
333
+ )
334
+ _print_grouped("RESOURCES WITH NO SHAPE TREE", contract_gaps)
335
+ print(
336
+ "\n This is an upstream problem, not an SDK one: the vendored contract understates\n"
337
+ " the API, so coverage cannot be checked for these resources at all. Refresh the\n"
338
+ " contract (scripts/refresh_contract.py); if it persists, the generator is failing\n"
339
+ " to extract them — see makegov/tango contracts/README.md."
340
+ )
341
+
308
342
  print("\n*** NEW shape-coverage drift (Tango exposes these; the SDK schema does not) ***")
309
343
  _print_grouped("MISSING FIELDS", [f for f in new if f["kind"] == "missing_field"])
310
344
  _print_grouped("MISSING EXPANDS", [f for f in new if f["kind"] == "missing_expand"])
@@ -50,7 +50,7 @@ from .webhooks import (
50
50
  )
51
51
  from .webhooks.receiver import Delivery, WebhookReceiver
52
52
 
53
- __version__ = "1.3.0"
53
+ __version__ = "1.4.0"
54
54
  __all__ = [
55
55
  "TangoClient",
56
56
  "TangoAPIError",
@@ -2540,6 +2540,7 @@ class TangoClient:
2540
2540
  filed_date_before: str | None = None,
2541
2541
  decision_date_after: str | None = None,
2542
2542
  decision_date_before: str | None = None,
2543
+ naics_code: str | None = None,
2543
2544
  search: str | None = None,
2544
2545
  ) -> PaginatedResponse:
2545
2546
  """
@@ -2569,6 +2570,7 @@ class TangoClient:
2569
2570
  filed_date_before: Filed date on or before
2570
2571
  decision_date_after: Decision date on or after
2571
2572
  decision_date_before: Decision date on or before
2573
+ naics_code: Filter by the solicitation's NAICS code (e.g. 541519)
2572
2574
  search: Full-text search over protest searchable fields
2573
2575
  """
2574
2576
  params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
@@ -2594,6 +2596,7 @@ class TangoClient:
2594
2596
  ("filed_date_before", filed_date_before),
2595
2597
  ("decision_date_after", decision_date_after),
2596
2598
  ("decision_date_before", decision_date_before),
2599
+ ("naics_code", naics_code),
2597
2600
  ("search", search),
2598
2601
  ):
2599
2602
  if val is not None:
@@ -4445,8 +4448,13 @@ class TangoClient:
4445
4448
  shape: str | None = None,
4446
4449
  flat: bool = False,
4447
4450
  flat_lists: bool = False,
4451
+ has_awards: bool | None = None,
4448
4452
  ) -> PaginatedResponse[dict[str, Any]]:
4449
- """List Product Service Codes (`/api/psc/`)."""
4453
+ """List Product Service Codes (`/api/psc/`).
4454
+
4455
+ Args:
4456
+ has_awards: When True, return only codes with contract award history.
4457
+ """
4450
4458
  params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
4451
4459
  if shape:
4452
4460
  params["shape"] = shape
@@ -4454,6 +4462,8 @@ class TangoClient:
4454
4462
  params["flat"] = "true"
4455
4463
  if flat_lists:
4456
4464
  params["flat_lists"] = "true"
4465
+ if has_awards is not None:
4466
+ params["has_awards"] = "true" if has_awards else "false"
4457
4467
  data = self._get("/api/psc/", params)
4458
4468
  return PaginatedResponse(
4459
4469
  count=int(data.get("count", 0)),