tango-python 1.1.1__tar.gz → 1.1.2__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.2}/.github/workflows/docs-dispatch.yml +1 -1
  2. {tango_python-1.1.1 → tango_python-1.1.2}/.github/workflows/lint.yml +8 -10
  3. {tango_python-1.1.1 → tango_python-1.1.2}/.github/workflows/publish.yml +2 -2
  4. {tango_python-1.1.1 → tango_python-1.1.2}/.github/workflows/test.yml +4 -4
  5. {tango_python-1.1.1 → tango_python-1.1.2}/CHANGELOG.md +40 -0
  6. {tango_python-1.1.1 → tango_python-1.1.2}/PKG-INFO +1 -1
  7. {tango_python-1.1.1 → tango_python-1.1.2}/docs/API_REFERENCE.md +134 -0
  8. {tango_python-1.1.1 → tango_python-1.1.2}/pyproject.toml +1 -1
  9. {tango_python-1.1.1 → tango_python-1.1.2}/tango/__init__.py +1 -1
  10. {tango_python-1.1.1 → tango_python-1.1.2}/tango/client.py +28 -3
  11. {tango_python-1.1.1 → tango_python-1.1.2}/tango/shapes/factory.py +12 -76
  12. {tango_python-1.1.1 → tango_python-1.1.2}/tango/shapes/generator.py +12 -27
  13. {tango_python-1.1.1 → tango_python-1.1.2}/tango/shapes/parser.py +17 -16
  14. {tango_python-1.1.1 → tango_python-1.1.2}/tango/shapes/schema.py +3 -2
  15. tango_python-1.1.2/tests/cassettes/TestEntitiesIntegration.test_get_entity_budget_flows +229 -0
  16. {tango_python-1.1.1 → tango_python-1.1.2}/tests/integration/test_entities_integration.py +58 -0
  17. {tango_python-1.1.1 → tango_python-1.1.2}/tests/test_client.py +59 -0
  18. {tango_python-1.1.1 → tango_python-1.1.2}/.gitignore +0 -0
  19. {tango_python-1.1.1 → tango_python-1.1.2}/LICENSE +0 -0
  20. {tango_python-1.1.1 → tango_python-1.1.2}/README.md +0 -0
  21. {tango_python-1.1.1 → tango_python-1.1.2}/docs/CLIENT.md +0 -0
  22. {tango_python-1.1.1 → tango_python-1.1.2}/docs/DYNAMIC_MODELS.md +0 -0
  23. {tango_python-1.1.1 → tango_python-1.1.2}/docs/ERRORS.md +0 -0
  24. {tango_python-1.1.1 → tango_python-1.1.2}/docs/PAGINATION.md +0 -0
  25. {tango_python-1.1.1 → tango_python-1.1.2}/docs/SHAPES.md +0 -0
  26. {tango_python-1.1.1 → tango_python-1.1.2}/docs/WEBHOOKS.md +0 -0
  27. {tango_python-1.1.1 → tango_python-1.1.2}/scripts/README.md +0 -0
  28. {tango_python-1.1.1 → tango_python-1.1.2}/scripts/check_filter_shape_conformance.py +0 -0
  29. {tango_python-1.1.1 → tango_python-1.1.2}/scripts/fetch_api_schema.py +0 -0
  30. {tango_python-1.1.1 → tango_python-1.1.2}/scripts/generate_schemas_from_api.py +0 -0
  31. {tango_python-1.1.1 → tango_python-1.1.2}/scripts/pr_review.py +0 -0
  32. {tango_python-1.1.1 → tango_python-1.1.2}/scripts/smoke_api_parity.py +0 -0
  33. {tango_python-1.1.1 → tango_python-1.1.2}/scripts/test_production.py +0 -0
  34. {tango_python-1.1.1 → tango_python-1.1.2}/tango/exceptions.py +0 -0
  35. {tango_python-1.1.1 → tango_python-1.1.2}/tango/models.py +0 -0
  36. {tango_python-1.1.1 → tango_python-1.1.2}/tango/shapes/__init__.py +0 -0
  37. {tango_python-1.1.1 → tango_python-1.1.2}/tango/shapes/explicit_schemas.py +0 -0
  38. {tango_python-1.1.1 → tango_python-1.1.2}/tango/shapes/models.py +0 -0
  39. {tango_python-1.1.1 → tango_python-1.1.2}/tango/shapes/types.py +0 -0
  40. {tango_python-1.1.1 → tango_python-1.1.2}/tango/webhooks/__init__.py +0 -0
  41. {tango_python-1.1.1 → tango_python-1.1.2}/tango/webhooks/cli.py +0 -0
  42. {tango_python-1.1.1 → tango_python-1.1.2}/tango/webhooks/receiver.py +0 -0
  43. {tango_python-1.1.1 → tango_python-1.1.2}/tango/webhooks/signing.py +0 -0
  44. {tango_python-1.1.1 → tango_python-1.1.2}/tango/webhooks/simulate.py +0 -0
  45. {tango_python-1.1.1 → tango_python-1.1.2}/tests/__init__.py +0 -0
  46. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestAgenciesIntegration.test_get_agency +0 -0
  47. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestAgenciesIntegration.test_list_agencies +0 -0
  48. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_field_type_validation +0 -0
  49. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_parsing_consistency +0 -0
  50. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestBusinessTypesIntegration.test_list_business_types +0 -0
  51. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestContractsIntegration.test_combined_filters_work_together +0 -0
  52. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestContractsIntegration.test_contract_cursor_pagination +0 -0
  53. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestContractsIntegration.test_contract_data_object_parsing +0 -0
  54. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestContractsIntegration.test_contract_field_types +0 -0
  55. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[keyword-software] +0 -0
  56. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[psc_code-R425] +0 -0
  57. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_awarding_agency_filter +0 -0
  58. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_date_range_filter +0 -0
  59. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_flat +0 -0
  60. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_naics_code_filter +0 -0
  61. {tango_python-1.1.1 → tango_python-1.1.2}/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.2}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[default-None] +0 -0
  63. {tango_python-1.1.1 → tango_python-1.1.2}/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.2}/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.2}/tests/cassettes/TestContractsIntegration.test_new_expiring_filters +0 -0
  66. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestContractsIntegration.test_new_fiscal_year_range_filters +0 -0
  67. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestContractsIntegration.test_new_identifier_filters +0 -0
  68. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestContractsIntegration.test_search_contracts_with_filters +0 -0
  69. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestContractsIntegration.test_search_filters_object_with_new_parameters +0 -0
  70. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[asc-] +0 -0
  71. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[desc--] +0 -0
  72. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_contracts +0 -0
  73. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_entities +0 -0
  74. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestEdgeCasesIntegration.test_date_field_parsing_edge_cases +0 -0
  75. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestEdgeCasesIntegration.test_decimal_field_parsing_edge_cases +0 -0
  76. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestEdgeCasesIntegration.test_empty_list_responses +0 -0
  77. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestEdgeCasesIntegration.test_entity_parsing_with_various_address_formats +0 -0
  78. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestEdgeCasesIntegration.test_flattened_responses_with_flat_lists +0 -0
  79. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestEdgeCasesIntegration.test_list_field_parsing_consistency +0 -0
  80. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_nested_objects_with_missing_data +0 -0
  81. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_null_missing_fields_in_contracts +0 -0
  82. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_with_minimal_shape_sparse_data +0 -0
  83. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestEntitiesIntegration.test_entity_field_types +0 -0
  84. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestEntitiesIntegration.test_entity_location_parsing +0 -0
  85. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestEntitiesIntegration.test_entity_parsing_with_business_types +0 -0
  86. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestEntitiesIntegration.test_entity_with_various_identifiers +0 -0
  87. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestEntitiesIntegration.test_get_entity_by_uei +0 -0
  88. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_flat +0 -0
  89. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_search +0 -0
  90. {tango_python-1.1.1 → tango_python-1.1.2}/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.2}/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.2}/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.2}/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.2}/tests/cassettes/TestForecastsIntegration.test_forecast_field_types +0 -0
  95. {tango_python-1.1.1 → tango_python-1.1.2}/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.2}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[default-None] +0 -0
  97. {tango_python-1.1.1 → tango_python-1.1.2}/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.2}/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.2}/tests/cassettes/TestGrantsIntegration.test_grant_field_types +0 -0
  100. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestGrantsIntegration.test_grant_pagination +0 -0
  101. {tango_python-1.1.1 → tango_python-1.1.2}/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.2}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[default-None] +0 -0
  103. {tango_python-1.1.1 → tango_python-1.1.2}/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.2}/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.2}/tests/cassettes/TestIDVsIntegration.test_get_idv_uses_default_shape +0 -0
  106. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestIDVsIntegration.test_list_idv_awards_uses_default_shape +0 -0
  107. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestIDVsIntegration.test_list_idv_child_idvs_uses_default_shape +0 -0
  108. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestIDVsIntegration.test_list_idv_transactions +0 -0
  109. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestIDVsIntegration.test_list_idvs_uses_default_shape_and_keyset_params +0 -0
  110. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_code +0 -0
  111. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_name_text +0 -0
  112. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating +0 -0
  113. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating_max +0 -0
  114. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestITDashboardIntegration.test_filter_by_performance_risk +0 -0
  115. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestITDashboardIntegration.test_filter_by_type_of_investment +0 -0
  116. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestITDashboardIntegration.test_filter_by_updated_time_range +0 -0
  117. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestITDashboardIntegration.test_funding_and_cio_evaluation_expansions +0 -0
  118. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestITDashboardIntegration.test_get_itdashboard_investment_by_uii +0 -0
  119. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestITDashboardIntegration.test_itdashboard_pagination +0 -0
  120. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_search +0 -0
  121. {tango_python-1.1.1 → tango_python-1.1.2}/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.2}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[default-None] +0 -0
  123. {tango_python-1.1.1 → tango_python-1.1.2}/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.2}/tests/cassettes/TestNaicsIntegration.test_list_naics +0 -0
  125. {tango_python-1.1.1 → tango_python-1.1.2}/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.2}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[default-None] +0 -0
  127. {tango_python-1.1.1 → tango_python-1.1.2}/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.2}/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.2}/tests/cassettes/TestNoticesIntegration.test_notice_field_types +0 -0
  130. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestNoticesIntegration.test_notice_pagination +0 -0
  131. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestNoticesIntegration.test_notice_with_meta_fields +0 -0
  132. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestOTAsIntegration.test_get_ota +0 -0
  133. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestOTAsIntegration.test_list_otas +0 -0
  134. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestOTIDVsIntegration.test_get_otidv +0 -0
  135. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestOTIDVsIntegration.test_list_otidvs +0 -0
  136. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestOfficesIntegration.test_get_office +0 -0
  137. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestOfficesIntegration.test_list_offices +0 -0
  138. {tango_python-1.1.1 → tango_python-1.1.2}/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.2}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[default-None] +0 -0
  140. {tango_python-1.1.1 → tango_python-1.1.2}/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.2}/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.2}/tests/cassettes/TestOpportunitiesIntegration.test_opportunity_field_types +0 -0
  143. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestOrganizationsIntegration.test_get_organization +0 -0
  144. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestOrganizationsIntegration.test_list_organizations +0 -0
  145. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestProtestsIntegration.test_get_protest_by_case_id +0 -0
  146. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_filter +0 -0
  147. {tango_python-1.1.1 → tango_python-1.1.2}/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.2}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[default-None] +0 -0
  149. {tango_python-1.1.1 → tango_python-1.1.2}/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.2}/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.2}/tests/cassettes/TestProtestsIntegration.test_protest_pagination +0 -0
  152. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestSubawardsIntegration.test_list_subawards +0 -0
  153. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[custom-key,piid,description] +0 -0
  154. {tango_python-1.1.1 → tango_python-1.1.2}/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.2}/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.2}/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.2}/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.2}/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.2}/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.2}/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.2}/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.2}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists +0 -0
  163. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_with_metrics_expansion +0 -0
  164. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape +0 -0
  165. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_orders_uses_default_shape +0 -0
  166. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search +0 -0
  167. {tango_python-1.1.1 → tango_python-1.1.2}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_with_ordering +0 -0
  168. {tango_python-1.1.1 → tango_python-1.1.2}/tests/conftest.py +0 -0
  169. {tango_python-1.1.1 → tango_python-1.1.2}/tests/integration/README.md +0 -0
  170. {tango_python-1.1.1 → tango_python-1.1.2}/tests/integration/__init__.py +0 -0
  171. {tango_python-1.1.1 → tango_python-1.1.2}/tests/integration/conftest.py +0 -0
  172. {tango_python-1.1.1 → tango_python-1.1.2}/tests/integration/test_agencies_integration.py +0 -0
  173. {tango_python-1.1.1 → tango_python-1.1.2}/tests/integration/test_contracts_integration.py +0 -0
  174. {tango_python-1.1.1 → tango_python-1.1.2}/tests/integration/test_edge_cases_integration.py +0 -0
  175. {tango_python-1.1.1 → tango_python-1.1.2}/tests/integration/test_forecasts_integration.py +0 -0
  176. {tango_python-1.1.1 → tango_python-1.1.2}/tests/integration/test_grants_integration.py +0 -0
  177. {tango_python-1.1.1 → tango_python-1.1.2}/tests/integration/test_itdashboard_integration.py +0 -0
  178. {tango_python-1.1.1 → tango_python-1.1.2}/tests/integration/test_naics_integration.py +0 -0
  179. {tango_python-1.1.1 → tango_python-1.1.2}/tests/integration/test_notices_integration.py +0 -0
  180. {tango_python-1.1.1 → tango_python-1.1.2}/tests/integration/test_offices_integration.py +0 -0
  181. {tango_python-1.1.1 → tango_python-1.1.2}/tests/integration/test_opportunities_integration.py +0 -0
  182. {tango_python-1.1.1 → tango_python-1.1.2}/tests/integration/test_organizations_integration.py +0 -0
  183. {tango_python-1.1.1 → tango_python-1.1.2}/tests/integration/test_otas_otidvs_integration.py +0 -0
  184. {tango_python-1.1.1 → tango_python-1.1.2}/tests/integration/test_protests_integration.py +0 -0
  185. {tango_python-1.1.1 → tango_python-1.1.2}/tests/integration/test_reference_data_integration.py +0 -0
  186. {tango_python-1.1.1 → tango_python-1.1.2}/tests/integration/test_subawards_integration.py +0 -0
  187. {tango_python-1.1.1 → tango_python-1.1.2}/tests/integration/test_vehicles_idvs_integration.py +0 -0
  188. {tango_python-1.1.1 → tango_python-1.1.2}/tests/integration/validation.py +0 -0
  189. {tango_python-1.1.1 → tango_python-1.1.2}/tests/production/__init__.py +0 -0
  190. {tango_python-1.1.1 → tango_python-1.1.2}/tests/production/conftest.py +0 -0
  191. {tango_python-1.1.1 → tango_python-1.1.2}/tests/production/test_production_smoke.py +0 -0
  192. {tango_python-1.1.1 → tango_python-1.1.2}/tests/test_api_parity.py +0 -0
  193. {tango_python-1.1.1 → tango_python-1.1.2}/tests/test_models.py +0 -0
  194. {tango_python-1.1.1 → tango_python-1.1.2}/tests/test_shapes.py +0 -0
  195. {tango_python-1.1.1 → tango_python-1.1.2}/tests/test_webhooks_cli.py +0 -0
  196. {tango_python-1.1.1 → tango_python-1.1.2}/tests/test_webhooks_receiver.py +0 -0
  197. {tango_python-1.1.1 → tango_python-1.1.2}/tests/test_webhooks_signing.py +0 -0
  198. {tango_python-1.1.1 → tango_python-1.1.2}/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,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.1.2] - 2026-06-04
