tango-python 1.1.3__tar.gz → 1.2.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 (198) hide show
  1. {tango_python-1.1.3 → tango_python-1.2.0}/CHANGELOG.md +20 -0
  2. {tango_python-1.1.3 → tango_python-1.2.0}/PKG-INFO +1 -1
  3. {tango_python-1.1.3 → tango_python-1.2.0}/pyproject.toml +1 -1
  4. {tango_python-1.1.3 → tango_python-1.2.0}/tango/__init__.py +1 -1
  5. {tango_python-1.1.3 → tango_python-1.2.0}/tango/client.py +260 -3
  6. {tango_python-1.1.3 → tango_python-1.2.0}/tests/test_client.py +68 -0
  7. {tango_python-1.1.3 → tango_python-1.2.0}/.github/workflows/docs-dispatch.yml +0 -0
  8. {tango_python-1.1.3 → tango_python-1.2.0}/.github/workflows/lint.yml +0 -0
  9. {tango_python-1.1.3 → tango_python-1.2.0}/.github/workflows/publish.yml +0 -0
  10. {tango_python-1.1.3 → tango_python-1.2.0}/.github/workflows/test.yml +0 -0
  11. {tango_python-1.1.3 → tango_python-1.2.0}/.gitignore +0 -0
  12. {tango_python-1.1.3 → tango_python-1.2.0}/LICENSE +0 -0
  13. {tango_python-1.1.3 → tango_python-1.2.0}/README.md +0 -0
  14. {tango_python-1.1.3 → tango_python-1.2.0}/docs/API_REFERENCE.md +0 -0
  15. {tango_python-1.1.3 → tango_python-1.2.0}/docs/CLIENT.md +0 -0
  16. {tango_python-1.1.3 → tango_python-1.2.0}/docs/DYNAMIC_MODELS.md +0 -0
  17. {tango_python-1.1.3 → tango_python-1.2.0}/docs/ERRORS.md +0 -0
  18. {tango_python-1.1.3 → tango_python-1.2.0}/docs/PAGINATION.md +0 -0
  19. {tango_python-1.1.3 → tango_python-1.2.0}/docs/SHAPES.md +0 -0
  20. {tango_python-1.1.3 → tango_python-1.2.0}/docs/WEBHOOKS.md +0 -0
  21. {tango_python-1.1.3 → tango_python-1.2.0}/scripts/README.md +0 -0
  22. {tango_python-1.1.3 → tango_python-1.2.0}/scripts/check_filter_shape_conformance.py +0 -0
  23. {tango_python-1.1.3 → tango_python-1.2.0}/scripts/fetch_api_schema.py +0 -0
  24. {tango_python-1.1.3 → tango_python-1.2.0}/scripts/generate_schemas_from_api.py +0 -0
  25. {tango_python-1.1.3 → tango_python-1.2.0}/scripts/pr_review.py +0 -0
  26. {tango_python-1.1.3 → tango_python-1.2.0}/scripts/smoke_api_parity.py +0 -0
  27. {tango_python-1.1.3 → tango_python-1.2.0}/scripts/test_production.py +0 -0
  28. {tango_python-1.1.3 → tango_python-1.2.0}/tango/exceptions.py +0 -0
  29. {tango_python-1.1.3 → tango_python-1.2.0}/tango/models.py +0 -0
  30. {tango_python-1.1.3 → tango_python-1.2.0}/tango/shapes/__init__.py +0 -0
  31. {tango_python-1.1.3 → tango_python-1.2.0}/tango/shapes/explicit_schemas.py +0 -0
  32. {tango_python-1.1.3 → tango_python-1.2.0}/tango/shapes/factory.py +0 -0
  33. {tango_python-1.1.3 → tango_python-1.2.0}/tango/shapes/generator.py +0 -0
  34. {tango_python-1.1.3 → tango_python-1.2.0}/tango/shapes/models.py +0 -0
  35. {tango_python-1.1.3 → tango_python-1.2.0}/tango/shapes/parser.py +0 -0
  36. {tango_python-1.1.3 → tango_python-1.2.0}/tango/shapes/schema.py +0 -0
  37. {tango_python-1.1.3 → tango_python-1.2.0}/tango/shapes/types.py +0 -0
  38. {tango_python-1.1.3 → tango_python-1.2.0}/tango/webhooks/__init__.py +0 -0
  39. {tango_python-1.1.3 → tango_python-1.2.0}/tango/webhooks/cli.py +0 -0
  40. {tango_python-1.1.3 → tango_python-1.2.0}/tango/webhooks/receiver.py +0 -0
  41. {tango_python-1.1.3 → tango_python-1.2.0}/tango/webhooks/signing.py +0 -0
  42. {tango_python-1.1.3 → tango_python-1.2.0}/tango/webhooks/simulate.py +0 -0
  43. {tango_python-1.1.3 → tango_python-1.2.0}/tests/__init__.py +0 -0
  44. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestAgenciesIntegration.test_get_agency +0 -0
  45. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestAgenciesIntegration.test_list_agencies +0 -0
  46. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_field_type_validation +0 -0
  47. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_parsing_consistency +0 -0
  48. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestBusinessTypesIntegration.test_list_business_types +0 -0
  49. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestContractsIntegration.test_combined_filters_work_together +0 -0
  50. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestContractsIntegration.test_contract_cursor_pagination +0 -0
  51. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestContractsIntegration.test_contract_data_object_parsing +0 -0
  52. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestContractsIntegration.test_contract_field_types +0 -0
  53. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[keyword-software] +0 -0
  54. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[psc_code-R425] +0 -0
  55. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_awarding_agency_filter +0 -0
  56. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_date_range_filter +0 -0
  57. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_flat +0 -0
  58. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_naics_code_filter +0 -0
  59. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[custom-key,piid,recipient(display_name),total_contract_value,award_date] +0 -0
  60. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[default-None] +0 -0
  61. {tango_python-1.1.3 → tango_python-1.2.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
  62. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[minimal-key,piid,award_date,recipient(display_name),description,total_contract_value] +0 -0
  63. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestContractsIntegration.test_new_expiring_filters +0 -0
  64. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestContractsIntegration.test_new_fiscal_year_range_filters +0 -0
  65. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestContractsIntegration.test_new_identifier_filters +0 -0
  66. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestContractsIntegration.test_search_contracts_with_filters +0 -0
  67. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestContractsIntegration.test_search_filters_object_with_new_parameters +0 -0
  68. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[asc-] +0 -0
  69. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[desc--] +0 -0
  70. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_contracts +0 -0
  71. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_entities +0 -0
  72. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEdgeCasesIntegration.test_date_field_parsing_edge_cases +0 -0
  73. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEdgeCasesIntegration.test_decimal_field_parsing_edge_cases +0 -0
  74. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEdgeCasesIntegration.test_empty_list_responses +0 -0
  75. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEdgeCasesIntegration.test_entity_parsing_with_various_address_formats +0 -0
  76. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEdgeCasesIntegration.test_flattened_responses_with_flat_lists +0 -0
  77. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEdgeCasesIntegration.test_list_field_parsing_consistency +0 -0
  78. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_nested_objects_with_missing_data +0 -0
  79. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_null_missing_fields_in_contracts +0 -0
  80. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_with_minimal_shape_sparse_data +0 -0
  81. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEntitiesIntegration.test_entity_field_types +0 -0
  82. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEntitiesIntegration.test_entity_location_parsing +0 -0
  83. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEntitiesIntegration.test_entity_parsing_with_business_types +0 -0
  84. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEntitiesIntegration.test_entity_with_various_identifiers +0 -0
  85. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_budget_flows +0 -0
  86. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_by_uei +0 -0
  87. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_flat +0 -0
  88. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_search +0 -0
  89. {tango_python-1.1.3 → tango_python-1.2.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
  90. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[custom-uei,legal_business_name,cage_code] +0 -0
  91. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
  92. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[with_address-uei,legal_business_name,cage_code,business_types,physical_address] +0 -0
  93. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestForecastsIntegration.test_forecast_field_types +0 -0
  94. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[custom-id,title,anticipated_award_date] +0 -0
  95. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[default-None] +0 -0
  96. {tango_python-1.1.3 → tango_python-1.2.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
  97. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[minimal-id,title,anticipated_award_date,fiscal_year,naics_code,status] +0 -0
  98. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestGrantsIntegration.test_grant_field_types +0 -0
  99. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestGrantsIntegration.test_grant_pagination +0 -0
  100. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[custom-grant_id,title,opportunity_number] +0 -0
  101. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[default-None] +0 -0
  102. {tango_python-1.1.3 → tango_python-1.2.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
  103. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[minimal-grant_id,opportunity_number,title,status(-),agency_code] +0 -0
  104. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestIDVsIntegration.test_get_idv_uses_default_shape +0 -0
  105. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_awards_uses_default_shape +0 -0
  106. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_child_idvs_uses_default_shape +0 -0
  107. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_transactions +0 -0
  108. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestIDVsIntegration.test_list_idvs_uses_default_shape_and_keyset_params +0 -0
  109. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_code +0 -0
  110. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_name_text +0 -0
  111. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating +0 -0
  112. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating_max +0 -0
  113. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_performance_risk +0 -0
  114. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_type_of_investment +0 -0
  115. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_updated_time_range +0 -0
  116. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestITDashboardIntegration.test_funding_and_cio_evaluation_expansions +0 -0
  117. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestITDashboardIntegration.test_get_itdashboard_investment_by_uii +0 -0
  118. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestITDashboardIntegration.test_itdashboard_pagination +0 -0
  119. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_search +0 -0
  120. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[custom-uii,agency_name,investment_title,updated_time] +0 -0
  121. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[default-None] +0 -0
  122. {tango_python-1.1.3 → tango_python-1.2.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
  123. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestNaicsIntegration.test_list_naics +0 -0
  124. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[custom-notice_id,title,solicitation_number] +0 -0
  125. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[default-None] +0 -0
  126. {tango_python-1.1.3 → tango_python-1.2.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
  127. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
  128. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestNoticesIntegration.test_notice_field_types +0 -0
  129. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestNoticesIntegration.test_notice_pagination +0 -0
  130. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestNoticesIntegration.test_notice_with_meta_fields +0 -0
  131. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestOTAsIntegration.test_get_ota +0 -0
  132. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestOTAsIntegration.test_list_otas +0 -0
  133. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestOTIDVsIntegration.test_get_otidv +0 -0
  134. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestOTIDVsIntegration.test_list_otidvs +0 -0
  135. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestOfficesIntegration.test_get_office +0 -0
  136. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestOfficesIntegration.test_list_offices +0 -0
  137. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[custom-opportunity_id,title,solicitation_number] +0 -0
  138. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[default-None] +0 -0
  139. {tango_python-1.1.3 → tango_python-1.2.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
  140. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
  141. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestOpportunitiesIntegration.test_opportunity_field_types +0 -0
  142. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestOrganizationsIntegration.test_get_organization +0 -0
  143. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestOrganizationsIntegration.test_list_organizations +0 -0
  144. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestProtestsIntegration.test_get_protest_by_case_id +0 -0
  145. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_filter +0 -0
  146. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[custom-case_id,title,source_system,outcome] +0 -0
  147. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[default-None] +0 -0
  148. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[minimal-case_id,case_number,title,source_system,outcome,filed_date] +0 -0
  149. {tango_python-1.1.3 → tango_python-1.2.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
  150. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestProtestsIntegration.test_protest_pagination +0 -0
  151. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestSubawardsIntegration.test_list_subawards +0 -0
  152. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[custom-key,piid,description] +0 -0
  153. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[minimal-key,piid,award_date,recipient(display_name),description,total_contract_value] +0 -0
  154. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[ultra_minimal-key,piid,recipient(display_name),total_contract_value] +0 -0
  155. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
  156. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[with_address-uei,legal_business_name,cage_code,business_types,physical_address] +0 -0
  157. {tango_python-1.1.3 → tango_python-1.2.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
  158. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestTypeHintsIntegration.test_notices_dict_access[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
  159. {tango_python-1.1.3 → tango_python-1.2.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
  160. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestTypeHintsIntegration.test_opportunities_dict_access[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
  161. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists +0 -0
  162. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_with_metrics_expansion +0 -0
  163. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape +0 -0
  164. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_orders_uses_default_shape +0 -0
  165. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search +0 -0
  166. {tango_python-1.1.3 → tango_python-1.2.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_with_ordering +0 -0
  167. {tango_python-1.1.3 → tango_python-1.2.0}/tests/conftest.py +0 -0
  168. {tango_python-1.1.3 → tango_python-1.2.0}/tests/integration/README.md +0 -0
  169. {tango_python-1.1.3 → tango_python-1.2.0}/tests/integration/__init__.py +0 -0
  170. {tango_python-1.1.3 → tango_python-1.2.0}/tests/integration/conftest.py +0 -0
  171. {tango_python-1.1.3 → tango_python-1.2.0}/tests/integration/test_agencies_integration.py +0 -0
  172. {tango_python-1.1.3 → tango_python-1.2.0}/tests/integration/test_contracts_integration.py +0 -0
  173. {tango_python-1.1.3 → tango_python-1.2.0}/tests/integration/test_edge_cases_integration.py +0 -0
  174. {tango_python-1.1.3 → tango_python-1.2.0}/tests/integration/test_entities_integration.py +0 -0
  175. {tango_python-1.1.3 → tango_python-1.2.0}/tests/integration/test_forecasts_integration.py +0 -0
  176. {tango_python-1.1.3 → tango_python-1.2.0}/tests/integration/test_grants_integration.py +0 -0
  177. {tango_python-1.1.3 → tango_python-1.2.0}/tests/integration/test_itdashboard_integration.py +0 -0
  178. {tango_python-1.1.3 → tango_python-1.2.0}/tests/integration/test_naics_integration.py +0 -0
  179. {tango_python-1.1.3 → tango_python-1.2.0}/tests/integration/test_notices_integration.py +0 -0
  180. {tango_python-1.1.3 → tango_python-1.2.0}/tests/integration/test_offices_integration.py +0 -0
  181. {tango_python-1.1.3 → tango_python-1.2.0}/tests/integration/test_opportunities_integration.py +0 -0
  182. {tango_python-1.1.3 → tango_python-1.2.0}/tests/integration/test_organizations_integration.py +0 -0
  183. {tango_python-1.1.3 → tango_python-1.2.0}/tests/integration/test_otas_otidvs_integration.py +0 -0
  184. {tango_python-1.1.3 → tango_python-1.2.0}/tests/integration/test_protests_integration.py +0 -0
  185. {tango_python-1.1.3 → tango_python-1.2.0}/tests/integration/test_reference_data_integration.py +0 -0
  186. {tango_python-1.1.3 → tango_python-1.2.0}/tests/integration/test_subawards_integration.py +0 -0
  187. {tango_python-1.1.3 → tango_python-1.2.0}/tests/integration/test_vehicles_idvs_integration.py +0 -0
  188. {tango_python-1.1.3 → tango_python-1.2.0}/tests/integration/validation.py +0 -0
  189. {tango_python-1.1.3 → tango_python-1.2.0}/tests/production/__init__.py +0 -0
  190. {tango_python-1.1.3 → tango_python-1.2.0}/tests/production/conftest.py +0 -0
  191. {tango_python-1.1.3 → tango_python-1.2.0}/tests/production/test_production_smoke.py +0 -0
  192. {tango_python-1.1.3 → tango_python-1.2.0}/tests/test_api_parity.py +0 -0
  193. {tango_python-1.1.3 → tango_python-1.2.0}/tests/test_models.py +0 -0
  194. {tango_python-1.1.3 → tango_python-1.2.0}/tests/test_shapes.py +0 -0
  195. {tango_python-1.1.3 → tango_python-1.2.0}/tests/test_webhooks_cli.py +0 -0
  196. {tango_python-1.1.3 → tango_python-1.2.0}/tests/test_webhooks_receiver.py +0 -0
  197. {tango_python-1.1.3 → tango_python-1.2.0}/tests/test_webhooks_signing.py +0 -0
  198. {tango_python-1.1.3 → tango_python-1.2.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.2.0] - 2026-06-05
11
+
12
+ ### Added
13
+ - `list_budget_accounts()` now exposes the full range-filter surface that the
14
+ REST endpoint has always supported. Every numeric metric on
15
+ `/api/budget/accounts/` — the 26 fields in the backend's
16
+ `RANGE_NUMERIC_FIELDS` list (`enacted_ba`, `apportioned`, `obligated_total`,
17
+ `unobligated_balance`, `contract_obligated`,
18
+ `contract_share_of_obligated_capped`, `ba_growth_next_year_pct`,
19
+ `actual_vs_requested_contract`, all the ratio fields and their `_capped`
20
+ variants, etc.) — is now accepted in three forms: exact match (`field=`),
21
+ greater-or-equal (`field_gte=`), and less-or-equal (`field_lte=`).
22
+ Previously only the identity / taxonomy filters were exposed, which forced
23
+ callers to discover accounts by exact symbol lookup; the new surface makes
24
+ pipeline-style queries (e.g. "all FY24 accounts where contract share ≥ 60%,
25
+ unobligated balance ≥ $200M, and next-year growth ≥ 15%, sorted by largest
26
+ headroom first") a single SDK call. The new params map to the API's
27
+ `field__gte` / `field__lte` form; `ordering=` already accepted any of these
28
+ fields and continues to.
29
+
10
30
  ## [1.1.3] - 2026-06-04
11
31
 
12
32
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tango-python
3
- Version: 1.1.3
3
+ Version: 1.2.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.1.3"
7
+ version = "1.2.0"
8
8
  description = "Python SDK for the Tango API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -44,7 +44,7 @@ from .webhooks import (
44
44
  )
45
45
  from .webhooks.receiver import Delivery, WebhookReceiver
46
46
 
47
- __version__ = "1.1.3"
47
+ __version__ = "1.2.0"
48
48
  __all__ = [
49
49
  "TangoClient",
50
50
  "TangoAPIError",
@@ -2649,6 +2649,85 @@ class TangoClient:
2649
2649
  bea_category: str | None = None,
2650
2650
  on_off_budget: str | None = None,
2651
2651
  subfunction_code: str | None = None,
2652
+ # Range-numeric filters: each field also accepts ``__gte`` / ``__lte``.
2653
+ requested_ba: float | None = None,
2654
+ requested_ba_gte: float | None = None,
2655
+ requested_ba_lte: float | None = None,
2656
+ enacted_ba: float | None = None,
2657
+ enacted_ba_gte: float | None = None,
2658
+ enacted_ba_lte: float | None = None,
2659
+ apportioned: float | None = None,
2660
+ apportioned_gte: float | None = None,
2661
+ apportioned_lte: float | None = None,
2662
+ obligated_total: float | None = None,
2663
+ obligated_total_gte: float | None = None,
2664
+ obligated_total_lte: float | None = None,
2665
+ outlayed_total: float | None = None,
2666
+ outlayed_total_gte: float | None = None,
2667
+ outlayed_total_lte: float | None = None,
2668
+ unobligated_balance: float | None = None,
2669
+ unobligated_balance_gte: float | None = None,
2670
+ unobligated_balance_lte: float | None = None,
2671
+ contract_obligated: float | None = None,
2672
+ contract_obligated_gte: float | None = None,
2673
+ contract_obligated_lte: float | None = None,
2674
+ contract_outlayed: float | None = None,
2675
+ contract_outlayed_gte: float | None = None,
2676
+ contract_outlayed_lte: float | None = None,
2677
+ assistance_obligated: float | None = None,
2678
+ assistance_obligated_gte: float | None = None,
2679
+ assistance_obligated_lte: float | None = None,
2680
+ assistance_outlayed: float | None = None,
2681
+ assistance_outlayed_gte: float | None = None,
2682
+ assistance_outlayed_lte: float | None = None,
2683
+ contract_share_of_obligated_capped: float | None = None,
2684
+ contract_share_of_obligated_capped_gte: float | None = None,
2685
+ contract_share_of_obligated_capped_lte: float | None = None,
2686
+ obligated_to_apportioned_pct: float | None = None,
2687
+ obligated_to_apportioned_pct_gte: float | None = None,
2688
+ obligated_to_apportioned_pct_lte: float | None = None,
2689
+ obligated_to_apportioned_pct_capped: float | None = None,
2690
+ obligated_to_apportioned_pct_capped_gte: float | None = None,
2691
+ obligated_to_apportioned_pct_capped_lte: float | None = None,
2692
+ apportioned_to_enacted_pct: float | None = None,
2693
+ apportioned_to_enacted_pct_gte: float | None = None,
2694
+ apportioned_to_enacted_pct_lte: float | None = None,
2695
+ apportioned_to_enacted_pct_capped: float | None = None,
2696
+ apportioned_to_enacted_pct_capped_gte: float | None = None,
2697
+ apportioned_to_enacted_pct_capped_lte: float | None = None,
2698
+ obligated_to_enacted_pct: float | None = None,
2699
+ obligated_to_enacted_pct_gte: float | None = None,
2700
+ obligated_to_enacted_pct_lte: float | None = None,
2701
+ obligated_to_enacted_pct_capped: float | None = None,
2702
+ obligated_to_enacted_pct_capped_gte: float | None = None,
2703
+ obligated_to_enacted_pct_capped_lte: float | None = None,
2704
+ outlayed_to_obligated_pct: float | None = None,
2705
+ outlayed_to_obligated_pct_gte: float | None = None,
2706
+ outlayed_to_obligated_pct_lte: float | None = None,
2707
+ outlayed_to_obligated_pct_capped: float | None = None,
2708
+ outlayed_to_obligated_pct_capped_gte: float | None = None,
2709
+ outlayed_to_obligated_pct_capped_lte: float | None = None,
2710
+ unobligated_pct: float | None = None,
2711
+ unobligated_pct_gte: float | None = None,
2712
+ unobligated_pct_lte: float | None = None,
2713
+ enacted_ba_yoy_pct: float | None = None,
2714
+ enacted_ba_yoy_pct_gte: float | None = None,
2715
+ enacted_ba_yoy_pct_lte: float | None = None,
2716
+ obligated_yoy_pct: float | None = None,
2717
+ obligated_yoy_pct_gte: float | None = None,
2718
+ obligated_yoy_pct_lte: float | None = None,
2719
+ enacted_ba_5yr_cagr: float | None = None,
2720
+ enacted_ba_5yr_cagr_gte: float | None = None,
2721
+ enacted_ba_5yr_cagr_lte: float | None = None,
2722
+ ba_growth_next_year_pct: float | None = None,
2723
+ ba_growth_next_year_pct_gte: float | None = None,
2724
+ ba_growth_next_year_pct_lte: float | None = None,
2725
+ actual_vs_requested_contract: float | None = None,
2726
+ actual_vs_requested_contract_gte: float | None = None,
2727
+ actual_vs_requested_contract_lte: float | None = None,
2728
+ actual_vs_requested_contract_capped: float | None = None,
2729
+ actual_vs_requested_contract_capped_gte: float | None = None,
2730
+ actual_vs_requested_contract_capped_lte: float | None = None,
2652
2731
  search: str | None = None,
2653
2732
  ordering: str | None = None,
2654
2733
  ) -> PaginatedResponse:
@@ -2674,8 +2753,49 @@ class TangoClient:
2674
2753
  bea_category: BEA category (exact).
2675
2754
  on_off_budget: On/off budget flag (exact).
2676
2755
  subfunction_code: Subfunction code (exact).
2756
+ requested_ba: President's-budget requested BA (exact). Also
2757
+ ``requested_ba_gte`` / ``requested_ba_lte`` for range queries.
2758
+ enacted_ba: Enacted budget authority (exact / gte / lte).
2759
+ apportioned: Apportioned amount (exact / gte / lte).
2760
+ obligated_total: Total obligated (exact / gte / lte).
2761
+ outlayed_total: Total outlayed (exact / gte / lte).
2762
+ unobligated_balance: Apportioned minus obligated, in dollars
2763
+ (exact / gte / lte). Use ``__gte`` to surface accounts with
2764
+ appropriated headroom that hasn't yet hit contract.
2765
+ contract_obligated: Contract-only obligated (exact / gte / lte).
2766
+ contract_outlayed: Contract-only outlayed (exact / gte / lte).
2767
+ assistance_obligated: Assistance-only obligated (exact / gte / lte).
2768
+ assistance_outlayed: Assistance-only outlayed (exact / gte / lte).
2769
+ contract_share_of_obligated_capped: Contracts as share of
2770
+ obligated, capped at 1.0 (exact / gte / lte). Use ``__gte`` to
2771
+ filter to contract-heavy accounts.
2772
+ obligated_to_apportioned_pct: Burn ratio
2773
+ (obligated / apportioned). Also ``_capped`` variant capped at
2774
+ 1.0. Both expose exact / gte / lte.
2775
+ apportioned_to_enacted_pct: Apportionment ratio
2776
+ (apportioned / enacted). Also ``_capped`` variant. Exact / gte / lte.
2777
+ obligated_to_enacted_pct: Obligated-to-enacted ratio. Also
2778
+ ``_capped`` variant. Exact / gte / lte.
2779
+ outlayed_to_obligated_pct: Spendout ratio (outlayed / obligated).
2780
+ Also ``_capped`` variant. Exact / gte / lte.
2781
+ unobligated_pct: Unobligated share of apportioned (exact / gte / lte).
2782
+ enacted_ba_yoy_pct: Year-over-year enacted BA growth
2783
+ (exact / gte / lte).
2784
+ obligated_yoy_pct: Year-over-year obligated growth
2785
+ (exact / gte / lte).
2786
+ enacted_ba_5yr_cagr: 5-year compound annual growth of enacted BA
2787
+ (exact / gte / lte).
2788
+ ba_growth_next_year_pct: Next-year requested BA growth
2789
+ (exact / gte / lte). Use ``__gte`` for forward-looking
2790
+ pipeline discovery.
2791
+ actual_vs_requested_contract: Realization ratio of contract
2792
+ obligated against the prior-year request (exact / gte / lte).
2793
+ Also ``_capped`` variant.
2677
2794
  search: Full-text search over account_title/agency_name/bureau_name.
2678
- ordering: Sort field (prefix with '-' for descending).
2795
+ ordering: Sort field (prefix with '-' for descending). Any of the
2796
+ numeric fields above is a valid ordering target — e.g.
2797
+ ``ordering="-unobligated_balance"`` to rank by largest
2798
+ headroom first.
2679
2799
  """
2680
2800
  params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
2681
2801
  if shape is None:
@@ -2686,7 +2806,7 @@ class TangoClient:
2686
2806
  params["flat"] = "true"
2687
2807
  if flat_lists:
2688
2808
  params["flat_lists"] = "true"
2689
- for key, val in (
2809
+ scalar_filters: tuple[tuple[str, Any], ...] = (
2690
2810
  ("federal_account_symbol", federal_account_symbol),
2691
2811
  ("fiscal_year", fiscal_year),
2692
2812
  ("fiscal_year__gte", fiscal_year_gte),
@@ -2699,9 +2819,146 @@ class TangoClient:
2699
2819
  ("subfunction_code", subfunction_code),
2700
2820
  ("search", search),
2701
2821
  ("ordering", ordering),
2702
- ):
2822
+ )
2823
+ for key, val in scalar_filters:
2703
2824
  if val is not None:
2704
2825
  params[key] = val
2826
+ # Range-numeric filters: each field has exact / __gte / __lte forms.
2827
+ range_filters: tuple[tuple[str, float | None, float | None, float | None], ...] = (
2828
+ ("requested_ba", requested_ba, requested_ba_gte, requested_ba_lte),
2829
+ ("enacted_ba", enacted_ba, enacted_ba_gte, enacted_ba_lte),
2830
+ ("apportioned", apportioned, apportioned_gte, apportioned_lte),
2831
+ ("obligated_total", obligated_total, obligated_total_gte, obligated_total_lte),
2832
+ ("outlayed_total", outlayed_total, outlayed_total_gte, outlayed_total_lte),
2833
+ (
2834
+ "unobligated_balance",
2835
+ unobligated_balance,
2836
+ unobligated_balance_gte,
2837
+ unobligated_balance_lte,
2838
+ ),
2839
+ (
2840
+ "contract_obligated",
2841
+ contract_obligated,
2842
+ contract_obligated_gte,
2843
+ contract_obligated_lte,
2844
+ ),
2845
+ (
2846
+ "contract_outlayed",
2847
+ contract_outlayed,
2848
+ contract_outlayed_gte,
2849
+ contract_outlayed_lte,
2850
+ ),
2851
+ (
2852
+ "assistance_obligated",
2853
+ assistance_obligated,
2854
+ assistance_obligated_gte,
2855
+ assistance_obligated_lte,
2856
+ ),
2857
+ (
2858
+ "assistance_outlayed",
2859
+ assistance_outlayed,
2860
+ assistance_outlayed_gte,
2861
+ assistance_outlayed_lte,
2862
+ ),
2863
+ (
2864
+ "contract_share_of_obligated_capped",
2865
+ contract_share_of_obligated_capped,
2866
+ contract_share_of_obligated_capped_gte,
2867
+ contract_share_of_obligated_capped_lte,
2868
+ ),
2869
+ (
2870
+ "obligated_to_apportioned_pct",
2871
+ obligated_to_apportioned_pct,
2872
+ obligated_to_apportioned_pct_gte,
2873
+ obligated_to_apportioned_pct_lte,
2874
+ ),
2875
+ (
2876
+ "obligated_to_apportioned_pct_capped",
2877
+ obligated_to_apportioned_pct_capped,
2878
+ obligated_to_apportioned_pct_capped_gte,
2879
+ obligated_to_apportioned_pct_capped_lte,
2880
+ ),
2881
+ (
2882
+ "apportioned_to_enacted_pct",
2883
+ apportioned_to_enacted_pct,
2884
+ apportioned_to_enacted_pct_gte,
2885
+ apportioned_to_enacted_pct_lte,
2886
+ ),
2887
+ (
2888
+ "apportioned_to_enacted_pct_capped",
2889
+ apportioned_to_enacted_pct_capped,
2890
+ apportioned_to_enacted_pct_capped_gte,
2891
+ apportioned_to_enacted_pct_capped_lte,
2892
+ ),
2893
+ (
2894
+ "obligated_to_enacted_pct",
2895
+ obligated_to_enacted_pct,
2896
+ obligated_to_enacted_pct_gte,
2897
+ obligated_to_enacted_pct_lte,
2898
+ ),
2899
+ (
2900
+ "obligated_to_enacted_pct_capped",
2901
+ obligated_to_enacted_pct_capped,
2902
+ obligated_to_enacted_pct_capped_gte,
2903
+ obligated_to_enacted_pct_capped_lte,
2904
+ ),
2905
+ (
2906
+ "outlayed_to_obligated_pct",
2907
+ outlayed_to_obligated_pct,
2908
+ outlayed_to_obligated_pct_gte,
2909
+ outlayed_to_obligated_pct_lte,
2910
+ ),
2911
+ (
2912
+ "outlayed_to_obligated_pct_capped",
2913
+ outlayed_to_obligated_pct_capped,
2914
+ outlayed_to_obligated_pct_capped_gte,
2915
+ outlayed_to_obligated_pct_capped_lte,
2916
+ ),
2917
+ ("unobligated_pct", unobligated_pct, unobligated_pct_gte, unobligated_pct_lte),
2918
+ (
2919
+ "enacted_ba_yoy_pct",
2920
+ enacted_ba_yoy_pct,
2921
+ enacted_ba_yoy_pct_gte,
2922
+ enacted_ba_yoy_pct_lte,
2923
+ ),
2924
+ (
2925
+ "obligated_yoy_pct",
2926
+ obligated_yoy_pct,
2927
+ obligated_yoy_pct_gte,
2928
+ obligated_yoy_pct_lte,
2929
+ ),
2930
+ (
2931
+ "enacted_ba_5yr_cagr",
2932
+ enacted_ba_5yr_cagr,
2933
+ enacted_ba_5yr_cagr_gte,
2934
+ enacted_ba_5yr_cagr_lte,
2935
+ ),
2936
+ (
2937
+ "ba_growth_next_year_pct",
2938
+ ba_growth_next_year_pct,
2939
+ ba_growth_next_year_pct_gte,
2940
+ ba_growth_next_year_pct_lte,
2941
+ ),
2942
+ (
2943
+ "actual_vs_requested_contract",
2944
+ actual_vs_requested_contract,
2945
+ actual_vs_requested_contract_gte,
2946
+ actual_vs_requested_contract_lte,
2947
+ ),
2948
+ (
2949
+ "actual_vs_requested_contract_capped",
2950
+ actual_vs_requested_contract_capped,
2951
+ actual_vs_requested_contract_capped_gte,
2952
+ actual_vs_requested_contract_capped_lte,
2953
+ ),
2954
+ )
2955
+ for field, exact, gte, lte in range_filters:
2956
+ if exact is not None:
2957
+ params[field] = exact
2958
+ if gte is not None:
2959
+ params[f"{field}__gte"] = gte
2960
+ if lte is not None:
2961
+ params[f"{field}__lte"] = lte
2705
2962
  data = self._get("/api/budget/accounts/", params)
2706
2963
  results = [
2707
2964
  self._parse_response_with_shape(obj, shape, BudgetAccount, flat, flat_lists)
@@ -505,6 +505,74 @@ class TestTangoClient:
505
505
 
506
506
  assert exc_info.value.status_code == 404
507
507
 
508
+ @patch("tango.client.httpx.Client.request")
509
+ def test_list_budget_accounts_range_filters(self, mock_request):
510
+ """Range filters serialize to the API's ``field__gte`` / ``field__lte`` form."""
511
+ mock_response = Mock()
512
+ mock_response.is_success = True
513
+ mock_response.json.return_value = {
514
+ "count": 0,
515
+ "next": None,
516
+ "previous": None,
517
+ "results": [],
518
+ }
519
+ mock_response.content = b'{"count": 0, "results": []}'
520
+ mock_request.return_value = mock_response
521
+
522
+ client = TangoClient(api_key="test-key")
523
+ client.list_budget_accounts(
524
+ fiscal_year=2024,
525
+ contract_share_of_obligated_capped_gte=0.6,
526
+ ba_growth_next_year_pct_gte=0.15,
527
+ unobligated_balance_gte=200_000_000,
528
+ unobligated_balance_lte=10_000_000_000,
529
+ enacted_ba=3_135_000_000,
530
+ ordering="-unobligated_balance",
531
+ )
532
+
533
+ params = mock_request.call_args[1]["params"]
534
+ # Exact-match scalar filter still works.
535
+ assert params["fiscal_year"] == 2024
536
+ # Range filters use the API's double-underscore form.
537
+ assert params["contract_share_of_obligated_capped__gte"] == 0.6
538
+ assert params["ba_growth_next_year_pct__gte"] == 0.15
539
+ assert params["unobligated_balance__gte"] == 200_000_000
540
+ assert params["unobligated_balance__lte"] == 10_000_000_000
541
+ # Range fields also accept exact-match.
542
+ assert params["enacted_ba"] == 3_135_000_000
543
+ # Ordering passes through untouched (callers prefix with '-' for desc).
544
+ assert params["ordering"] == "-unobligated_balance"
545
+ # Unspecified filters are not sent.
546
+ assert "apportioned__gte" not in params
547
+ assert "obligated_yoy_pct__lte" not in params
548
+
549
+ @patch("tango.client.httpx.Client.request")
550
+ def test_list_budget_accounts_no_filters_sends_only_pagination_and_shape(self, mock_request):
551
+ """With no filter args, the request carries only paging + default shape."""
552
+ mock_response = Mock()
553
+ mock_response.is_success = True
554
+ mock_response.json.return_value = {
555
+ "count": 0,
556
+ "next": None,
557
+ "previous": None,
558
+ "results": [],
559
+ }
560
+ mock_response.content = b'{"count": 0, "results": []}'
561
+ mock_request.return_value = mock_response
562
+
563
+ client = TangoClient(api_key="test-key")
564
+ client.list_budget_accounts()
565
+
566
+ params = mock_request.call_args[1]["params"]
567
+ assert params["page"] == 1
568
+ assert params["limit"] == 25
569
+ assert "shape" in params
570
+ # None of the new range filters leak through as null.
571
+ leaked = [
572
+ k for k in params if k.endswith("__gte") or k.endswith("__lte") or k == "fiscal_year"
573
+ ]
574
+ assert leaked == [], f"unexpected filter keys sent: {leaked}"
575
+
508
576
 
509
577
  class TestShapeConfig:
510
578
  """Test ShapeConfig class"""
File without changes
File without changes
File without changes