tango-python 1.18.1__tar.gz → 1.19.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 (209) hide show
  1. {tango_python-1.18.1 → tango_python-1.19.0}/CHANGELOG.md +12 -0
  2. {tango_python-1.18.1 → tango_python-1.19.0}/PKG-INFO +1 -1
  3. {tango_python-1.18.1 → tango_python-1.19.0}/docs/API_REFERENCE.md +16 -0
  4. {tango_python-1.18.1 → tango_python-1.19.0}/pyproject.toml +1 -1
  5. {tango_python-1.18.1 → tango_python-1.19.0}/tango/__init__.py +1 -1
  6. {tango_python-1.18.1 → tango_python-1.19.0}/tango/client.py +68 -7
  7. {tango_python-1.18.1 → tango_python-1.19.0}/tango/exceptions.py +10 -4
  8. {tango_python-1.18.1 → tango_python-1.19.0}/tests/test_api_parity.py +26 -0
  9. {tango_python-1.18.1 → tango_python-1.19.0}/tests/test_client.py +16 -0
  10. {tango_python-1.18.1 → tango_python-1.19.0}/.gitignore +0 -0
  11. {tango_python-1.18.1 → tango_python-1.19.0}/LICENSE +0 -0
  12. {tango_python-1.18.1 → tango_python-1.19.0}/README.md +0 -0
  13. {tango_python-1.18.1 → tango_python-1.19.0}/docs/CLIENT.md +0 -0
  14. {tango_python-1.18.1 → tango_python-1.19.0}/docs/DYNAMIC_MODELS.md +0 -0
  15. {tango_python-1.18.1 → tango_python-1.19.0}/docs/ERRORS.md +0 -0
  16. {tango_python-1.18.1 → tango_python-1.19.0}/docs/PAGINATION.md +0 -0
  17. {tango_python-1.18.1 → tango_python-1.19.0}/docs/SHAPES.md +0 -0
  18. {tango_python-1.18.1 → tango_python-1.19.0}/docs/WEBHOOKS.md +0 -0
  19. {tango_python-1.18.1 → tango_python-1.19.0}/scripts/README.md +0 -0
  20. {tango_python-1.18.1 → tango_python-1.19.0}/scripts/check_filter_shape_conformance.py +0 -0
  21. {tango_python-1.18.1 → tango_python-1.19.0}/scripts/check_shape_coverage.py +0 -0
  22. {tango_python-1.18.1 → tango_python-1.19.0}/scripts/fetch_api_schema.py +0 -0
  23. {tango_python-1.18.1 → tango_python-1.19.0}/scripts/generate_schemas_from_api.py +0 -0
  24. {tango_python-1.18.1 → tango_python-1.19.0}/scripts/generate_shape_overlay.py +0 -0
  25. {tango_python-1.18.1 → tango_python-1.19.0}/scripts/pr_review.py +0 -0
  26. {tango_python-1.18.1 → tango_python-1.19.0}/scripts/probe_shape_types.py +0 -0
  27. {tango_python-1.18.1 → tango_python-1.19.0}/scripts/refresh_contract.py +0 -0
  28. {tango_python-1.18.1 → tango_python-1.19.0}/scripts/smoke_api_parity.py +0 -0
  29. {tango_python-1.18.1 → tango_python-1.19.0}/scripts/test_production.py +0 -0
  30. {tango_python-1.18.1 → tango_python-1.19.0}/tango/models.py +0 -0
  31. {tango_python-1.18.1 → tango_python-1.19.0}/tango/shapes/__init__.py +0 -0
  32. {tango_python-1.18.1 → tango_python-1.19.0}/tango/shapes/explicit_schemas.py +0 -0
  33. {tango_python-1.18.1 → tango_python-1.19.0}/tango/shapes/factory.py +0 -0
  34. {tango_python-1.18.1 → tango_python-1.19.0}/tango/shapes/generated_overlay.py +0 -0
  35. {tango_python-1.18.1 → tango_python-1.19.0}/tango/shapes/generator.py +0 -0
  36. {tango_python-1.18.1 → tango_python-1.19.0}/tango/shapes/models.py +0 -0
  37. {tango_python-1.18.1 → tango_python-1.19.0}/tango/shapes/parser.py +0 -0
  38. {tango_python-1.18.1 → tango_python-1.19.0}/tango/shapes/schema.py +0 -0
  39. {tango_python-1.18.1 → tango_python-1.19.0}/tango/shapes/types.py +0 -0
  40. {tango_python-1.18.1 → tango_python-1.19.0}/tango/webhooks/__init__.py +0 -0
  41. {tango_python-1.18.1 → tango_python-1.19.0}/tango/webhooks/cli.py +0 -0
  42. {tango_python-1.18.1 → tango_python-1.19.0}/tango/webhooks/receiver.py +0 -0
  43. {tango_python-1.18.1 → tango_python-1.19.0}/tango/webhooks/signing.py +0 -0
  44. {tango_python-1.18.1 → tango_python-1.19.0}/tango/webhooks/simulate.py +0 -0
  45. {tango_python-1.18.1 → tango_python-1.19.0}/tests/__init__.py +0 -0
  46. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestAgenciesIntegration.test_get_agency +0 -0
  47. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestAgenciesIntegration.test_list_agencies +0 -0
  48. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_field_type_validation +0 -0
  49. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_parsing_consistency +0 -0
  50. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestBusinessTypesIntegration.test_list_business_types +0 -0
  51. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestContractsIntegration.test_combined_filters_work_together +0 -0
  52. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestContractsIntegration.test_contract_cursor_pagination +0 -0
  53. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestContractsIntegration.test_contract_data_object_parsing +0 -0
  54. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestContractsIntegration.test_contract_field_types +0 -0
  55. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[keyword-software] +0 -0
  56. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[psc_code-R425] +0 -0
  57. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_awarding_agency_filter +0 -0
  58. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_date_range_filter +0 -0
  59. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_flat +0 -0
  60. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_naics_code_filter +0 -0
  61. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[custom-key,piid,recipient(display_name),total_contract_value,award_date] +0 -0
  62. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[default-None] +0 -0
  63. {tango_python-1.18.1 → tango_python-1.19.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
  64. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[minimal-key,piid,award_date,recipient(display_name),description,total_contract_value] +0 -0
  65. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestContractsIntegration.test_new_expiring_filters +0 -0
  66. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestContractsIntegration.test_new_fiscal_year_range_filters +0 -0
  67. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestContractsIntegration.test_new_identifier_filters +0 -0
  68. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestContractsIntegration.test_search_contracts_with_filters +0 -0
  69. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestContractsIntegration.test_search_filters_object_with_new_parameters +0 -0
  70. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[asc-] +0 -0
  71. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[desc--] +0 -0
  72. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_contracts +0 -0
  73. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_entities +0 -0
  74. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEdgeCasesIntegration.test_date_field_parsing_edge_cases +0 -0
  75. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEdgeCasesIntegration.test_decimal_field_parsing_edge_cases +0 -0
  76. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEdgeCasesIntegration.test_empty_list_responses +0 -0
  77. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEdgeCasesIntegration.test_entity_parsing_with_various_address_formats +0 -0
  78. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEdgeCasesIntegration.test_flattened_responses_with_flat_lists +0 -0
  79. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEdgeCasesIntegration.test_list_field_parsing_consistency +0 -0
  80. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_nested_objects_with_missing_data +0 -0
  81. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_null_missing_fields_in_contracts +0 -0
  82. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_with_minimal_shape_sparse_data +0 -0
  83. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEntitiesIntegration.test_entity_field_types +0 -0
  84. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEntitiesIntegration.test_entity_location_parsing +0 -0
  85. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEntitiesIntegration.test_entity_parsing_with_business_types +0 -0
  86. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEntitiesIntegration.test_entity_with_various_identifiers +0 -0
  87. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_budget_flows +0 -0
  88. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_by_uei +0 -0
  89. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_flat +0 -0
  90. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_search +0 -0
  91. {tango_python-1.18.1 → tango_python-1.19.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
  92. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[custom-uei,legal_business_name,cage_code] +0 -0
  93. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
  94. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[with_address-uei,legal_business_name,cage_code,business_types,physical_address] +0 -0
  95. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestForecastsIntegration.test_forecast_field_types +0 -0
  96. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[custom-id,title,anticipated_award_date] +0 -0
  97. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[default-None] +0 -0
  98. {tango_python-1.18.1 → tango_python-1.19.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
  99. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[minimal-id,title,anticipated_award_date,fiscal_year,naics_code,status] +0 -0
  100. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestGrantsIntegration.test_grant_field_types +0 -0
  101. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestGrantsIntegration.test_grant_pagination +0 -0
  102. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[custom-grant_id,title,opportunity_number] +0 -0
  103. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[default-None] +0 -0
  104. {tango_python-1.18.1 → tango_python-1.19.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
  105. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[minimal-grant_id,opportunity_number,title,status(-),agency_code] +0 -0
  106. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestIDVsIntegration.test_get_idv_uses_default_shape +0 -0
  107. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_awards_uses_default_shape +0 -0
  108. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_child_idvs_uses_default_shape +0 -0
  109. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_transactions +0 -0
  110. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestIDVsIntegration.test_list_idvs_uses_default_shape_and_keyset_params +0 -0
  111. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_code +0 -0
  112. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_name_text +0 -0
  113. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating +0 -0
  114. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating_max +0 -0
  115. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_performance_risk +0 -0
  116. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_type_of_investment +0 -0
  117. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_updated_time_range +0 -0
  118. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestITDashboardIntegration.test_funding_and_cio_evaluation_expansions +0 -0
  119. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestITDashboardIntegration.test_get_itdashboard_investment_by_uii +0 -0
  120. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestITDashboardIntegration.test_itdashboard_pagination +0 -0
  121. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_search +0 -0
  122. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[custom-uii,agency_name,investment_title,updated_time] +0 -0
  123. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[default-None] +0 -0
  124. {tango_python-1.18.1 → tango_python-1.19.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
  125. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestNaicsIntegration.test_list_naics +0 -0
  126. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[custom-notice_id,title,solicitation_number] +0 -0
  127. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[default-None] +0 -0
  128. {tango_python-1.18.1 → tango_python-1.19.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
  129. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
  130. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestNoticesIntegration.test_notice_field_types +0 -0
  131. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestNoticesIntegration.test_notice_pagination +0 -0
  132. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestNoticesIntegration.test_notice_with_meta_fields +0 -0
  133. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestOTAsIntegration.test_get_ota +0 -0
  134. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestOTAsIntegration.test_list_otas +0 -0
  135. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestOTIDVsIntegration.test_get_otidv +0 -0
  136. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestOTIDVsIntegration.test_list_otidvs +0 -0
  137. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestOfficesIntegration.test_get_office +0 -0
  138. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestOfficesIntegration.test_list_offices +0 -0
  139. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[custom-opportunity_id,title,solicitation_number] +0 -0
  140. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[default-None] +0 -0
  141. {tango_python-1.18.1 → tango_python-1.19.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
  142. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
  143. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestOpportunitiesIntegration.test_opportunity_field_types +0 -0
  144. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestOrganizationsIntegration.test_get_organization +0 -0
  145. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestOrganizationsIntegration.test_list_organizations +0 -0
  146. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestProtestsIntegration.test_get_protest_by_case_id +0 -0
  147. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_filter +0 -0
  148. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[custom-case_id,title,source_system,outcome] +0 -0
  149. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[default-None] +0 -0
  150. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[minimal-case_id,case_number,title,source_system,outcome,filed_date] +0 -0
  151. {tango_python-1.18.1 → tango_python-1.19.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
  152. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestProtestsIntegration.test_protest_pagination +0 -0
  153. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestSubawardsIntegration.test_list_subawards +0 -0
  154. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[custom-key,piid,description] +0 -0
  155. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[minimal-key,piid,award_date,recipient(display_name),description,total_contract_value] +0 -0
  156. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[ultra_minimal-key,piid,recipient(display_name),total_contract_value] +0 -0
  157. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
  158. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[with_address-uei,legal_business_name,cage_code,business_types,physical_address] +0 -0
  159. {tango_python-1.18.1 → tango_python-1.19.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
  160. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestTypeHintsIntegration.test_notices_dict_access[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
  161. {tango_python-1.18.1 → tango_python-1.19.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
  162. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestTypeHintsIntegration.test_opportunities_dict_access[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
  163. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists +0 -0
  164. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_with_metrics_expansion +0 -0
  165. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape +0 -0
  166. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_orders_uses_default_shape +0 -0
  167. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search +0 -0
  168. {tango_python-1.18.1 → tango_python-1.19.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_with_ordering +0 -0
  169. {tango_python-1.18.1 → tango_python-1.19.0}/tests/conftest.py +0 -0
  170. {tango_python-1.18.1 → tango_python-1.19.0}/tests/integration/README.md +0 -0
  171. {tango_python-1.18.1 → tango_python-1.19.0}/tests/integration/__init__.py +0 -0
  172. {tango_python-1.18.1 → tango_python-1.19.0}/tests/integration/conftest.py +0 -0
  173. {tango_python-1.18.1 → tango_python-1.19.0}/tests/integration/test_agencies_integration.py +0 -0
  174. {tango_python-1.18.1 → tango_python-1.19.0}/tests/integration/test_contracts_integration.py +0 -0
  175. {tango_python-1.18.1 → tango_python-1.19.0}/tests/integration/test_edge_cases_integration.py +0 -0
  176. {tango_python-1.18.1 → tango_python-1.19.0}/tests/integration/test_entities_integration.py +0 -0
  177. {tango_python-1.18.1 → tango_python-1.19.0}/tests/integration/test_forecasts_integration.py +0 -0
  178. {tango_python-1.18.1 → tango_python-1.19.0}/tests/integration/test_grants_integration.py +0 -0
  179. {tango_python-1.18.1 → tango_python-1.19.0}/tests/integration/test_itdashboard_integration.py +0 -0
  180. {tango_python-1.18.1 → tango_python-1.19.0}/tests/integration/test_naics_integration.py +0 -0
  181. {tango_python-1.18.1 → tango_python-1.19.0}/tests/integration/test_notices_integration.py +0 -0
  182. {tango_python-1.18.1 → tango_python-1.19.0}/tests/integration/test_offices_integration.py +0 -0
  183. {tango_python-1.18.1 → tango_python-1.19.0}/tests/integration/test_opportunities_integration.py +0 -0
  184. {tango_python-1.18.1 → tango_python-1.19.0}/tests/integration/test_organizations_integration.py +0 -0
  185. {tango_python-1.18.1 → tango_python-1.19.0}/tests/integration/test_otas_otidvs_integration.py +0 -0
  186. {tango_python-1.18.1 → tango_python-1.19.0}/tests/integration/test_protests_integration.py +0 -0
  187. {tango_python-1.18.1 → tango_python-1.19.0}/tests/integration/test_reference_data_integration.py +0 -0
  188. {tango_python-1.18.1 → tango_python-1.19.0}/tests/integration/test_subawards_integration.py +0 -0
  189. {tango_python-1.18.1 → tango_python-1.19.0}/tests/integration/test_vehicles_idvs_integration.py +0 -0
  190. {tango_python-1.18.1 → tango_python-1.19.0}/tests/integration/validation.py +0 -0
  191. {tango_python-1.18.1 → tango_python-1.19.0}/tests/production/__init__.py +0 -0
  192. {tango_python-1.18.1 → tango_python-1.19.0}/tests/production/conftest.py +0 -0
  193. {tango_python-1.18.1 → tango_python-1.19.0}/tests/production/test_production_smoke.py +0 -0
  194. {tango_python-1.18.1 → tango_python-1.19.0}/tests/test_contract_appeals.py +0 -0
  195. {tango_python-1.18.1 → tango_python-1.19.0}/tests/test_dibbs_exclusions_sbir.py +0 -0
  196. {tango_python-1.18.1 → tango_python-1.19.0}/tests/test_ebuy.py +0 -0
  197. {tango_python-1.18.1 → tango_python-1.19.0}/tests/test_federal_register.py +0 -0
  198. {tango_python-1.18.1 → tango_python-1.19.0}/tests/test_models.py +0 -0
  199. {tango_python-1.18.1 → tango_python-1.19.0}/tests/test_obligations_by_fiscal_year.py +0 -0
  200. {tango_python-1.18.1 → tango_python-1.19.0}/tests/test_opportunity_attachments.py +0 -0
  201. {tango_python-1.18.1 → tango_python-1.19.0}/tests/test_opportunity_awards.py +0 -0
  202. {tango_python-1.18.1 → tango_python-1.19.0}/tests/test_organization_scope.py +0 -0
  203. {tango_python-1.18.1 → tango_python-1.19.0}/tests/test_shape_coverage_gate.py +0 -0
  204. {tango_python-1.18.1 → tango_python-1.19.0}/tests/test_shapes.py +0 -0
  205. {tango_python-1.18.1 → tango_python-1.19.0}/tests/test_sled.py +0 -0
  206. {tango_python-1.18.1 → tango_python-1.19.0}/tests/test_webhooks_cli.py +0 -0
  207. {tango_python-1.18.1 → tango_python-1.19.0}/tests/test_webhooks_receiver.py +0 -0
  208. {tango_python-1.18.1 → tango_python-1.19.0}/tests/test_webhooks_signing.py +0 -0
  209. {tango_python-1.18.1 → tango_python-1.19.0}/tests/test_webhooks_simulate.py +0 -0
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.19.0] - 2026-10-21
11
+
12
+ ### Added
13
+
14
+ - **`group_by`, `fiscal_year` and `rolling` on `get_entity_metrics()`, `get_naics_metrics()` and `get_psc_metrics()`.** The metrics endpoints have always accepted these query parameters, but the SDK built only the path, so callers could not split obligations by funding `"agency"` or `"department"` or bucket them by federal fiscal year. Each is optional; existing calls send the same request as before.
15
+
16
+ ## [1.18.2] - 2026-10-19
17
+
18
+ ### Fixed
19
+
20
+ - **`TangoRateLimitError.limit_type` no longer reports a daily lockout as `burst`.** It matched "minute" anywhere in the API's detail, and a daily 429 reads `Rate limit exceeded for daily. Please try again in 16 hours, 58 minutes.`, so callers were told to retry a burst limit they could not clear until midnight UTC. It now reads the rate's name from `exceeded for <name>` and checks for daily first.
21
+
10
22
  ## [1.18.1] - 2026-10-19
11
23
 
12
24
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tango-python
3
- Version: 1.18.1
3
+ Version: 1.19.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
@@ -2071,6 +2071,16 @@ psc = client.get_psc("D302")
2071
2071
  metrics = client.get_psc_metrics(code="D302", months=12, period_grouping="month")
2072
2072
  ```
2073
2073
 
2074
+ **Parameters:**
2075
+ - `code` (str): PSC code. Required.
2076
+ - `months` (int): Trailing window in months.
2077
+ - `period_grouping` (str): `"month"`, `"quarter"` or `"year"`.
2078
+ - `group_by` (`"agency"` | `"department"` | list | None): Split each period by funding organization. `agency` is an L2 short code such as `"2100"`; `department` is the CGAC code as an int, e.g. `97` for DoD.
2079
+ - `fiscal_year` (bool): Bucket by federal fiscal year instead of calendar periods. Default `False`.
2080
+ - `rolling` (bool): Add rolling averages. The API skips them, with a `warning`, when `group_by` is set. Default `False`.
2081
+
2082
+ `get_naics_metrics()` and `get_entity_metrics()` take the same options.
2083
+
2074
2084
  ---
2075
2085
 
2076
2086
  ## MAS SINs
@@ -2203,8 +2213,14 @@ lcats = client.list_entity_lcats("ABCDEF123456", limit=25)
2203
2213
 
2204
2214
  ```python
2205
2215
  metrics = client.get_entity_metrics("ABCDEF123456", months=12, period_grouping="month")
2216
+ # five fiscal years of obligations, split by funding department
2217
+ by_dept = client.get_entity_metrics(
2218
+ "ABCDEF123456", months=60, period_grouping="year", group_by="department", fiscal_year=True
2219
+ )
2206
2220
  ```
2207
2221
 
2222
+ Takes the same `group_by`, `fiscal_year` and `rolling` options as [`get_psc_metrics()`](#get_psc_metrics).
2223
+
2208
2224
  ### get_entity_budget_flows()
2209
2225
 
2210
2226
  Get budget flows for an entity (`/api/entities/{uei}/budget-flows/`) — the federal accounts that funded contracts and assistance awarded to this entity.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "tango-python"
7
- version = "1.18.1"
7
+ version = "1.19.0"
8
8
  description = "Python SDK for the Tango API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -62,7 +62,7 @@ from .webhooks import (
62
62
  )
63
63
  from .webhooks.receiver import Delivery, WebhookReceiver
64
64
 
65
- __version__ = "1.18.1"
65
+ __version__ = "1.19.0"
66
66
  __all__ = [
67
67
  "TangoClient",
68
68
  "TangoAPIError",
@@ -100,6 +100,10 @@ def _error_reason(error_data: dict[str, Any]) -> str | None:
100
100
  return None
101
101
 
102
102
 
103
+ # Funding-organization keys the metrics endpoints accept in `group_by`.
104
+ MetricsGroupBy = Literal["agency", "department"]
105
+
106
+
103
107
  class TangoClient:
104
108
  """Tango API Client"""
105
109
 
@@ -5615,7 +5619,15 @@ class TangoClient:
5615
5619
  params["flat_lists"] = "true"
5616
5620
  return self._get(f"/api/psc/{code}/", params)
5617
5621
 
5618
- def get_psc_metrics(self, code: str, months: int, period_grouping: str) -> dict[str, Any]:
5622
+ def get_psc_metrics(
5623
+ self,
5624
+ code: str,
5625
+ months: int,
5626
+ period_grouping: str,
5627
+ group_by: MetricsGroupBy | list[MetricsGroupBy] | None = None,
5628
+ fiscal_year: bool = False,
5629
+ rolling: bool = False,
5630
+ ) -> dict[str, Any]:
5619
5631
  """Get rolling PSC metrics (`/api/psc/{code}/metrics/{months}/{period_grouping}/`).
5620
5632
 
5621
5633
  Args:
@@ -5623,10 +5635,32 @@ class TangoClient:
5623
5635
  months: Window size in months (e.g. 6, 12, 24, 36).
5624
5636
  period_grouping: ``"month"``, ``"quarter"``, or ``"year"`` (the
5625
5637
  values the API accepts on the path segment).
5638
+ group_by: Split each period by the funding ``"agency"`` (an L2 short code such as ``"2100"``) or ``"department"`` (the CGAC code as an int, e.g. ``97`` for DoD), or both.
5639
+ fiscal_year: Bucket by federal fiscal year (October-September) instead of calendar periods.
5640
+ rolling: Add rolling averages. The API skips them, with a ``warning``, when ``group_by`` is set.
5626
5641
  """
5627
5642
  if not code:
5628
5643
  raise TangoValidationError("PSC code is required")
5629
- return self._get(f"/api/psc/{code}/metrics/{months}/{period_grouping}/")
5644
+ return self._get(
5645
+ f"/api/psc/{code}/metrics/{months}/{period_grouping}/",
5646
+ self._metrics_params(group_by, fiscal_year, rolling),
5647
+ )
5648
+
5649
+ @staticmethod
5650
+ def _metrics_params(
5651
+ group_by: MetricsGroupBy | list[MetricsGroupBy] | None,
5652
+ fiscal_year: bool,
5653
+ rolling: bool,
5654
+ ) -> dict[str, Any]:
5655
+ """Query params shared by the entity, NAICS and PSC metrics endpoints."""
5656
+ params: dict[str, Any] = {}
5657
+ if group_by:
5658
+ params["group_by"] = group_by if isinstance(group_by, str) else ",".join(group_by)
5659
+ if fiscal_year:
5660
+ params["fiscal_year"] = "true"
5661
+ if rolling:
5662
+ params["rolling"] = "true"
5663
+ return params
5630
5664
 
5631
5665
  def get_naics(
5632
5666
  self,
@@ -5647,11 +5681,25 @@ class TangoClient:
5647
5681
  params["flat_lists"] = "true"
5648
5682
  return self._get(f"/api/naics/{code}/", params)
5649
5683
 
5650
- def get_naics_metrics(self, code: str, months: int, period_grouping: str) -> dict[str, Any]:
5651
- """Get rolling NAICS metrics (`/api/naics/{code}/metrics/{months}/{period_grouping}/`)."""
5684
+ def get_naics_metrics(
5685
+ self,
5686
+ code: str,
5687
+ months: int,
5688
+ period_grouping: str,
5689
+ group_by: MetricsGroupBy | list[MetricsGroupBy] | None = None,
5690
+ fiscal_year: bool = False,
5691
+ rolling: bool = False,
5692
+ ) -> dict[str, Any]:
5693
+ """Get rolling NAICS metrics (`/api/naics/{code}/metrics/{months}/{period_grouping}/`).
5694
+
5695
+ Takes the same ``group_by``, ``fiscal_year`` and ``rolling`` options as :meth:`get_psc_metrics`.
5696
+ """
5652
5697
  if not code:
5653
5698
  raise TangoValidationError("NAICS code is required")
5654
- return self._get(f"/api/naics/{code}/metrics/{months}/{period_grouping}/")
5699
+ return self._get(
5700
+ f"/api/naics/{code}/metrics/{months}/{period_grouping}/",
5701
+ self._metrics_params(group_by, fiscal_year, rolling),
5702
+ )
5655
5703
 
5656
5704
  def get_business_type(
5657
5705
  self,
@@ -6026,13 +6074,26 @@ class TangoClient:
6026
6074
  meta=data.get("meta"),
6027
6075
  )
6028
6076
 
6029
- def get_entity_metrics(self, uei: str, months: int, period_grouping: str) -> dict[str, Any]:
6077
+ def get_entity_metrics(
6078
+ self,
6079
+ uei: str,
6080
+ months: int,
6081
+ period_grouping: str,
6082
+ group_by: MetricsGroupBy | list[MetricsGroupBy] | None = None,
6083
+ fiscal_year: bool = False,
6084
+ rolling: bool = False,
6085
+ ) -> dict[str, Any]:
6030
6086
  """Get rolling metrics for an entity
6031
6087
  (`/api/entities/{uei}/metrics/{months}/{period_grouping}/`).
6088
+
6089
+ Takes the same ``group_by``, ``fiscal_year`` and ``rolling`` options as :meth:`get_psc_metrics`.
6032
6090
  """
6033
6091
  if not uei:
6034
6092
  raise TangoValidationError("UEI is required")
6035
- return self._get(f"/api/entities/{uei}/metrics/{months}/{period_grouping}/")
6093
+ return self._get(
6094
+ f"/api/entities/{uei}/metrics/{months}/{period_grouping}/",
6095
+ self._metrics_params(group_by, fiscal_year, rolling),
6096
+ )
6036
6097
 
6037
6098
  # ============================================================================
6038
6099
  # IDV sub-resources
@@ -1,7 +1,11 @@
1
1
  """Tango API exceptions"""
2
2
 
3
+ import re
3
4
  from typing import Any
4
5
 
6
+ # The API names the tripped rate in its 429 detail: "Rate limit exceeded for daily. Please try again in 16 hours, 58 minutes."
7
+ _RATE_NAME = re.compile(r"exceeded for ([\w-]+)", re.IGNORECASE)
8
+
5
9
 
6
10
  class TangoAPIError(Exception):
7
11
  """Base exception for Tango API errors"""
@@ -88,11 +92,13 @@ class TangoRateLimitError(TangoAPIError):
88
92
  d = self.detail
89
93
  if not d:
90
94
  return None
91
- lower = d.lower()
92
- if "burst" in lower or "minute" in lower:
93
- return "burst"
94
- if "daily" in lower or "day" in lower:
95
+ # Read the rate's name, not the whole sentence: a daily lockout's wait reads "16 hours, 58 minutes", which a match on "minute" took for burst.
96
+ match = _RATE_NAME.search(d)
97
+ text = match.group(1).lower() if match else d.lower()
98
+ if "daily" in text or re.search(r"\bday\b", text):
95
99
  return "daily"
100
+ if "burst" in text or "minute" in text:
101
+ return "burst"
96
102
  return None
97
103
 
98
104
 
@@ -372,6 +372,23 @@ class TestReferenceData:
372
372
  client = TangoClient(api_key="x", base_url="https://t.example")
373
373
  client.get_psc_metrics("R425", 12, "month")
374
374
  assert mock_request.call_args[1]["url"].endswith("/api/psc/R425/metrics/12/month/")
375
+ assert not mock_request.call_args[1]["params"]
376
+
377
+ def test_metrics_query_params(self, mock_request: Mock) -> None:
378
+ mock_request.return_value = _mock_response({"results": []})
379
+ client = TangoClient(api_key="x", base_url="https://t.example")
380
+ client.get_psc_metrics("R425", 12, "year", group_by="department", fiscal_year=True)
381
+ assert mock_request.call_args[1]["params"] == {
382
+ "group_by": "department",
383
+ "fiscal_year": "true",
384
+ }
385
+ client.get_naics_metrics(
386
+ "541511", 12, "year", group_by=["agency", "department"], rolling=True
387
+ )
388
+ assert mock_request.call_args[1]["params"] == {
389
+ "group_by": "agency,department",
390
+ "rolling": "true",
391
+ }
375
392
 
376
393
  def test_get_naics(self, mock_request: Mock) -> None:
377
394
  mock_request.return_value = _mock_response({"code": "541511"})
@@ -409,6 +426,15 @@ class TestEntitySubResources:
409
426
  client.get_entity_metrics("UEI1", 24, "quarter")
410
427
  assert mock_request.call_args[1]["url"].endswith("/api/entities/UEI1/metrics/24/quarter/")
411
428
 
429
+ def test_get_entity_metrics_by_department(self, mock_request: Mock) -> None:
430
+ mock_request.return_value = _mock_response({"results": []})
431
+ client = TangoClient(api_key="x", base_url="https://t.example")
432
+ client.get_entity_metrics("UEI1", 60, "year", group_by="department", fiscal_year=True)
433
+ assert mock_request.call_args[1]["params"] == {
434
+ "group_by": "department",
435
+ "fiscal_year": "true",
436
+ }
437
+
412
438
  def test_list_entity_lcats(self, mock_request: Mock) -> None:
413
439
  mock_request.return_value = _mock_response(
414
440
  {"count": 0, "next": None, "previous": None, "results": []}
@@ -1502,6 +1502,22 @@ class TestErrorHandling:
1502
1502
  assert exc_info.value.limit_type == "daily"
1503
1503
  assert exc_info.value.wait_in_seconds == 3600
1504
1504
 
1505
+ @pytest.mark.parametrize(
1506
+ ("detail", "expected"),
1507
+ [
1508
+ # The API's own wording, with its waits as convert_seconds_to_human_readable_wait_time renders them.
1509
+ ("Rate limit exceeded for daily. Please try again in 16 hours, 58 minutes.", "daily"),
1510
+ ("Rate limit exceeded for daily. Please try again in 1 minute.", "daily"),
1511
+ ("Rate limit exceeded for burst. Please try again in 45 seconds.", "burst"),
1512
+ ("Rate limit exceeded for burst. Please try again in 1 minute.", "burst"),
1513
+ ("Too many requests.", None),
1514
+ ],
1515
+ )
1516
+ def test_limit_type_reads_the_rate_name_not_the_wait(self, detail, expected):
1517
+ error = TangoRateLimitError("Rate limit exceeded", 429, {"detail": detail})
1518
+
1519
+ assert error.limit_type == expected
1520
+
1505
1521
  @patch("tango.client.httpx.Client.request")
1506
1522
  def test_429_empty_body(self, mock_request):
1507
1523
  """Test 429 with no content body still works"""
File without changes
File without changes
File without changes