11
+
12
+ ### Changed
13
+ - `get_entity_budget_flows()` now exposes the backend's standard page/limit
14
+ pagination and `fiscal_year` filter, and returns
15
+ `PaginatedResponse[dict[str, Any]]` instead of a raw dict. The backend has
16
+ always paginated this endpoint via `StandardResultsSetPagination`; the
17
+ previous signature gave callers no way to reach pages beyond the first or
18
+ to narrow by fiscal year. Callers that were indexing `result["results"]`
19
+ on the old return value should switch to `result.results` (and can now use
20
+ `result.next` / `page=` to walk further pages).
21
+ - Completed the strict-`mypy` burn-down across `tango/shapes/` (parser,
22
+ generator, factory, schema). All changes are type-annotation/typing
23
+ corrections with no runtime behavior change, except:
24
+ - `FieldSchema.nested_model` is now typed `type | str | None` (it always
25
+ accepted string model names from the explicit schemas; the annotation was
26
+ wrong). `ModelFactory.validate_data` and `ShapeParser._validate_field_spec`
27
+ likewise accept `type | str` for the model argument.
28
+ - Removed two dead `elif field_spec.is_wildcard:` branches (in
29
+ `TypeGenerator.generate_type` and `ModelFactory.create_instance`) and the
30
+ now-orphaned `_parse_nested_wildcard` helper. These were unreachable —
31
+ wildcard field specs are fully handled by the top-of-loop branch that
32
+ `continue`s before reaching them — so removal is behavior-preserving.
33
+
34
+ ### Docs
35
+ - `docs/API_REFERENCE.md` now documents the Budget surface that shipped in
36
+ v1.1.0: a new `## Budget` section covering `list_budget_accounts`,
37
+ `get_budget_account`, `get_budget_account_quarters`, and
38
+ `get_budget_account_recipients`; a `get_entity_budget_flows()` entry under
39
+ Entity Sub-resources; a `BUDGET_ACCOUNTS_MINIMAL` row in the ShapeConfig
40
+ table; and a Budget entry in the table of contents.
41
+
42
+ ### CI
43
+ - Bumped GitHub Actions off the deprecated Node 20 runtime (forced off
44
+ 2026-06-02): `actions/checkout` v4→v6, `astral-sh/setup-uv` v4→v8.1.0
45
+ (pinned exact — no floating `v8` major tag is published yet), and
46
+ `codecov/codecov-action` v3→v5 (with the renamed `files:` input).
47
+ - `mypy` is now a **hard gate** in `lint.yml` (no longer advisory). The
48
+ `tango/` package type-checks cleanly under strict mypy.
49
+
10
50
  ## [1.1.1] - 2026-05-29
