tango-python 1.1.1__tar.gz → 1.1.3__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.1 → tango_python-1.1.3}/.github/workflows/docs-dispatch.yml +1 -1
  2. {tango_python-1.1.1 → tango_python-1.1.3}/.github/workflows/lint.yml +8 -10
  3. {tango_python-1.1.1 → tango_python-1.1.3}/.github/workflows/publish.yml +2 -2
  4. {tango_python-1.1.1 → tango_python-1.1.3}/.github/workflows/test.yml +4 -4
  5. {tango_python-1.1.1 → tango_python-1.1.3}/CHANGELOG.md +55 -0
  6. {tango_python-1.1.1 → tango_python-1.1.3}/PKG-INFO +1 -1
  7. {tango_python-1.1.1 → tango_python-1.1.3}/docs/API_REFERENCE.md +134 -0
  8. {tango_python-1.1.1 → tango_python-1.1.3}/pyproject.toml +1 -1
  9. {tango_python-1.1.1 → tango_python-1.1.3}/tango/__init__.py +1 -1
  10. {tango_python-1.1.1 → tango_python-1.1.3}/tango/client.py +180 -20
  11. {tango_python-1.1.1 → tango_python-1.1.3}/tango/shapes/factory.py +12 -76
  12. {tango_python-1.1.1 → tango_python-1.1.3}/tango/shapes/generator.py +12 -27
  13. {tango_python-1.1.1 → tango_python-1.1.3}/tango/shapes/parser.py +17 -16
  14. {tango_python-1.1.1 → tango_python-1.1.3}/tango/shapes/schema.py +3 -2
  15. tango_python-1.1.3/tests/cassettes/TestEntitiesIntegration.test_get_entity_budget_flows +229 -0
  16. {tango_python-1.1.1 → tango_python-1.1.3}/tests/integration/test_entities_integration.py +58 -0
  17. {tango_python-1.1.1 → tango_python-1.1.3}/tests/test_client.py +59 -0
  18. {tango_python-1.1.1 → tango_python-1.1.3}/.gitignore +0 -0
  19. {tango_python-1.1.1 → tango_python-1.1.3}/LICENSE +0 -0
  20. {tango_python-1.1.1 → tango_python-1.1.3}/README.md +0 -0
  21. {tango_python-1.1.1 → tango_python-1.1.3}/docs/CLIENT.md +0 -0
  22. {tango_python-1.1.1 → tango_python-1.1.3}/docs/DYNAMIC_MODELS.md +0 -0
  23. {tango_python-1.1.1 → tango_python-1.1.3}/docs/ERRORS.md +0 -0
  24. {tango_python-1.1.1 → tango_python-1.1.3}/docs/PAGINATION.md +0 -0
  25. {tango_python-1.1.1 → tango_python-1.1.3}/docs/SHAPES.md +0 -0
  26. {tango_python-1.1.1 → tango_python-1.1.3}/docs/WEBHOOKS.md +0 -0
  27. {tango_python-1.1.1 → tango_python-1.1.3}/scripts/README.md +0 -0
  28. {tango_python-1.1.1 → tango_python-1.1.3}/scripts/check_filter_shape_conformance.py +0 -0
  29. {tango_python-1.1.1 → tango_python-1.1.3}/scripts/fetch_api_schema.py +0 -0
  30. {tango_python-1.1.1 → tango_python-1.1.3}/scripts/generate_schemas_from_api.py +0 -0
  31. {tango_python-1.1.1 → tango_python-1.1.3}/scripts/pr_review.py +0 -0
  32. {tango_python-1.1.1 → tango_python-1.1.3}/scripts/smoke_api_parity.py +0 -0
  33. {tango_python-1.1.1 → tango_python-1.1.3}/scripts/test_production.py +0 -0
  34. {tango_python-1.1.1 → tango_python-1.1.3}/tango/exceptions.py +0 -0
  35. {tango_python-1.1.1 → tango_python-1.1.3}/tango/models.py +0 -0
  36. {tango_python-1.1.1 → tango_python-1.1.3}/tango/shapes/__init__.py +0 -0
  37. {tango_python-1.1.1 → tango_python-1.1.3}/tango/shapes/explicit_schemas.py +0 -0
  38. {tango_python-1.1.1 → tango_python-1.1.3}/tango/shapes/models.py +0 -0
  39. {tango_python-1.1.1 → tango_python-1.1.3}/tango/shapes/types.py +0 -0
  40. {tango_python-1.1.1 → tango_python-1.1.3}/tango/webhooks/__init__.py +0 -0
  41. {tango_python-1.1.1 → tango_python-1.1.3}/tango/webhooks/cli.py +0 -0
  42. {tango_python-1.1.1 → tango_python-1.1.3}/tango/webhooks/receiver.py +0 -0
  43. {tango_python-1.1.1 → tango_python-1.1.3}/tango/webhooks/signing.py +0 -0
  44. {tango_python-1.1.1 → tango_python-1.1.3}/tango/webhooks/simulate.py +0 -0
  45. {tango_python-1.1.1 → tango_python-1.1.3}/tests/__init__.py +0 -0
  46. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestAgenciesIntegration.test_get_agency +0 -0
  47. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestAgenciesIntegration.test_list_agencies +0 -0
  48. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_field_type_validation +0 -0
  49. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_parsing_consistency +0 -0
  50. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestBusinessTypesIntegration.test_list_business_types +0 -0
  51. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestContractsIntegration.test_combined_filters_work_together +0 -0
  52. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestContractsIntegration.test_contract_cursor_pagination +0 -0
  53. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestContractsIntegration.test_contract_data_object_parsing +0 -0
  54. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestContractsIntegration.test_contract_field_types +0 -0
  55. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[keyword-software] +0 -0
  56. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[psc_code-R425] +0 -0
  57. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_awarding_agency_filter +0 -0
  58. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_date_range_filter +0 -0
  59. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_flat +0 -0
  60. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_naics_code_filter +0 -0
  61. {tango_python-1.1.1 → tango_python-1.1.3}/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.1.1 → tango_python-1.1.3}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[default-None] +0 -0
  63. {tango_python-1.1.1 → tango_python-1.1.3}/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.1.1 → tango_python-1.1.3}/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.1.1 → tango_python-1.1.3}/tests/cassettes/TestContractsIntegration.test_new_expiring_filters +0 -0
  66. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestContractsIntegration.test_new_fiscal_year_range_filters +0 -0
  67. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestContractsIntegration.test_new_identifier_filters +0 -0
  68. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestContractsIntegration.test_search_contracts_with_filters +0 -0
  69. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestContractsIntegration.test_search_filters_object_with_new_parameters +0 -0
  70. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[asc-] +0 -0
  71. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[desc--] +0 -0
  72. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_contracts +0 -0
  73. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_entities +0 -0
  74. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestEdgeCasesIntegration.test_date_field_parsing_edge_cases +0 -0
  75. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestEdgeCasesIntegration.test_decimal_field_parsing_edge_cases +0 -0
  76. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestEdgeCasesIntegration.test_empty_list_responses +0 -0
  77. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestEdgeCasesIntegration.test_entity_parsing_with_various_address_formats +0 -0
  78. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestEdgeCasesIntegration.test_flattened_responses_with_flat_lists +0 -0
  79. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestEdgeCasesIntegration.test_list_field_parsing_consistency +0 -0
  80. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_nested_objects_with_missing_data +0 -0
  81. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_null_missing_fields_in_contracts +0 -0
  82. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_with_minimal_shape_sparse_data +0 -0
  83. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestEntitiesIntegration.test_entity_field_types +0 -0
  84. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestEntitiesIntegration.test_entity_location_parsing +0 -0
  85. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestEntitiesIntegration.test_entity_parsing_with_business_types +0 -0
  86. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestEntitiesIntegration.test_entity_with_various_identifiers +0 -0
  87. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestEntitiesIntegration.test_get_entity_by_uei +0 -0
  88. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_flat +0 -0
  89. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_search +0 -0
  90. {tango_python-1.1.1 → tango_python-1.1.3}/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
  91. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[custom-uei,legal_business_name,cage_code] +0 -0
  92. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
  93. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[with_address-uei,legal_business_name,cage_code,business_types,physical_address] +0 -0
  94. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestForecastsIntegration.test_forecast_field_types +0 -0
  95. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[custom-id,title,anticipated_award_date] +0 -0
  96. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[default-None] +0 -0
  97. {tango_python-1.1.1 → tango_python-1.1.3}/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
  98. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[minimal-id,title,anticipated_award_date,fiscal_year,naics_code,status] +0 -0
  99. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestGrantsIntegration.test_grant_field_types +0 -0
  100. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestGrantsIntegration.test_grant_pagination +0 -0
  101. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[custom-grant_id,title,opportunity_number] +0 -0
  102. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[default-None] +0 -0
  103. {tango_python-1.1.1 → tango_python-1.1.3}/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
  104. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[minimal-grant_id,opportunity_number,title,status(-),agency_code] +0 -0
  105. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestIDVsIntegration.test_get_idv_uses_default_shape +0 -0
  106. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestIDVsIntegration.test_list_idv_awards_uses_default_shape +0 -0
  107. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestIDVsIntegration.test_list_idv_child_idvs_uses_default_shape +0 -0
  108. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestIDVsIntegration.test_list_idv_transactions +0 -0
  109. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestIDVsIntegration.test_list_idvs_uses_default_shape_and_keyset_params +0 -0
  110. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_code +0 -0
  111. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_name_text +0 -0
  112. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating +0 -0
  113. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating_max +0 -0
  114. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestITDashboardIntegration.test_filter_by_performance_risk +0 -0
  115. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestITDashboardIntegration.test_filter_by_type_of_investment +0 -0
  116. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestITDashboardIntegration.test_filter_by_updated_time_range +0 -0
  117. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestITDashboardIntegration.test_funding_and_cio_evaluation_expansions +0 -0
  118. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestITDashboardIntegration.test_get_itdashboard_investment_by_uii +0 -0
  119. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestITDashboardIntegration.test_itdashboard_pagination +0 -0
  120. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_search +0 -0
  121. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[custom-uii,agency_name,investment_title,updated_time] +0 -0
  122. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[default-None] +0 -0
  123. {tango_python-1.1.1 → tango_python-1.1.3}/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
  124. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestNaicsIntegration.test_list_naics +0 -0
  125. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[custom-notice_id,title,solicitation_number] +0 -0
  126. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[default-None] +0 -0
  127. {tango_python-1.1.1 → tango_python-1.1.3}/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
  128. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
  129. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestNoticesIntegration.test_notice_field_types +0 -0
  130. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestNoticesIntegration.test_notice_pagination +0 -0
  131. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestNoticesIntegration.test_notice_with_meta_fields +0 -0
  132. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestOTAsIntegration.test_get_ota +0 -0
  133. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestOTAsIntegration.test_list_otas +0 -0
  134. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestOTIDVsIntegration.test_get_otidv +0 -0
  135. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestOTIDVsIntegration.test_list_otidvs +0 -0
  136. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestOfficesIntegration.test_get_office +0 -0
  137. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestOfficesIntegration.test_list_offices +0 -0
  138. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[custom-opportunity_id,title,solicitation_number] +0 -0
  139. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[default-None] +0 -0
  140. {tango_python-1.1.1 → tango_python-1.1.3}/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
  141. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
  142. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestOpportunitiesIntegration.test_opportunity_field_types +0 -0
  143. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestOrganizationsIntegration.test_get_organization +0 -0
  144. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestOrganizationsIntegration.test_list_organizations +0 -0
  145. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestProtestsIntegration.test_get_protest_by_case_id +0 -0
  146. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_filter +0 -0
  147. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[custom-case_id,title,source_system,outcome] +0 -0
  148. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[default-None] +0 -0
  149. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[minimal-case_id,case_number,title,source_system,outcome,filed_date] +0 -0
  150. {tango_python-1.1.1 → tango_python-1.1.3}/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
  151. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestProtestsIntegration.test_protest_pagination +0 -0
  152. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestSubawardsIntegration.test_list_subawards +0 -0
  153. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[custom-key,piid,description] +0 -0
  154. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[minimal-key,piid,award_date,recipient(display_name),description,total_contract_value] +0 -0
  155. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[ultra_minimal-key,piid,recipient(display_name),total_contract_value] +0 -0
  156. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
  157. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[with_address-uei,legal_business_name,cage_code,business_types,physical_address] +0 -0
  158. {tango_python-1.1.1 → tango_python-1.1.3}/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
  159. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestTypeHintsIntegration.test_notices_dict_access[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
  160. {tango_python-1.1.1 → tango_python-1.1.3}/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
  161. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestTypeHintsIntegration.test_opportunities_dict_access[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
  162. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists +0 -0
  163. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_with_metrics_expansion +0 -0
  164. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape +0 -0
  165. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_orders_uses_default_shape +0 -0
  166. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search +0 -0
  167. {tango_python-1.1.1 → tango_python-1.1.3}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_with_ordering +0 -0
  168. {tango_python-1.1.1 → tango_python-1.1.3}/tests/conftest.py +0 -0
  169. {tango_python-1.1.1 → tango_python-1.1.3}/tests/integration/README.md +0 -0
  170. {tango_python-1.1.1 → tango_python-1.1.3}/tests/integration/__init__.py +0 -0
  171. {tango_python-1.1.1 → tango_python-1.1.3}/tests/integration/conftest.py +0 -0
  172. {tango_python-1.1.1 → tango_python-1.1.3}/tests/integration/test_agencies_integration.py +0 -0
  173. {tango_python-1.1.1 → tango_python-1.1.3}/tests/integration/test_contracts_integration.py +0 -0
  174. {tango_python-1.1.1 → tango_python-1.1.3}/tests/integration/test_edge_cases_integration.py +0 -0
  175. {tango_python-1.1.1 → tango_python-1.1.3}/tests/integration/test_forecasts_integration.py +0 -0
  176. {tango_python-1.1.1 → tango_python-1.1.3}/tests/integration/test_grants_integration.py +0 -0
  177. {tango_python-1.1.1 → tango_python-1.1.3}/tests/integration/test_itdashboard_integration.py +0 -0
  178. {tango_python-1.1.1 → tango_python-1.1.3}/tests/integration/test_naics_integration.py +0 -0
  179. {tango_python-1.1.1 → tango_python-1.1.3}/tests/integration/test_notices_integration.py +0 -0
  180. {tango_python-1.1.1 → tango_python-1.1.3}/tests/integration/test_offices_integration.py +0 -0
  181. {tango_python-1.1.1 → tango_python-1.1.3}/tests/integration/test_opportunities_integration.py +0 -0
  182. {tango_python-1.1.1 → tango_python-1.1.3}/tests/integration/test_organizations_integration.py +0 -0
  183. {tango_python-1.1.1 → tango_python-1.1.3}/tests/integration/test_otas_otidvs_integration.py +0 -0
  184. {tango_python-1.1.1 → tango_python-1.1.3}/tests/integration/test_protests_integration.py +0 -0
  185. {tango_python-1.1.1 → tango_python-1.1.3}/tests/integration/test_reference_data_integration.py +0 -0
  186. {tango_python-1.1.1 → tango_python-1.1.3}/tests/integration/test_subawards_integration.py +0 -0
  187. {tango_python-1.1.1 → tango_python-1.1.3}/tests/integration/test_vehicles_idvs_integration.py +0 -0
  188. {tango_python-1.1.1 → tango_python-1.1.3}/tests/integration/validation.py +0 -0
  189. {tango_python-1.1.1 → tango_python-1.1.3}/tests/production/__init__.py +0 -0
  190. {tango_python-1.1.1 → tango_python-1.1.3}/tests/production/conftest.py +0 -0
  191. {tango_python-1.1.1 → tango_python-1.1.3}/tests/production/test_production_smoke.py +0 -0
  192. {tango_python-1.1.1 → tango_python-1.1.3}/tests/test_api_parity.py +0 -0
  193. {tango_python-1.1.1 → tango_python-1.1.3}/tests/test_models.py +0 -0
  194. {tango_python-1.1.1 → tango_python-1.1.3}/tests/test_shapes.py +0 -0
  195. {tango_python-1.1.1 → tango_python-1.1.3}/tests/test_webhooks_cli.py +0 -0
  196. {tango_python-1.1.1 → tango_python-1.1.3}/tests/test_webhooks_receiver.py +0 -0
  197. {tango_python-1.1.1 → tango_python-1.1.3}/tests/test_webhooks_signing.py +0 -0
  198. {tango_python-1.1.1 → tango_python-1.1.3}/tests/test_webhooks_simulate.py +0 -0
@@ -30,7 +30,7 @@ jobs:
30
30
  permissions:
31
31
  contents: read
32
32
  steps:
33
- - uses: actions/checkout@v4
33
+ - uses: actions/checkout@v6
34
34
  with:
35
35
  fetch-depth: 2
36
36
 
@@ -3,9 +3,8 @@ name: Linting
3
3
  # Lint gate runs on every PR and push to main.
4
4
  #
5
5
  # - ruff format + ruff check are HARD gates (block the PR).
6
- # - mypy is ADVISORY for now (continue-on-error): the package carries ~28
7
- # pre-existing type errors that predate CI enforcement. Tracked for burn-down
8
- # in makegov/tango-python; flip `continue-on-error` off once that's clear.
6
+ # - mypy is a HARD gate: the package type-checks cleanly under strict mypy.
7
+ # (The earlier ~28-error burn-down is complete.)
9
8
  # - The SDK filter/shape conformance check needs the canonical manifest from the
10
9
  # private makegov/tango repo, which requires a TANGO_API_REPO_ACCESS_TOKEN
11
10
  # secret the public CI does not have. The conformance job SKIPS cleanly when
@@ -23,10 +22,10 @@ jobs:
23
22
  runs-on: ubuntu-latest
24
23
 
25
24
  steps:
26
- - uses: actions/checkout@v4
25
+ - uses: actions/checkout@v6
27
26
 
28
27
  - name: Install uv
29
- uses: astral-sh/setup-uv@v4
28
+ uses: astral-sh/setup-uv@v8.1.0
30
29
  with:
31
30
  version: "latest"
32
31
 
@@ -42,8 +41,7 @@ jobs:
42
41
  - name: Lint with ruff
43
42
  run: uv run ruff check tango/
44
43
 
45
- - name: Type check with mypy (advisory)
46
- continue-on-error: true
44
+ - name: Type check with mypy
47
45
  run: uv run mypy tango/
48
46
 
49
47
  conformance:
@@ -66,12 +64,12 @@ jobs:
66
64
  echo "::notice::Skipping SDK conformance check — TANGO_API_REPO_ACCESS_TOKEN not configured."
67
65
  fi
68
66
 
69
- - uses: actions/checkout@v4
67
+ - uses: actions/checkout@v6
70
68
  if: steps.gate.outputs.ready == 'true'
71
69
 
72
70
  - name: Checkout tango API repo (manifest source)
73
71
  if: steps.gate.outputs.ready == 'true'
74
- uses: actions/checkout@v4
72
+ uses: actions/checkout@v6
75
73
  with:
76
74
  repository: makegov/tango
77
75
  path: tango-api
@@ -79,7 +77,7 @@ jobs:
79
77
 
80
78
  - name: Install uv
81
79
  if: steps.gate.outputs.ready == 'true'
82
- uses: astral-sh/setup-uv@v4
80
+ uses: astral-sh/setup-uv@v8.1.0
83
81
  with:
84
82
  version: "latest"
85
83
 
@@ -10,10 +10,10 @@ jobs:
10
10
  runs-on: ubuntu-latest
11
11
 
12
12
  steps:
13
- - uses: actions/checkout@v4
13
+ - uses: actions/checkout@v6
14
14
 
15
15
  - name: Install uv
16
- uses: astral-sh/setup-uv@v4
16
+ uses: astral-sh/setup-uv@v8.1.0
17
17
  with:
18
18
  version: 'latest'
19
19
 
@@ -20,10 +20,10 @@ jobs:
20
20
  if: matrix.os == 'windows-latest'
21
21
  run: git config --global core.longpaths true
22
22
 
23
- - uses: actions/checkout@v4
23
+ - uses: actions/checkout@v6
24
24
 
25
25
  - name: Install uv
26
- uses: astral-sh/setup-uv@v4
26
+ uses: astral-sh/setup-uv@v8.1.0
27
27
  with:
28
28
  version: "latest"
29
29
 
@@ -37,8 +37,8 @@ jobs:
37
37
  run: uv run pytest
38
38
 
39
39
  - name: Upload coverage to Codecov
40
- uses: codecov/codecov-action@v3
40
+ uses: codecov/codecov-action@v5
41
41
  with:
42
- file: ./coverage.xml
42
+ files: ./coverage.xml
43
43
  flags: unittests
44
44
  name: codecov-${{ matrix.os }}-py${{ matrix.python-version }}
@@ -7,6 +7,61 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.1.3] - 2026-06-04
11
+
12
+ ### Added
13
+ - Reference-data list/get methods now accept `shape` (and the associated
14
+ `flat` / `flat_lists`) parameters, matching the underlying API which has
15
+ always supported the shape system via `ShapeOnDemandMixin`. Affected:
16
+ `list_naics` / `get_naics`, `list_psc` / `get_psc`,
17
+ `list_assistance_listings` / `get_assistance_listing`,
18
+ `list_business_types` / `get_business_type`,
19
+ `list_mas_sins` / `get_mas_sin`. When `shape` is omitted, behavior is
20
+ unchanged — the API applies its own per-resource default.
21
+ `list_business_types` returns raw dicts (instead of `BusinessType`
22
+ instances) when a `shape` is supplied so the caller gets exactly the
23
+ shape requested.
24
+
25
+ ## [1.1.2] - 2026-06-04
26
+
27
+ ### Changed
28
+ - `get_entity_budget_flows()` now exposes the backend's standard page/limit
29
+ pagination and `fiscal_year` filter, and returns
30
+ `PaginatedResponse[dict[str, Any]]` instead of a raw dict. The backend has
31
+ always paginated this endpoint via `StandardResultsSetPagination`; the
32
+ previous signature gave callers no way to reach pages beyond the first or
33
+ to narrow by fiscal year. Callers that were indexing `result["results"]`
34
+ on the old return value should switch to `result.results` (and can now use
35
+ `result.next` / `page=` to walk further pages).
36
+ - Completed the strict-`mypy` burn-down across `tango/shapes/` (parser,
37
+ generator, factory, schema). All changes are type-annotation/typing
38
+ corrections with no runtime behavior change, except:
39
+ - `FieldSchema.nested_model` is now typed `type | str | None` (it always
40
+ accepted string model names from the explicit schemas; the annotation was
41
+ wrong). `ModelFactory.validate_data` and `ShapeParser._validate_field_spec`
42
+ likewise accept `type | str` for the model argument.
43
+ - Removed two dead `elif field_spec.is_wildcard:` branches (in
44
+ `TypeGenerator.generate_type` and `ModelFactory.create_instance`) and the
45
+ now-orphaned `_parse_nested_wildcard` helper. These were unreachable —
46
+ wildcard field specs are fully handled by the top-of-loop branch that
47
+ `continue`s before reaching them — so removal is behavior-preserving.
48
+
49
+ ### Docs
50
+ - `docs/API_REFERENCE.md` now documents the Budget surface that shipped in
51
+ v1.1.0: a new `## Budget` section covering `list_budget_accounts`,
52
+ `get_budget_account`, `get_budget_account_quarters`, and
53
+ `get_budget_account_recipients`; a `get_entity_budget_flows()` entry under
54
+ Entity Sub-resources; a `BUDGET_ACCOUNTS_MINIMAL` row in the ShapeConfig
55
+ table; and a Budget entry in the table of contents.
56
+
57
+ ### CI
58
+ - Bumped GitHub Actions off the deprecated Node 20 runtime (forced off
59
+ 2026-06-02): `actions/checkout` v4→v6, `astral-sh/setup-uv` v4→v8.1.0
60
+ (pinned exact — no floating `v8` major tag is published yet), and
61
+ `codecov/codecov-action` v3→v5 (with the renamed `files:` input).
62
+ - `mypy` is now a **hard gate** in `lint.yml` (no longer advisory). The
63
+ `tango/` package type-checks cleanly under strict mypy.
64
+
10
65
  ## [1.1.1] - 2026-05-29
11
66
 
12
67
  ### Removed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tango-python
3
- Version: 1.1.1
3
+ Version: 1.1.3
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
@@ -21,6 +21,7 @@ Complete reference for all Tango Python SDK methods and functionality.
21
21
  - [Grants](#grants)
22
22
  - [GSA eLibrary Contracts](#gsa-elibrary-contracts)
23
23
  - [Protests](#protests)
24
+ - [Budget](#budget)
24
25
  - [Business Types](#business-types)
25
26
  - [NAICS](#naics)
26
27
  - [Webhooks](#webhooks)
@@ -1212,6 +1213,118 @@ protest = client.get_protest(
1212
1213
 
1213
1214
  ---
1214
1215
 
1216
+ ## Budget
1217
+
1218
+ Federal account × fiscal year budget rollups, covering the full budget lifecycle (requested → enacted → apportioned → obligated → outlayed), pre-computed ratios and trends, the contract / assistance / unlinked breakdown, and request-vs-actual spend.
1219
+
1220
+ ### list_budget_accounts()
1221
+
1222
+ List budget accounts. One row per `(federal_account_symbol, fiscal_year)`.
1223
+
1224
+ ```python
1225
+ accounts = client.list_budget_accounts(
1226
+ page=1,
1227
+ limit=25,
1228
+ shape=ShapeConfig.BUDGET_ACCOUNTS_MINIMAL,
1229
+ # Filter parameters (all optional)
1230
+ federal_account_symbol=None,
1231
+ fiscal_year=None,
1232
+ fiscal_year_gte=None,
1233
+ fiscal_year_lte=None,
1234
+ agency_code=None,
1235
+ bureau_name=None,
1236
+ account_title=None,
1237
+ bea_category=None,
1238
+ on_off_budget=None,
1239
+ subfunction_code=None,
1240
+ search=None,
1241
+ ordering=None,
1242
+ )
1243
+ ```
1244
+
1245
+ **Filter Parameters:**
1246
+ - `federal_account_symbol` - Exact federal account symbol (e.g., `"097-0100"`)
1247
+ - `fiscal_year` - Fiscal year (exact)
1248
+ - `fiscal_year_gte` / `fiscal_year_lte` - Fiscal year range
1249
+ - `agency_code` - Agency code (exact)
1250
+ - `bureau_name` - Bureau name (exact)
1251
+ - `account_title` - Account title (case-insensitive substring match)
1252
+ - `bea_category` - BEA category (exact)
1253
+ - `on_off_budget` - On/off budget flag (exact)
1254
+ - `subfunction_code` - Subfunction code (exact)
1255
+ - `search` - Full-text search over `account_title`, `agency_name`, `bureau_name`
1256
+ - `ordering` - Sort field; prefix with `-` for descending
1257
+
1258
+ **Returns:** [PaginatedResponse](#paginatedresponse) of `BudgetAccount` records (see [ShapeConfig](#shapeconfig-predefined-shapes) for the default shape).
1259
+
1260
+ **Example:**
1261
+ ```python
1262
+ accounts = client.list_budget_accounts(
1263
+ agency_code="097",
1264
+ fiscal_year_gte=2023,
1265
+ ordering="-enacted_ba",
1266
+ limit=10,
1267
+ )
1268
+
1269
+ for acct in accounts.results:
1270
+ print(f"{acct.federal_account_symbol} FY{acct.fiscal_year}: "
1271
+ f"enacted ${acct.enacted_ba:,}")
1272
+ ```
1273
+
1274
+ ### get_budget_account()
1275
+
1276
+ Get a single budget account by id.
1277
+
1278
+ ```python
1279
+ account = client.get_budget_account(
1280
+ 12345,
1281
+ shape=ShapeConfig.BUDGET_ACCOUNTS_MINIMAL,
1282
+ )
1283
+ ```
1284
+
1285
+ **Parameters:**
1286
+ - `id` (str | int): Budget account id.
1287
+ - `shape` (str, optional): Response shape. Defaults to `BUDGET_ACCOUNTS_MINIMAL`.
1288
+ - `flat` / `flat_lists` / `joiner`: See [Shaping Guide](SHAPES.md).
1289
+
1290
+ **Returns:** A `BudgetAccount` record.
1291
+
1292
+ ### get_budget_account_quarters()
1293
+
1294
+ Get quarterly TAS-grain flow for a budget account. FY21+ only.
1295
+
1296
+ ```python
1297
+ quarters = client.get_budget_account_quarters(12345, limit=25)
1298
+ ```
1299
+
1300
+ **Parameters:**
1301
+ - `id` (str | int): Budget account id.
1302
+ - `tas` (str, optional): Narrow to a single Treasury Account Symbol.
1303
+ - `limit` (int): Results per page (max 100).
1304
+
1305
+ **Returns:** [PaginatedResponse](#paginatedresponse) of quarterly flow records.
1306
+
1307
+ ### get_budget_account_recipients()
1308
+
1309
+ Get funding-office × recipient contract-flow detail for a budget account.
1310
+
1311
+ ```python
1312
+ recipients = client.get_budget_account_recipients(
1313
+ 12345,
1314
+ funding_organization_id=None,
1315
+ limit=25,
1316
+ )
1317
+ ```
1318
+
1319
+ **Parameters:**
1320
+ - `id` (str | int): Budget account id.
1321
+ - `funding_organization_id` (str, optional): Narrow to a single funding office (Organization UUID).
1322
+ - `limit` (int): Results per page (max 100).
1323
+
1324
+ **Returns:** [PaginatedResponse](#paginatedresponse) of `(funding_office, recipient)` flow records.
1325
+
1326
+ ---
1327
+
1215
1328
  ## Business Types
1216
1329
 
1217
1330
  Business type classifications.
@@ -1461,6 +1574,26 @@ lcats = client.list_entity_lcats("ABCDEF123456", limit=25)
1461
1574
  metrics = client.get_entity_metrics("ABCDEF123456", months=12, period_grouping="month")
1462
1575
  ```
1463
1576
 
1577
+ ### get_entity_budget_flows()
1578
+
1579
+ Get budget flows for an entity (`/api/entities/{uei}/budget-flows/`) — the federal accounts that funded contracts and assistance awarded to this entity.
1580
+
1581
+ ```python
1582
+ flows = client.get_entity_budget_flows("ABCDEF123456", fiscal_year=2024)
1583
+ for row in flows.results:
1584
+ print(row["federal_account_symbol"], row["contract_obligated"])
1585
+ # next page
1586
+ more = client.get_entity_budget_flows("ABCDEF123456", page=2, fiscal_year=2024)
1587
+ ```
1588
+
1589
+ **Parameters:**
1590
+ - `uei` (str): Entity UEI. Required.
1591
+ - `page` (int): Page number. Default 1.
1592
+ - `limit` (int): Results per page. Default 25, max 100.
1593
+ - `fiscal_year` (int | None): Optional fiscal year filter.
1594
+
1595
+ **Returns:** `PaginatedResponse[dict[str, Any]]` — standard `count / next / previous / results`. Result rows are raw dicts from the API (not shape-controlled).
1596
+
1464
1597
  ---
1465
1598
 
1466
1599
  ## IDV LCATs
@@ -1875,6 +2008,7 @@ entity = client.get_entity("UEI_KEY", shape=ShapeConfig.ENTITIES_COMPREHENSIVE)
1875
2008
  | `SUBAWARDS_MINIMAL` | `list_subawards` | award_key, prime_recipient(uei,display_name), subaward_recipient(uei,display_name) |
1876
2009
  | `GSA_ELIBRARY_CONTRACTS_MINIMAL` | `list_gsa_elibrary_contracts` | uuid, contract_number, schedule, recipient(display_name,uei), idv(key,award_date) |
1877
2010
  | `PROTESTS_MINIMAL` | `list_protests` | case_id, case_number, title, source_system, outcome, filed_date |
2011
+ | `BUDGET_ACCOUNTS_MINIMAL` | `list_budget_accounts`, `get_budget_account` | id, federal_account_symbol, fiscal_year, agency_code/name, bureau_name, account_title, bea_category, on_off_budget, subfunction_code, lifecycle (requested/enacted/apportioned/obligated/outlayed/unobligated), contract & assistance rollups, key ratios, next-year growth |
1878
2012
  | `VEHICLE_ORDERS_MINIMAL` | `list_vehicle_orders` | key, piid, award_date, recipient(display_name,uei), total_contract_value, obligated |
1879
2013
  | `ITDASHBOARD_INVESTMENTS_MINIMAL` | `list_itdashboard_investments` | Minimal IT Dashboard investment fields |
1880
2014
  | `ITDASHBOARD_INVESTMENTS_COMPREHENSIVE` | `get_itdashboard_investment` | Full investment fields: uii, agency_code, agency_name, bureau_code, bureau_name, investment_title, type_of_investment, part_of_it_portfolio, updated_time, url |
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "tango-python"
7
- version = "1.1.1"
7
+ version = "1.1.3"
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.1"
47
+ __version__ = "1.1.3"
48
48
  __all__ = [
49
49
  "TangoClient",
50
50
  "TangoAPIError",
@@ -1923,15 +1923,37 @@ class TangoClient:
1923
1923
  )
1924
1924
 
1925
1925
  # Business Types endpoints
1926
- def list_business_types(self, page: int = 1, limit: int = 25) -> PaginatedResponse:
1927
- """List business types"""
1928
- params = {"page": page, "limit": min(limit, 100)}
1926
+ def list_business_types(
1927
+ self,
1928
+ page: int = 1,
1929
+ limit: int = 25,
1930
+ shape: str | None = None,
1931
+ flat: bool = False,
1932
+ flat_lists: bool = False,
1933
+ ) -> PaginatedResponse:
1934
+ """List business types.
1935
+
1936
+ When ``shape`` is omitted the API applies its own default
1937
+ (``name,code``) and results are returned as :class:`BusinessType`
1938
+ instances. When ``shape`` is provided, raw dicts are returned so the
1939
+ caller can rely on the exact shape requested.
1940
+ """
1941
+ params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
1942
+ if shape:
1943
+ params["shape"] = shape
1944
+ if flat:
1945
+ params["flat"] = "true"
1946
+ if flat_lists:
1947
+ params["flat_lists"] = "true"
1929
1948
  data = self._get("/api/business_types/", params)
1949
+ results: list[Any] = (
1950
+ list(data["results"]) if shape else [BusinessType(**btype) for btype in data["results"]]
1951
+ )
1930
1952
  return PaginatedResponse(
1931
1953
  count=data["count"],
1932
1954
  next=data.get("next"),
1933
1955
  previous=data.get("previous"),
1934
- results=[BusinessType(**btype) for btype in data["results"]],
1956
+ results=results,
1935
1957
  )
1936
1958
 
1937
1959
  def list_naics(
@@ -1945,8 +1967,17 @@ class TangoClient:
1945
1967
  revenue_limit_gte: int | None = None,
1946
1968
  revenue_limit_lte: int | None = None,
1947
1969
  search: str | None = None,
1970
+ shape: str | None = None,
1971
+ flat: bool = False,
1972
+ flat_lists: bool = False,
1948
1973
  ) -> PaginatedResponse:
1949
- """List NAICS codes (`/api/naics/`)."""
1974
+ """List NAICS codes (`/api/naics/`).
1975
+
1976
+ When ``shape`` is omitted the API applies its own default. Passing any
1977
+ of the ``revenue_limit*`` / ``employee_limit*`` filters causes the API
1978
+ to widen the default shape to include ``size_standards`` and
1979
+ ``federal_obligations``.
1980
+ """
1950
1981
  params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
1951
1982
  if employee_limit is not None:
1952
1983
  params["employee_limit"] = employee_limit
@@ -1962,6 +1993,12 @@ class TangoClient:
1962
1993
  params["revenue_limit_lte"] = revenue_limit_lte
1963
1994
  if search is not None:
1964
1995
  params["search"] = search
1996
+ if shape:
1997
+ params["shape"] = shape
1998
+ if flat:
1999
+ params["flat"] = "true"
2000
+ if flat_lists:
2001
+ params["flat_lists"] = "true"
1965
2002
  data = self._get("/api/naics/", params)
1966
2003
  return PaginatedResponse(
1967
2004
  count=data.get("count", 0),
@@ -2081,11 +2118,36 @@ class TangoClient:
2081
2118
  data = self._get(f"/api/entities/{key}/", params)
2082
2119
  return self._parse_response_with_shape(data, shape, Entity, flat, flat_lists)
2083
2120
 
2084
- def get_entity_budget_flows(self, uei: str) -> dict[str, Any]:
2085
- """Get budget flows for an entity (`/api/entities/{uei}/budget-flows/`)."""
2121
+ def get_entity_budget_flows(
2122
+ self,
2123
+ uei: str,
2124
+ page: int = 1,
2125
+ limit: int = 25,
2126
+ fiscal_year: int | None = None,
2127
+ ) -> PaginatedResponse[dict[str, Any]]:
2128
+ """Get budget flows for an entity (`/api/entities/{uei}/budget-flows/`).
2129
+
2130
+ Standard page/limit pagination (default 25, max 100). Each result row
2131
+ is a hand-built dict from the backend (no shape system).
2132
+
2133
+ Args:
2134
+ uei: Entity UEI. Required.
2135
+ page: Page number.
2136
+ limit: Results per page (max 100).
2137
+ fiscal_year: Optional fiscal year filter.
2138
+ """
2086
2139
  if not uei:
2087
2140
  raise TangoValidationError("UEI is required")
2088
- return self._get(f"/api/entities/{uei}/budget-flows/")
2141
+ params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
2142
+ if fiscal_year is not None:
2143
+ params["fiscal_year"] = fiscal_year
2144
+ data = self._get(f"/api/entities/{uei}/budget-flows/", params)
2145
+ return PaginatedResponse(
2146
+ count=int(data.get("count", 0)),
2147
+ next=data.get("next"),
2148
+ previous=data.get("previous"),
2149
+ results=list(data.get("results") or []),
2150
+ )
2089
2151
 
2090
2152
  # Forecast endpoints
2091
2153
  def list_forecasts(
@@ -3319,9 +3381,22 @@ class TangoClient:
3319
3381
  raise TangoValidationError("Department code is required")
3320
3382
  return self._get(f"/api/departments/{code}/")
3321
3383
 
3322
- def list_psc(self, page: int = 1, limit: int = 25) -> PaginatedResponse[dict[str, Any]]:
3384
+ def list_psc(
3385
+ self,
3386
+ page: int = 1,
3387
+ limit: int = 25,
3388
+ shape: str | None = None,
3389
+ flat: bool = False,
3390
+ flat_lists: bool = False,
3391
+ ) -> PaginatedResponse[dict[str, Any]]:
3323
3392
  """List Product Service Codes (`/api/psc/`)."""
3324
3393
  params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
3394
+ if shape:
3395
+ params["shape"] = shape
3396
+ if flat:
3397
+ params["flat"] = "true"
3398
+ if flat_lists:
3399
+ params["flat_lists"] = "true"
3325
3400
  data = self._get("/api/psc/", params)
3326
3401
  return PaginatedResponse(
3327
3402
  count=int(data.get("count", 0)),
@@ -3330,11 +3405,24 @@ class TangoClient:
3330
3405
  results=list(data.get("results") or []),
3331
3406
  )
3332
3407
 
3333
- def get_psc(self, code: str) -> dict[str, Any]:
3408
+ def get_psc(
3409
+ self,
3410
+ code: str,
3411
+ shape: str | None = None,
3412
+ flat: bool = False,
3413
+ flat_lists: bool = False,
3414
+ ) -> dict[str, Any]:
3334
3415
  """Get a Product Service Code by code (`/api/psc/{code}/`)."""
3335
3416
  if not code:
3336
3417
  raise TangoValidationError("PSC code is required")
3337
- return self._get(f"/api/psc/{code}/")
3418
+ params: dict[str, Any] = {}
3419
+ if shape:
3420
+ params["shape"] = shape
3421
+ if flat:
3422
+ params["flat"] = "true"
3423
+ if flat_lists:
3424
+ params["flat_lists"] = "true"
3425
+ return self._get(f"/api/psc/{code}/", params)
3338
3426
 
3339
3427
  def get_psc_metrics(self, code: str, months: int, period_grouping: str) -> dict[str, Any]:
3340
3428
  """Get rolling PSC metrics (`/api/psc/{code}/metrics/{months}/{period_grouping}/`).
@@ -3349,11 +3437,24 @@ class TangoClient:
3349
3437
  raise TangoValidationError("PSC code is required")
3350
3438
  return self._get(f"/api/psc/{code}/metrics/{months}/{period_grouping}/")
3351
3439
 
3352
- def get_naics(self, code: str) -> dict[str, Any]:
3440
+ def get_naics(
3441
+ self,
3442
+ code: str,
3443
+ shape: str | None = None,
3444
+ flat: bool = False,
3445
+ flat_lists: bool = False,
3446
+ ) -> dict[str, Any]:
3353
3447
  """Get a NAICS code by code (`/api/naics/{code}/`)."""
3354
3448
  if not code:
3355
3449
  raise TangoValidationError("NAICS code is required")
3356
- return self._get(f"/api/naics/{code}/")
3450
+ params: dict[str, Any] = {}
3451
+ if shape:
3452
+ params["shape"] = shape
3453
+ if flat:
3454
+ params["flat"] = "true"
3455
+ if flat_lists:
3456
+ params["flat_lists"] = "true"
3457
+ return self._get(f"/api/naics/{code}/", params)
3357
3458
 
3358
3459
  def get_naics_metrics(self, code: str, months: int, period_grouping: str) -> dict[str, Any]:
3359
3460
  """Get rolling NAICS metrics (`/api/naics/{code}/metrics/{months}/{period_grouping}/`)."""
@@ -3361,17 +3462,41 @@ class TangoClient:
3361
3462
  raise TangoValidationError("NAICS code is required")
3362
3463
  return self._get(f"/api/naics/{code}/metrics/{months}/{period_grouping}/")
3363
3464
 
3364
- def get_business_type(self, code: str) -> dict[str, Any]:
3465
+ def get_business_type(
3466
+ self,
3467
+ code: str,
3468
+ shape: str | None = None,
3469
+ flat: bool = False,
3470
+ flat_lists: bool = False,
3471
+ ) -> dict[str, Any]:
3365
3472
  """Get a business type by code (`/api/business_types/{code}/`)."""
3366
3473
  if not code:
3367
3474
  raise TangoValidationError("Business type code is required")
3368
- return self._get(f"/api/business_types/{code}/")
3475
+ params: dict[str, Any] = {}
3476
+ if shape:
3477
+ params["shape"] = shape
3478
+ if flat:
3479
+ params["flat"] = "true"
3480
+ if flat_lists:
3481
+ params["flat_lists"] = "true"
3482
+ return self._get(f"/api/business_types/{code}/", params)
3369
3483
 
3370
3484
  def list_assistance_listings(
3371
- self, page: int = 1, limit: int = 25
3485
+ self,
3486
+ page: int = 1,
3487
+ limit: int = 25,
3488
+ shape: str | None = None,
3489
+ flat: bool = False,
3490
+ flat_lists: bool = False,
3372
3491
  ) -> PaginatedResponse[dict[str, Any]]:
3373
3492
  """List Assistance Listings (CFDA programs) (`/api/assistance_listings/`)."""
3374
3493
  params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
3494
+ if shape:
3495
+ params["shape"] = shape
3496
+ if flat:
3497
+ params["flat"] = "true"
3498
+ if flat_lists:
3499
+ params["flat_lists"] = "true"
3375
3500
  data = self._get("/api/assistance_listings/", params)
3376
3501
  return PaginatedResponse(
3377
3502
  count=int(data.get("count", 0)),
@@ -3380,22 +3505,44 @@ class TangoClient:
3380
3505
  results=list(data.get("results") or []),
3381
3506
  )
3382
3507
 
3383
- def get_assistance_listing(self, number: str) -> dict[str, Any]:
3508
+ def get_assistance_listing(
3509
+ self,
3510
+ number: str,
3511
+ shape: str | None = None,
3512
+ flat: bool = False,
3513
+ flat_lists: bool = False,
3514
+ ) -> dict[str, Any]:
3384
3515
  """Get an Assistance Listing by CFDA number (`/api/assistance_listings/{number}/`)."""
3385
3516
  if not number:
3386
3517
  raise TangoValidationError("Assistance listing number is required")
3387
- return self._get(f"/api/assistance_listings/{number}/")
3518
+ params: dict[str, Any] = {}
3519
+ if shape:
3520
+ params["shape"] = shape
3521
+ if flat:
3522
+ params["flat"] = "true"
3523
+ if flat_lists:
3524
+ params["flat_lists"] = "true"
3525
+ return self._get(f"/api/assistance_listings/{number}/", params)
3388
3526
 
3389
3527
  def list_mas_sins(
3390
3528
  self,
3391
3529
  page: int = 1,
3392
3530
  limit: int = 25,
3393
3531
  search: str | None = None,
3532
+ shape: str | None = None,
3533
+ flat: bool = False,
3534
+ flat_lists: bool = False,
3394
3535
  ) -> PaginatedResponse[dict[str, Any]]:
3395
3536
  """List GSA MAS SINs (`/api/mas_sins/`)."""
3396
3537
  params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
3397
3538
  if search is not None:
3398
3539
  params["search"] = search
3540
+ if shape:
3541
+ params["shape"] = shape
3542
+ if flat:
3543
+ params["flat"] = "true"
3544
+ if flat_lists:
3545
+ params["flat_lists"] = "true"
3399
3546
  data = self._get("/api/mas_sins/", params)
3400
3547
  return PaginatedResponse(
3401
3548
  count=int(data.get("count", 0)),
@@ -3404,11 +3551,24 @@ class TangoClient:
3404
3551
  results=list(data.get("results") or []),
3405
3552
  )
3406
3553
 
3407
- def get_mas_sin(self, sin: str) -> dict[str, Any]:
3554
+ def get_mas_sin(
3555
+ self,
3556
+ sin: str,
3557
+ shape: str | None = None,
3558
+ flat: bool = False,
3559
+ flat_lists: bool = False,
3560
+ ) -> dict[str, Any]:
3408
3561
  """Get a MAS SIN by code (`/api/mas_sins/{sin}/`)."""
3409
3562
  if not sin:
3410
3563
  raise TangoValidationError("MAS SIN is required")
3411
- return self._get(f"/api/mas_sins/{sin}/")
3564
+ params: dict[str, Any] = {}
3565
+ if shape:
3566
+ params["shape"] = shape
3567
+ if flat:
3568
+ params["flat"] = "true"
3569
+ if flat_lists:
3570
+ params["flat_lists"] = "true"
3571
+ return self._get(f"/api/mas_sins/{sin}/", params)
3412
3572
 
3413
3573
  # ============================================================================
3414
3574
  # Entity sub-resources