11
51
 
12
52
  ### 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.2
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.2"
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.2"
48
48
  __all__ = [
49
49
  "TangoClient",
50
50
  "TangoAPIError",
@@ -2081,11 +2081,36 @@ class TangoClient:
2081
2081
  data = self._get(f"/api/entities/{key}/", params)
2082
2082
  return self._parse_response_with_shape(data, shape, Entity, flat, flat_lists)
2083
2083
 
2084
- def get_entity_budget_flows(self, uei: str) -> dict[str, Any]:
2085
- """Get budget flows for an entity (`/api/entities/{uei}/budget-flows/`)."""
2084
+ def get_entity_budget_flows(
2085
+ self,
2086
+ uei: str,
2087
+ page: int = 1,
2088
+ limit: int = 25,
2089
+ fiscal_year: int | None = None,
2090
+ ) -> PaginatedResponse[dict[str, Any]]:
2091
+ """Get budget flows for an entity (`/api/entities/{uei}/budget-flows/`).
2092
+
2093
+ Standard page/limit pagination (default 25, max 100). Each result row
2094
+ is a hand-built dict from the backend (no shape system).
2095
+
2096
+ Args:
2097
+ uei: Entity UEI. Required.
2098
+ page: Page number.
2099
+ limit: Results per page (max 100).
2100
+ fiscal_year: Optional fiscal year filter.
2101
+ """
2086
2102
  if not uei:
2087
2103
  raise TangoValidationError("UEI is required")
2088
- return self._get(f"/api/entities/{uei}/budget-flows/")
2104
+ params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
2105
+ if fiscal_year is not None:
2106
+ params["fiscal_year"] = fiscal_year
2107
+ data = self._get(f"/api/entities/{uei}/budget-flows/", params)
2108
+ return PaginatedResponse(
2109
+ count=int(data.get("count", 0)),
2110
+ next=data.get("next"),
2111
+ previous=data.get("previous"),
2112
+ results=list(data.get("results") or []),
2113
+ )
2089
2114
 
2090
2115
  # Forecast endpoints
2091
2116
  def list_forecasts(
@@ -23,7 +23,7 @@ import logging
23
23
  from collections.abc import Callable
24
24
  from datetime import date, datetime
25
25
  from decimal import Decimal
26
- from typing import Any
26
+ from typing import Any, cast
27
27
 
28
28
  from tango.exceptions import ModelInstantiationError
29
29
  from tango.shapes.generator import TypeGenerator
@@ -542,38 +542,6 @@ class ModelFactory:
542
542
  # Value is not a dict - might be a primitive or None
543
543
  result[result_field_name] = value
544
544
 
545
- elif field_spec.is_wildcard:
546
- # Wildcard on nested field - use full model type
547
- # This is handled at the top level, but we need to handle it here too
548
- # for nested wildcards like recipient(*)
549
- if field_schema.nested_model:
550
- if field_schema.is_list:
551
- if isinstance(value, list):
552
- nested_instances = []
553
- for item in value:
554
- if isinstance(item, dict):
555
- # Parse all fields from the nested model
556
- nested_instance = self._parse_nested_wildcard(
557
- item, field_schema.nested_model
558
- )
559
- nested_instances.append(nested_instance)
560
- else:
561
- nested_instances.append(item)
562
- result[result_field_name] = nested_instances
563
- else:
564
- result[result_field_name] = value
565
- else:
566
- if isinstance(value, dict):
567
- nested_instance = self._parse_nested_wildcard(
568
- value, field_schema.nested_model
569
- )
570
- result[result_field_name] = nested_instance
571
- else:
572
- result[result_field_name] = value
573
- else:
574
- # Not a nested model, just use the value
575
- result[result_field_name] = value
576
-
577
545
  else:
578
546
  # Simple field - parse using appropriate parser
579
547
  parsed_value = self._parse_field(
@@ -661,7 +629,7 @@ class ModelFactory:
661
629
  raise ModelInstantiationError(
662
630
  f"Could not resolve nested model '{nested_model}'"
663
631
  )
664
- return model_class
632
+ return cast(type, model_class)
665
633
  except ImportError as err:
666
634
  raise ModelInstantiationError(
667
635
  f"Could not import models module to resolve '{nested_model}'"
@@ -704,41 +672,6 @@ class ModelFactory:
704
672
  # Recursively create nested instance
705
673
  return self.create_instance(data, nested_shape, resolved_model, nested_type)
706
674
 
707
- def _parse_nested_wildcard(
708
- self, data: dict[str, Any], nested_model: type | str
709
- ) -> dict[str, Any]:
710
- """Parse nested object with wildcard (all fields)
711
-
712
- Args:
713
- data: Nested object data
714
- nested_model: Model class or string name for the nested object
715
-
716
- Returns:
717
- Dictionary with all parsed fields
718
- """
719
- # Resolve nested model if it's a string
720
- resolved_model = self._resolve_nested_model(nested_model)
721
-
722
- # Ensure model is registered
723
- if not self.schema_registry.is_registered(resolved_model):
724
- self.schema_registry.register(resolved_model)
725
-
726
- # Get model schema
727
- model_schema = self.schema_registry.get_schema(resolved_model)
728
-
729
- # Parse all fields
730
- result: dict[str, Any] = {}
731
- for field_name, value in data.items():
732
- if field_name in model_schema:
733
- field_schema = model_schema[field_name]
734
- parsed_value = self._parse_field(field_name, value, field_schema.type, field_schema)
735
- result[field_name] = parsed_value
736
- else:
737
- # Field not in schema, include as-is
738
- result[field_name] = value
739
-
740
- return result
741
-
742
675
  def _parse_field(self, field_name: str, value: Any, field_type: type, field_schema: Any) -> Any:
743
676
  """Parse a single field value using appropriate parser
744
677
 
@@ -778,7 +711,7 @@ class ModelFactory:
778
711
  return value
779
712
 
780
713
  def validate_data(
781
- self, data: dict[str, Any], shape_spec: ShapeSpec, base_model: type
714
+ self, data: dict[str, Any], shape_spec: ShapeSpec, base_model: type | str
782
715
  ) -> list[str]:
783
716
  """Validate that data matches the shape specification
784
717
 
@@ -803,11 +736,15 @@ class ModelFactory:
803
736
  errors: list[str] = []
804
737
 
805
738
  if not isinstance(data, dict):
806
- errors.append(f"Expected dictionary data, got {type(data).__name__}")
739
+ errors.append( # type: ignore[unreachable]
740
+ f"Expected dictionary data, got {type(data).__name__}"
741
+ )
807
742
  return errors
808
743
 
809
- # Ensure model is registered
810
- if not self.schema_registry.is_registered(base_model):
744
+ # Ensure model is registered. String model names are expected to be
745
+ # pre-registered (explicit schemas); only concrete classes can be
746
+ # auto-registered via introspection.
747
+ if isinstance(base_model, type) and not self.schema_registry.is_registered(base_model):
811
748
  self.schema_registry.register(base_model)
812
749
 
813
750
  # Get model schema
@@ -826,9 +763,8 @@ class ModelFactory:
826
763
 
827
764
  # Check if field exists in schema
828
765
  if field_spec.name not in model_schema:
829
- errors.append(
830
- f"Field '{field_spec.name}' does not exist in {base_model.__name__} schema"
831
- )
766
+ model_name = base_model.__name__ if isinstance(base_model, type) else base_model
767
+ errors.append(f"Field '{field_spec.name}' does not exist in {model_name} schema")
832
768
  continue
833
769
 
834
770
  field_schema = model_schema[field_spec.name]
@@ -20,7 +20,7 @@ Examples:
20
20
  import logging
21
21
  import threading
22
22
  from collections import OrderedDict
23
- from typing import Any, get_args, get_origin, get_type_hints
23
+ from typing import Any, cast, get_args, get_origin, get_type_hints
24
24
 
25
25
  from tango.exceptions import TypeGenerationError
26
26
  from tango.shapes.models import ShapeSpec
@@ -250,7 +250,10 @@ class TypeGenerator:
250
250
 
251
251
  field_schema = model_schema[field_spec.name]
252
252
 
253
- # Determine field type
253
+ # Determine field type. The value is a heterogeneous mix of type
254
+ # objects, parameterized generics (list[...]), and union objects,
255
+ # so it is intentionally typed as Any.
256
+ field_type: Any
254
257
  if field_spec.nested_fields:
255
258
  # Generate nested type
256
259
  if not field_schema.nested_model:
@@ -275,25 +278,7 @@ class TypeGenerator:
275
278
 
276
279
  # Handle optional types
277
280
  if field_schema.is_optional:
278
- field_type = field_type | None # type: ignore
279
-
280
- annotations[field_name] = field_type
281
-
282
- elif field_spec.is_wildcard:
283
- # Wildcard on nested field - use full model type
284
- if field_schema.nested_model:
285
- # Resolve nested model if it's a string
286
- field_type = self._resolve_nested_model(field_schema.nested_model)
287
- else:
288
- field_type = field_schema.type
289
-
290
- # Handle list types
291
- if field_schema.is_list:
292
- field_type = list[field_type] # type: ignore
293
-
294
- # Handle optional types
295
- if field_schema.is_optional:
296
- field_type = field_type | None # type: ignore
281
+ field_type = field_type | None
297
282
 
298
283
  annotations[field_name] = field_type
299
284
 
@@ -303,11 +288,11 @@ class TypeGenerator:
303
288
 
304
289
  # Handle list types
305
290
  if field_schema.is_list:
306
- field_type = list[field_type] # type: ignore
291
+ field_type = list[field_type]
307
292
 
308
293
  # Handle optional types
309
294
  if field_schema.is_optional:
310
- field_type = field_type | None # type: ignore
295
+ field_type = field_type | None
311
296
 
312
297
  annotations[field_name] = field_type
313
298
 
@@ -329,7 +314,7 @@ class TypeGenerator:
329
314
  field_type = field_schema.type
330
315
  # Handle optional types
331
316
  if field_schema.is_optional:
332
- field_type = field_type | None # type: ignore
317
+ field_type = field_type | None
333
318
  annotations[auto_field] = field_type
334
319
 
335
320
  # Create TypedDict dynamically
@@ -414,7 +399,7 @@ class TypeGenerator:
414
399
  model_class = getattr(models, nested_model, None)
415
400
  if model_class is None:
416
401
  raise TypeGenerationError(f"Could not resolve nested model '{nested_model}'")
417
- return model_class
402
+ return cast(type, model_class)
418
403
  except ImportError as err:
419
404
  raise TypeGenerationError(
420
405
  f"Could not import models module to resolve '{nested_model}'"
@@ -555,7 +540,7 @@ class TypeGenerator:
555
540
 
556
541
  # Handle basic types
557
542
  if hasattr(type_annotation, "__name__"):
558
- type_name = type_annotation.__name__
543
+ type_name = str(type_annotation.__name__)
559
544
  else:
560
545
  type_name = str(type_annotation)
561
546
 
@@ -576,7 +561,7 @@ class TypeGenerator:
576
561
  if args:
577
562
  formatted_args = [self._format_type_annotation(arg) for arg in args]
578
563
  return f"{origin.__name__}[{', '.join(formatted_args)}]"
579
- return origin.__name__
564
+ return str(origin.__name__)
580
565
 
581
566
  return type_name
582
567