tango-python 1.10.0__tar.gz → 1.12.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. {tango_python-1.10.0 → tango_python-1.12.0}/CHANGELOG.md +22 -0
  2. {tango_python-1.10.0 → tango_python-1.12.0}/PKG-INFO +1 -1
  3. {tango_python-1.10.0 → tango_python-1.12.0}/docs/API_REFERENCE.md +45 -2
  4. {tango_python-1.10.0 → tango_python-1.12.0}/pyproject.toml +1 -1
  5. {tango_python-1.10.0 → tango_python-1.12.0}/tango/__init__.py +7 -1
  6. {tango_python-1.10.0 → tango_python-1.12.0}/tango/client.py +23 -1
  7. {tango_python-1.10.0 → tango_python-1.12.0}/tango/models.py +73 -2
  8. {tango_python-1.10.0 → tango_python-1.12.0}/tango/shapes/generated_overlay.py +33 -0
  9. {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_client.py +135 -0
  10. tango_python-1.12.0/tests/test_opportunity_awards.py +136 -0
  11. {tango_python-1.10.0 → tango_python-1.12.0}/.gitignore +0 -0
  12. {tango_python-1.10.0 → tango_python-1.12.0}/LICENSE +0 -0
  13. {tango_python-1.10.0 → tango_python-1.12.0}/README.md +0 -0
  14. {tango_python-1.10.0 → tango_python-1.12.0}/docs/CLIENT.md +0 -0
  15. {tango_python-1.10.0 → tango_python-1.12.0}/docs/DYNAMIC_MODELS.md +0 -0
  16. {tango_python-1.10.0 → tango_python-1.12.0}/docs/ERRORS.md +0 -0
  17. {tango_python-1.10.0 → tango_python-1.12.0}/docs/PAGINATION.md +0 -0
  18. {tango_python-1.10.0 → tango_python-1.12.0}/docs/SHAPES.md +0 -0
  19. {tango_python-1.10.0 → tango_python-1.12.0}/docs/WEBHOOKS.md +0 -0
  20. {tango_python-1.10.0 → tango_python-1.12.0}/scripts/README.md +0 -0
  21. {tango_python-1.10.0 → tango_python-1.12.0}/scripts/check_filter_shape_conformance.py +0 -0
  22. {tango_python-1.10.0 → tango_python-1.12.0}/scripts/check_shape_coverage.py +0 -0
  23. {tango_python-1.10.0 → tango_python-1.12.0}/scripts/fetch_api_schema.py +0 -0
  24. {tango_python-1.10.0 → tango_python-1.12.0}/scripts/generate_schemas_from_api.py +0 -0
  25. {tango_python-1.10.0 → tango_python-1.12.0}/scripts/generate_shape_overlay.py +0 -0
  26. {tango_python-1.10.0 → tango_python-1.12.0}/scripts/pr_review.py +0 -0
  27. {tango_python-1.10.0 → tango_python-1.12.0}/scripts/probe_shape_types.py +0 -0
  28. {tango_python-1.10.0 → tango_python-1.12.0}/scripts/refresh_contract.py +0 -0
  29. {tango_python-1.10.0 → tango_python-1.12.0}/scripts/smoke_api_parity.py +0 -0
  30. {tango_python-1.10.0 → tango_python-1.12.0}/scripts/test_production.py +0 -0
  31. {tango_python-1.10.0 → tango_python-1.12.0}/tango/exceptions.py +0 -0
  32. {tango_python-1.10.0 → tango_python-1.12.0}/tango/shapes/__init__.py +0 -0
  33. {tango_python-1.10.0 → tango_python-1.12.0}/tango/shapes/explicit_schemas.py +0 -0
  34. {tango_python-1.10.0 → tango_python-1.12.0}/tango/shapes/factory.py +0 -0
  35. {tango_python-1.10.0 → tango_python-1.12.0}/tango/shapes/generator.py +0 -0
  36. {tango_python-1.10.0 → tango_python-1.12.0}/tango/shapes/models.py +0 -0
  37. {tango_python-1.10.0 → tango_python-1.12.0}/tango/shapes/parser.py +0 -0
  38. {tango_python-1.10.0 → tango_python-1.12.0}/tango/shapes/schema.py +0 -0
  39. {tango_python-1.10.0 → tango_python-1.12.0}/tango/shapes/types.py +0 -0
  40. {tango_python-1.10.0 → tango_python-1.12.0}/tango/webhooks/__init__.py +0 -0
  41. {tango_python-1.10.0 → tango_python-1.12.0}/tango/webhooks/cli.py +0 -0
  42. {tango_python-1.10.0 → tango_python-1.12.0}/tango/webhooks/receiver.py +0 -0
  43. {tango_python-1.10.0 → tango_python-1.12.0}/tango/webhooks/signing.py +0 -0
  44. {tango_python-1.10.0 → tango_python-1.12.0}/tango/webhooks/simulate.py +0 -0
  45. {tango_python-1.10.0 → tango_python-1.12.0}/tests/__init__.py +0 -0
  46. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestAgenciesIntegration.test_get_agency +0 -0
  47. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestAgenciesIntegration.test_list_agencies +0 -0
  48. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_field_type_validation +0 -0
  49. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_parsing_consistency +0 -0
  50. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestBusinessTypesIntegration.test_list_business_types +0 -0
  51. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_combined_filters_work_together +0 -0
  52. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_contract_cursor_pagination +0 -0
  53. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_contract_data_object_parsing +0 -0
  54. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_contract_field_types +0 -0
  55. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[keyword-software] +0 -0
  56. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[psc_code-R425] +0 -0
  57. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_awarding_agency_filter +0 -0
  58. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_date_range_filter +0 -0
  59. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_flat +0 -0
  60. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_naics_code_filter +0 -0
  61. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[custom-key,piid,recipient(display_name),total_contract_value,award_date] +0 -0
  62. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[default-None] +0 -0
  63. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[detailed-key,piid,award_date,description,total_contract_value,obligated,fiscal_year,set_aside,recipient(display_name,uei),awarding_office(-),place_of_performa...ce114a3c47e2037aaa3c15d00b7031bd +0 -0
  64. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[minimal-key,piid,award_date,recipient(display_name),description,total_contract_value] +0 -0
  65. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_new_expiring_filters +0 -0
  66. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_new_fiscal_year_range_filters +0 -0
  67. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_new_identifier_filters +0 -0
  68. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_search_contracts_with_filters +0 -0
  69. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_search_filters_object_with_new_parameters +0 -0
  70. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[asc-] +0 -0
  71. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[desc--] +0 -0
  72. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_contracts +0 -0
  73. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_entities +0 -0
  74. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_date_field_parsing_edge_cases +0 -0
  75. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_decimal_field_parsing_edge_cases +0 -0
  76. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_empty_list_responses +0 -0
  77. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_entity_parsing_with_various_address_formats +0 -0
  78. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_flattened_responses_with_flat_lists +0 -0
  79. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_list_field_parsing_consistency +0 -0
  80. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_nested_objects_with_missing_data +0 -0
  81. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_null_missing_fields_in_contracts +0 -0
  82. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_with_minimal_shape_sparse_data +0 -0
  83. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_entity_field_types +0 -0
  84. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_entity_location_parsing +0 -0
  85. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_entity_parsing_with_business_types +0 -0
  86. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_entity_with_various_identifiers +0 -0
  87. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_budget_flows +0 -0
  88. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_by_uei +0 -0
  89. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_flat +0 -0
  90. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_search +0 -0
  91. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[comprehensive-uei,legal_business_name,dba_name,cage_code,business_types,primary_naics,naics_codes,psc_codes,email_address,entity_url,description,capabilities,ke...1603a7d52e211cf2b3bc7d32080238aa +0 -0
  92. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[custom-uei,legal_business_name,cage_code] +0 -0
  93. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
  94. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[with_address-uei,legal_business_name,cage_code,business_types,physical_address] +0 -0
  95. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestForecastsIntegration.test_forecast_field_types +0 -0
  96. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[custom-id,title,anticipated_award_date] +0 -0
  97. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[default-None] +0 -0
  98. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[detailed-id,source_system,external_id,title,description,anticipated_award_date,fiscal_year,naics_code,status,is_active] +0 -0
  99. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[minimal-id,title,anticipated_award_date,fiscal_year,naics_code,status] +0 -0
  100. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestGrantsIntegration.test_grant_field_types +0 -0
  101. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestGrantsIntegration.test_grant_pagination +0 -0
  102. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[custom-grant_id,title,opportunity_number] +0 -0
  103. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[default-None] +0 -0
  104. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[detailed-grant_id,opportunity_number,title,status(-),agency_code,description,last_updated,cfda_numbers(number,title),applicant_types(-),funding_categories(-)] +0 -0
  105. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[minimal-grant_id,opportunity_number,title,status(-),agency_code] +0 -0
  106. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestIDVsIntegration.test_get_idv_uses_default_shape +0 -0
  107. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_awards_uses_default_shape +0 -0
  108. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_child_idvs_uses_default_shape +0 -0
  109. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_transactions +0 -0
  110. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestIDVsIntegration.test_list_idvs_uses_default_shape_and_keyset_params +0 -0
  111. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_code +0 -0
  112. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_name_text +0 -0
  113. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating +0 -0
  114. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating_max +0 -0
  115. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_performance_risk +0 -0
  116. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_type_of_investment +0 -0
  117. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_updated_time_range +0 -0
  118. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_funding_and_cio_evaluation_expansions +0 -0
  119. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_get_itdashboard_investment_by_uii +0 -0
  120. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_itdashboard_pagination +0 -0
  121. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_search +0 -0
  122. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[custom-uii,agency_name,investment_title,updated_time] +0 -0
  123. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[default-None] +0 -0
  124. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[minimal-uii,agency_name,bureau_name,investment_title,type_of_investment,part_of_it_portfolio,updated_time,url] +0 -0
  125. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestNaicsIntegration.test_list_naics +0 -0
  126. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[custom-notice_id,title,solicitation_number] +0 -0
  127. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[default-None] +0 -0
  128. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[detailed-notice_id,title,description,solicitation_number,posted_date,naics_code,set_aside,office(-),place_of_performance(-)] +0 -0
  129. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
  130. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestNoticesIntegration.test_notice_field_types +0 -0
  131. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestNoticesIntegration.test_notice_pagination +0 -0
  132. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestNoticesIntegration.test_notice_with_meta_fields +0 -0
  133. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOTAsIntegration.test_get_ota +0 -0
  134. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOTAsIntegration.test_list_otas +0 -0
  135. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOTIDVsIntegration.test_get_otidv +0 -0
  136. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOTIDVsIntegration.test_list_otidvs +0 -0
  137. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOfficesIntegration.test_get_office +0 -0
  138. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOfficesIntegration.test_list_offices +0 -0
  139. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[custom-opportunity_id,title,solicitation_number] +0 -0
  140. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[default-None] +0 -0
  141. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[detailed-opportunity_id,title,description,solicitation_number,response_deadline,first_notice_date,last_notice_date,active,naics_code,psc_code,set_asid...23b6b4502ddd665b7184afcff6c6d8d9 +0 -0
  142. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
  143. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOpportunitiesIntegration.test_opportunity_field_types +0 -0
  144. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOrganizationsIntegration.test_get_organization +0 -0
  145. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestOrganizationsIntegration.test_list_organizations +0 -0
  146. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestProtestsIntegration.test_get_protest_by_case_id +0 -0
  147. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_filter +0 -0
  148. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[custom-case_id,title,source_system,outcome] +0 -0
  149. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[default-None] +0 -0
  150. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[minimal-case_id,case_number,title,source_system,outcome,filed_date] +0 -0
  151. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[with_dockets-case_id,case_number,title,outcome,filed_date,dockets(docket_number,filed_date,outcome)] +0 -0
  152. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestProtestsIntegration.test_protest_pagination +0 -0
  153. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestSubawardsIntegration.test_list_subawards +0 -0
  154. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[custom-key,piid,description] +0 -0
  155. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[minimal-key,piid,award_date,recipient(display_name),description,total_contract_value] +0 -0
  156. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[ultra_minimal-key,piid,recipient(display_name),total_contract_value] +0 -0
  157. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
  158. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[with_address-uei,legal_business_name,cage_code,business_types,physical_address] +0 -0
  159. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestTypeHintsIntegration.test_notices_dict_access[detailed-notice_id,title,description,solicitation_number,posted_date,naics_code,set_aside,office(-),place_of_performance(-)] +0 -0
  160. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestTypeHintsIntegration.test_notices_dict_access[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
  161. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestTypeHintsIntegration.test_opportunities_dict_access[detailed-opportunity_id,title,description,solicitation_number,response_deadline,first_notice_date,last_notice_date,active,naics_code,psc_code,set_aside,sam_url,office(-),place_of_performance(-)] +0 -0
  162. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestTypeHintsIntegration.test_opportunities_dict_access[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
  163. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists +0 -0
  164. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_with_metrics_expansion +0 -0
  165. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape +0 -0
  166. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_orders_uses_default_shape +0 -0
  167. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search +0 -0
  168. {tango_python-1.10.0 → tango_python-1.12.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_with_ordering +0 -0
  169. {tango_python-1.10.0 → tango_python-1.12.0}/tests/conftest.py +0 -0
  170. {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/README.md +0 -0
  171. {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/__init__.py +0 -0
  172. {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/conftest.py +0 -0
  173. {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_agencies_integration.py +0 -0
  174. {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_contracts_integration.py +0 -0
  175. {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_edge_cases_integration.py +0 -0
  176. {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_entities_integration.py +0 -0
  177. {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_forecasts_integration.py +0 -0
  178. {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_grants_integration.py +0 -0
  179. {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_itdashboard_integration.py +0 -0
  180. {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_naics_integration.py +0 -0
  181. {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_notices_integration.py +0 -0
  182. {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_offices_integration.py +0 -0
  183. {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_opportunities_integration.py +0 -0
  184. {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_organizations_integration.py +0 -0
  185. {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_otas_otidvs_integration.py +0 -0
  186. {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_protests_integration.py +0 -0
  187. {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_reference_data_integration.py +0 -0
  188. {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_subawards_integration.py +0 -0
  189. {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/test_vehicles_idvs_integration.py +0 -0
  190. {tango_python-1.10.0 → tango_python-1.12.0}/tests/integration/validation.py +0 -0
  191. {tango_python-1.10.0 → tango_python-1.12.0}/tests/production/__init__.py +0 -0
  192. {tango_python-1.10.0 → tango_python-1.12.0}/tests/production/conftest.py +0 -0
  193. {tango_python-1.10.0 → tango_python-1.12.0}/tests/production/test_production_smoke.py +0 -0
  194. {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_api_parity.py +0 -0
  195. {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_contract_appeals.py +0 -0
  196. {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_dibbs_exclusions_sbir.py +0 -0
  197. {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_ebuy.py +0 -0
  198. {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_federal_register.py +0 -0
  199. {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_models.py +0 -0
  200. {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_opportunity_attachments.py +0 -0
  201. {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_organization_scope.py +0 -0
  202. {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_shape_coverage_gate.py +0 -0
  203. {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_shapes.py +0 -0
  204. {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_sled.py +0 -0
  205. {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_webhooks_cli.py +0 -0
  206. {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_webhooks_receiver.py +0 -0
  207. {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_webhooks_signing.py +0 -0
  208. {tango_python-1.10.0 → tango_python-1.12.0}/tests/test_webhooks_simulate.py +0 -0
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.12.0] - 2026-10-02
11
+
12
+ ### Added
13
+
14
+ - **`account_category` and `source_anomalies` on budget accounts** (Tango API 5.7.0). Both are in the default `BUDGET_ACCOUNTS_MINIMAL` shape and accepted in custom shapes. `account_category` is `budgetary` or `credit_financing` today. `source_anomalies` lists the problems found in the source data behind an account, and is `[]` when there are none. Each element is typed as the new `BudgetAccountSourceAnomaly` `TypedDict` (with `BudgetAccountAnomalySource` and `BudgetAccountAnomalySourceRow` for its nested `source`), and every key is optional. Treat `account_category` and an anomaly's `code` as open strings, since new values may appear.
15
+ - **`list_budget_accounts(account_category=..., account_category_in=...)`** filters by account category, exactly or against a comma-separated list.
16
+
17
+ ### Changed
18
+
19
+ - **`list_budget_accounts()` documents the API's default ordering**: the latest fiscal year first, then largest `enacted_ba` first, with accounts that have no `enacted_ba` last.
20
+ - Re-vendored `contracts/filter_shape_contract.json` (Tango API 5.7.0) and regenerated `tango/shapes/generated_overlay.py`.
21
+
22
+ ### Fixed
23
+
24
+ - **`list_idvs()`, `list_idv_awards()`, `list_idv_child_idvs()` and `list_idv_transactions()` now return the API's pagination `cursor`.** They dropped it, so `response.cursor` was always `None` and a caller following the cursor stopped after the first page.
25
+
26
+ ## [1.11.0] - 2026-09-29
27
+
28
+ ### Added
29
+
30
+ - **Award fields on opportunities and notices** (Tango API 5.5.0) — new `awarded` / `awardee_uei` filters on `list_opportunities()`, plus award leaves and an `awards(...)` expand in the opportunity and notice shapes (see `docs/API_REFERENCE.md`).
31
+
10
32
  ## [1.10.0] - 2026-09-29
11
33
 
12
34
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tango-python
3
- Version: 1.10.0
3
+ Version: 1.12.0
4
4
  Summary: Python SDK for the Tango API
5
5
  Project-URL: Homepage, https://github.com/makegov/tango-python
6
6
  Project-URL: Documentation, https://docs.makegov.com/tango-python
@@ -868,6 +868,8 @@ opportunities = client.list_opportunities(
868
868
  # Filter parameters (all optional)
869
869
  active=None,
870
870
  agency=None,
871
+ awarded=None,
872
+ awardee_uei=None,
871
873
  first_notice_date_after=None,
872
874
  first_notice_date_before=None,
873
875
  last_notice_date_after=None,
@@ -894,6 +896,8 @@ opportunities = client.list_opportunities(
894
896
  **Filter Parameters:**
895
897
  - `active` - Filter by active status (bool)
896
898
  - `agency` - Filter by agency code
899
+ - `awarded` - Whether the opportunity has an award, either posted on it or linked to it (bool). Requires Tango API 5.5.0
900
+ - `awardee_uei` - Awardee UEI, case-insensitive; OR several with `|`. Requires Tango API 5.5.0
897
901
  - `first_notice_date_after` / `first_notice_date_before` - First notice date range
898
902
  - `last_notice_date_after` / `last_notice_date_before` - Last notice date range
899
903
  - `naics` - NAICS code
@@ -947,6 +951,33 @@ for attachment in opp["attachments"]:
947
951
  - A Free-plan request that names them gets the response without them, plus an entry in `meta.upgrade_hints`.
948
952
  - There is no filter on document role.
949
953
 
954
+ **Award fields:**
955
+
956
+ Tango API 5.5.0 adds award information to opportunities.
957
+ None of it is in the SDK's default shape, so name the fields you want.
958
+
959
+ ```python
960
+ opp = client.get_opportunity(
961
+ opportunity_id,
962
+ shape=(
963
+ "opportunity_id,title,awarded,award_date,award_amount,awardee,awardee_uei,"
964
+ "award_count,solicitation_opportunity_id,"
965
+ "awards(opportunity_id,notice_id,award_number,award_date,award_amount,awardee,awardee_uei)"
966
+ ),
967
+ )
968
+
969
+ awarded = client.list_opportunities(
970
+ awardee_uei="ABCDEF123456",
971
+ shape="opportunity_id,title,award_date,awardee",
972
+ )
973
+ ```
974
+
975
+ - `award_date`, `award_amount`, `awardee` and `awardee_uei` are filled only where SAM.gov posted an award notice; elsewhere they are null. `award_amount` is the text SAM.gov published, not a number.
976
+ - SAM.gov often posts an award as its own opportunity. When that award notice references its solicitation, the award's `solicitation_opportunity_id` points back to the solicitation, and the solicitation's `awards(...)` lists up to ten of the most recent linked awards; `award_count` is the full count. An award notice that does not reference its solicitation is not linked.
977
+ - `awarded` is true when the opportunity has an award of its own or has linked awards.
978
+ - The `awarded` and `awardee_uei` filters search every opportunity, not just active ones, so pass `active=True` to narrow to open opportunities.
979
+ - Notices accept `award_date`, `award_amount`, `awardee` and `awardee_uei` in `shape` too.
980
+
950
981
  ---
951
982
 
952
983
  ## Notices
@@ -1739,6 +1770,8 @@ accounts = client.list_budget_accounts(
1739
1770
  bea_category=None,
1740
1771
  on_off_budget=None,
1741
1772
  subfunction_code=None,
1773
+ account_category=None,
1774
+ account_category_in=None,
1742
1775
  search=None,
1743
1776
  ordering=None,
1744
1777
  )
@@ -1754,8 +1787,18 @@ accounts = client.list_budget_accounts(
1754
1787
  - `bea_category` - BEA category (exact)
1755
1788
  - `on_off_budget` - On/off budget flag (exact)
1756
1789
  - `subfunction_code` - Subfunction code (exact)
1790
+ - `account_category` - Account category (exact): `budgetary` or `credit_financing` today; treat it as an open string
1791
+ - `account_category_in` - Comma-separated account categories to match any of (e.g. `"budgetary,credit_financing"`)
1757
1792
  - `search` - Full-text search over `account_title`, `agency_name`, `bureau_name`
1758
- - `ordering` - Sort field; prefix with `-` for descending
1793
+ - `ordering` - Sort field; prefix with `-` for descending. The default is the latest fiscal year first, then largest `enacted_ba` first, with accounts that have no `enacted_ba` last.
1794
+
1795
+ **Source anomalies:** the default shape includes `account_category` and `source_anomalies`. `source_anomalies` is a list of problems found in the source data behind the account, and `[]` when there are none. Each element is a `BudgetAccountSourceAnomaly` (a `TypedDict`; every key is optional and may be `None`, so read keys with `.get()`): `code`, `field`, `bound_field`, `action` (`capped` or `flagged`), `reported_value`, `served_value`, `likely_cause`, `affected_fields`, `message`, and `source` (`dataset`, `fiscal_year`, and `rows` of `fiscal_period`, `piid`, `parent_piid`, `tas`, `reporting_agency_id`, `transaction_obligated_amount`, `file_c_source`). Today `code` is one of `contract_exceeds_obligations`, `assistance_exceeds_obligations` or `contract_without_obligations`; treat it as an open string. There is no filter on anomalies.
1796
+
1797
+ ```python
1798
+ for acct in client.list_budget_accounts(fiscal_year=2025).results:
1799
+ for anomaly in acct["source_anomalies"] or []:
1800
+ print(acct["federal_account_symbol"], anomaly.get("code"), anomaly.get("action"))
1801
+ ```
1759
1802
 
1760
1803
  **Returns:** [PaginatedResponse](#paginatedresponse) of `BudgetAccount` records (see [ShapeConfig](#shapeconfig-predefined-shapes) for the default shape).
1761
1804
 
@@ -2516,7 +2559,7 @@ entity = client.get_entity("UEI_KEY", shape=ShapeConfig.ENTITIES_COMPREHENSIVE)
2516
2559
  | `FEDERAL_REGISTER_COMPREHENSIVE` | `get_federal_register_document` | The list fields plus dates, signing_date, start_page, end_page, volume, docket_ids, dockets, regulation_id_numbers, topics, correction_of, corrections, executive_order_number, presidential_document_number, proclamation_number, comment_url, regulations_dot_gov_url, raw_text_url, body_html_url (omits `full_text`) |
2517
2560
  | `EBUY_REQUESTS_MINIMAL` | `list_ebuy_requests` | rfq_id, request_type, title, schedule, sin, status, buyer_name, buyer_agency, buyer_agency_code, reference_number, issue_date, close_date, attachment_count, link_count, last_seen |
2518
2561
  | `EBUY_REQUESTS_COMPREHENSIVE` | `get_ebuy_request` | Every field, plus the `organization` and `attachments` expands |
2519
- | `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 |
2562
+ | `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, account_category, lifecycle (requested/enacted/apportioned/obligated/outlayed/unobligated), contract & assistance rollups, key ratios, next-year growth, source_anomalies |
2520
2563
  | `VEHICLE_ORDERS_MINIMAL` | `list_vehicle_orders` | key, piid, award_date, recipient(display_name,uei), total_contract_value, obligated |
2521
2564
  | `ITDASHBOARD_INVESTMENTS_MINIMAL` | `list_itdashboard_investments` | Minimal IT Dashboard investment fields |
2522
2565
  | `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.10.0"
7
+ version = "1.12.0"
8
8
  description = "Python SDK for the Tango API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -11,6 +11,9 @@ from .exceptions import (
11
11
  )
12
12
  from .models import (
13
13
  BudgetAccount,
14
+ BudgetAccountAnomalySource,
15
+ BudgetAccountAnomalySourceRow,
16
+ BudgetAccountSourceAnomaly,
14
17
  ContractAppeal,
15
18
  DibbsAward,
16
19
  DibbsRfp,
@@ -59,7 +62,7 @@ from .webhooks import (
59
62
  )
60
63
  from .webhooks.receiver import Delivery, WebhookReceiver
61
64
 
62
- __version__ = "1.10.0"
65
+ __version__ = "1.12.0"
63
66
  __all__ = [
64
67
  "TangoClient",
65
68
  "TangoAPIError",
@@ -72,6 +75,9 @@ __all__ = [
72
75
  "ResolveCandidate",
73
76
  "ResolveResult",
74
77
  "BudgetAccount",
78
+ "BudgetAccountAnomalySource",
79
+ "BudgetAccountAnomalySourceRow",
80
+ "BudgetAccountSourceAnomaly",
75
81
  "ContractAppeal",
76
82
  "FederalRegisterDocument",
77
83
  "EbuyAccess",
@@ -986,6 +986,7 @@ class TangoClient:
986
986
  next=data.get("next"),
987
987
  previous=data.get("previous"),
988
988
  results=results,
989
+ cursor=data.get("cursor"),
989
990
  page_metadata=data.get("page_metadata"),
990
991
  meta=data.get("meta"),
991
992
  )
@@ -1088,6 +1089,7 @@ class TangoClient:
1088
1089
  next=data.get("next"),
1089
1090
  previous=data.get("previous"),
1090
1091
  results=results,
1092
+ cursor=data.get("cursor"),
1091
1093
  page_metadata=data.get("page_metadata"),
1092
1094
  meta=data.get("meta"),
1093
1095
  )
@@ -1133,6 +1135,7 @@ class TangoClient:
1133
1135
  next=data.get("next"),
1134
1136
  previous=data.get("previous"),
1135
1137
  results=results,
1138
+ cursor=data.get("cursor"),
1136
1139
  page_metadata=data.get("page_metadata"),
1137
1140
  meta=data.get("meta"),
1138
1141
  )
@@ -1150,6 +1153,7 @@ class TangoClient:
1150
1153
  next=data.get("next"),
1151
1154
  previous=data.get("previous"),
1152
1155
  results=data.get("results") or [],
1156
+ cursor=data.get("cursor"),
1153
1157
  page_metadata=data.get("page_metadata"),
1154
1158
  meta=data.get("meta"),
1155
1159
  )
@@ -2346,6 +2350,9 @@ class TangoClient:
2346
2350
  search: str | None = None,
2347
2351
  set_aside: str | None = None,
2348
2352
  solicitation_number: str | None = None,
2353
+ *,
2354
+ awarded: bool | None = None,
2355
+ awardee_uei: str | None = None,
2349
2356
  ) -> PaginatedResponse:
2350
2357
  """
2351
2358
  List contract opportunities/solicitations
@@ -2358,6 +2365,8 @@ class TangoClient:
2358
2365
  flat_lists: If True, flatten arrays using indexed keys
2359
2366
  active: Filter by active status
2360
2367
  agency: Agency filter
2368
+ awarded: Filter by whether the opportunity has an award, either posted on it or linked to it from a separate award notice. Searches every opportunity rather than only active ones. Requires Tango API 5.5.0
2369
+ awardee_uei: Filter by the awardee's UEI (case-insensitive). OR several with ``|``. Searches every opportunity rather than only active ones. Requires Tango API 5.5.0
2361
2370
  first_notice_date_after: First notice date after
2362
2371
  first_notice_date_before: First notice date before
2363
2372
  last_notice_date_after: Last notice date after
@@ -2387,6 +2396,8 @@ class TangoClient:
2387
2396
  for key, val in (
2388
2397
  ("active", active),
2389
2398
  ("agency", agency),
2399
+ ("awarded", awarded),
2400
+ ("awardee_uei", awardee_uei),
2390
2401
  ("first_notice_date_after", first_notice_date_after),
2391
2402
  ("first_notice_date_before", first_notice_date_before),
2392
2403
  ("last_notice_date_after", last_notice_date_after),
@@ -4437,6 +4448,8 @@ class TangoClient:
4437
4448
  bea_category: str | None = None,
4438
4449
  on_off_budget: str | None = None,
4439
4450
  subfunction_code: str | None = None,
4451
+ account_category: str | None = None,
4452
+ account_category_in: str | None = None,
4440
4453
  # Range-numeric filters: each field also accepts ``__gte`` / ``__lte``.
4441
4454
  requested_ba: float | None = None,
4442
4455
  requested_ba_gte: float | None = None,
@@ -4543,6 +4556,11 @@ class TangoClient:
4543
4556
  bea_category: BEA category (exact).
4544
4557
  on_off_budget: On/off budget flag (exact).
4545
4558
  subfunction_code: Subfunction code (exact).
4559
+ account_category: Account category (exact), e.g. ``budgetary`` or
4560
+ ``credit_financing``. Treat the value as an open string; new
4561
+ categories may be added.
4562
+ account_category_in: Comma-separated account categories to match
4563
+ any of, e.g. ``"budgetary,credit_financing"``.
4546
4564
  requested_ba: President's-budget requested BA (exact). Also
4547
4565
  ``requested_ba_gte`` / ``requested_ba_lte`` for range queries.
4548
4566
  enacted_ba: Enacted budget authority (exact / gte / lte).
@@ -4585,7 +4603,9 @@ class TangoClient:
4585
4603
  ordering: Sort field (prefix with '-' for descending). Any of the
4586
4604
  numeric fields above is a valid ordering target — e.g.
4587
4605
  ``ordering="-unobligated_balance"`` to rank by largest
4588
- headroom first.
4606
+ headroom first. The default ordering is the latest fiscal year
4607
+ first, then largest ``enacted_ba`` first, with accounts that
4608
+ have no ``enacted_ba`` sorted last.
4589
4609
  """
4590
4610
  params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
4591
4611
  if shape is None:
@@ -4608,6 +4628,8 @@ class TangoClient:
4608
4628
  ("bea_category", bea_category),
4609
4629
  ("on_off_budget", on_off_budget),
4610
4630
  ("subfunction_code", subfunction_code),
4631
+ ("account_category", account_category),
4632
+ ("account_category__in", account_category_in),
4611
4633
  ("search", search),
4612
4634
  ("ordering", ordering),
4613
4635
  )
@@ -565,6 +565,10 @@ class Opportunity:
565
565
  Both must be named, e.g. ``attachments(name,url,doc_role,doc_role_alt)``, because ``attachments(*)`` does not include them.
566
566
  An attachment Tango has not classified omits both keys rather than returning null, so read them with ``.get()``.
567
567
  A Free-plan request that names them gets the response without them, plus an entry in ``meta.upgrade_hints``.
568
+
569
+ The award fields (``award_date``, ``award_amount``, ``awardee``, ``awardee_uei``) are filled only where SAM.gov posted an award notice, and ``award_amount`` is the text SAM.gov published, not a number.
570
+ SAM.gov often posts an award as its own opportunity; when that award notice references its solicitation, ``solicitation_opportunity_id`` points back to it, and the solicitation's ``awards(...)`` expand lists up to ten of the most recent linked awards, with ``award_count`` giving the full count.
571
+ An award notice that does not reference its solicitation is not linked.
568
572
  """
569
573
 
570
574
  opportunity_id: str
@@ -575,6 +579,15 @@ class Opportunity:
575
579
  active: bool | None = None
576
580
  naics_code: str | None = None
577
581
  psc_code: str | None = None
582
+ award_number: str | None = None
583
+ awarded: bool | None = None
584
+ award_date: date | None = None
585
+ award_amount: str | None = None
586
+ awardee: str | None = None
587
+ awardee_uei: str | None = None
588
+ award_count: int | None = None
589
+ solicitation_opportunity_id: str | None = None
590
+ awards: list[dict[str, Any]] | None = None
578
591
 
579
592
 
580
593
  @dataclass
@@ -593,6 +606,11 @@ class Notice:
593
606
  description: str | None = None
594
607
  posted_date: datetime | None = None
595
608
  naics_code: str | None = None
609
+ award_number: str | None = None
610
+ award_date: date | None = None
611
+ award_amount: str | None = None
612
+ awardee: str | None = None
613
+ awardee_uei: str | None = None
596
614
 
597
615
 
598
616
  @dataclass
@@ -768,6 +786,55 @@ class ValidateResult:
768
786
  errors: list[str] | None = None
769
787
 
770
788
 
789
+ class BudgetAccountAnomalySourceRow(TypedDict, total=False):
790
+ """One source record behind a budget-account source anomaly.
791
+
792
+ Every key is optional and any value may be ``None``.
793
+ """
794
+
795
+ fiscal_period: int | None
796
+ piid: str | None
797
+ parent_piid: str | None
798
+ tas: str | None
799
+ reporting_agency_id: str | None
800
+ transaction_obligated_amount: float | str | None
801
+ file_c_source: str | None
802
+
803
+
804
+ class BudgetAccountAnomalySource(TypedDict, total=False):
805
+ """Where a budget-account source anomaly was found.
806
+
807
+ Every key is optional and any value may be ``None``.
808
+ """
809
+
810
+ dataset: str | None
811
+ fiscal_year: int | None
812
+ rows: list[BudgetAccountAnomalySourceRow] | None
813
+
814
+
815
+ class BudgetAccountSourceAnomaly(TypedDict, total=False):
816
+ """One problem found in the source data behind a budget account.
817
+
818
+ An element of ``BudgetAccount.source_anomalies``. Every key is optional and
819
+ any value may be ``None``, so read keys with ``.get()``. ``code`` and
820
+ ``action`` are open strings: today ``code`` is one of
821
+ ``contract_exceeds_obligations``, ``assistance_exceeds_obligations`` or
822
+ ``contract_without_obligations``, and ``action`` is ``capped`` or
823
+ ``flagged``, but new values may appear.
824
+ """
825
+
826
+ code: str | None
827
+ field: str | None
828
+ bound_field: str | None
829
+ action: str | None
830
+ reported_value: float | str | None
831
+ served_value: float | str | None
832
+ likely_cause: str | None
833
+ affected_fields: list[str] | None
834
+ message: str | None
835
+ source: BudgetAccountAnomalySource | None
836
+
837
+
771
838
  @dataclass
772
839
  class BudgetAccount:
773
840
  """Schema definition for BudgetAccount (not used for instances).
@@ -787,6 +854,8 @@ class BudgetAccount:
787
854
  bea_category: str | None = None
788
855
  on_off_budget: str | None = None
789
856
  subfunction_code: str | None = None
857
+ # ``budgetary`` or ``credit_financing`` today; treat as an open string.
858
+ account_category: str | None = None
790
859
  # Lifecycle
791
860
  requested_ba: Decimal | None = None
792
861
  enacted_ba: Decimal | None = None
@@ -810,6 +879,8 @@ class BudgetAccount:
810
879
  assistance_share_of_obligated: Decimal | None = None
811
880
  assistance_share_of_obligated_capped: Decimal | None = None
812
881
  assistance_share_capped_flag: bool | None = None
882
+ # Problems found in the source data; ``[]`` when there are none.
883
+ source_anomalies: list[BudgetAccountSourceAnomaly] | None = None
813
884
  # Forward-look
814
885
  next_year_requested_ba: Decimal | None = None
815
886
  ba_growth_next_year: Decimal | None = None
@@ -1710,12 +1781,12 @@ class ShapeConfig:
1710
1781
  # Mirrors the API's BUDGET_ACCOUNT_DEFAULT_SHAPE.
1711
1782
  BUDGET_ACCOUNTS_MINIMAL: Final = (
1712
1783
  "id,federal_account_symbol,fiscal_year,agency_code,agency_name,bureau_name,"
1713
- "account_title,bea_category,on_off_budget,subfunction_code,"
1784
+ "account_title,bea_category,on_off_budget,subfunction_code,account_category,"
1714
1785
  "requested_ba,enacted_ba,apportioned,obligated_total,outlayed_total,"
1715
1786
  "unobligated_balance,contract_obligated,contract_share_of_obligated_capped,"
1716
1787
  "assistance_obligated,obligated_to_apportioned_pct_capped,"
1717
1788
  "obligated_to_enacted_pct_capped,outlayed_to_obligated_pct_capped,"
1718
- "ba_growth_next_year_pct"
1789
+ "ba_growth_next_year_pct,source_anomalies"
1719
1790
  )
1720
1791
 
1721
1792
  # Default for list_organizations()
@@ -213,6 +213,16 @@ AWARDS_SCHEMA: dict[str, FieldSchema] = {
213
213
  "transactions": FieldSchema(name="transactions", type=str, is_optional=True, is_list=False),
214
214
  }
215
215
 
216
+ AWARDS2_SCHEMA: dict[str, FieldSchema] = {
217
+ "award_amount": FieldSchema(name="award_amount", type=str, is_optional=True, is_list=False),
218
+ "award_date": FieldSchema(name="award_date", type=date, is_optional=True, is_list=False),
219
+ "award_number": FieldSchema(name="award_number", type=str, is_optional=True, is_list=False),
220
+ "awardee": FieldSchema(name="awardee", type=str, is_optional=True, is_list=False),
221
+ "awardee_uei": FieldSchema(name="awardee_uei", type=str, is_optional=True, is_list=False),
222
+ "notice_id": FieldSchema(name="notice_id", type=str, is_optional=True, is_list=False),
223
+ "opportunity_id": FieldSchema(name="opportunity_id", type=str, is_optional=True, is_list=False),
224
+ }
225
+
216
226
  BUDGET_APPROPRIATION_SCHEMA: dict[str, FieldSchema] = {
217
227
  "cgac": FieldSchema(name="cgac", type=int, is_optional=True, is_list=False),
218
228
  "fiscal_year": FieldSchema(name="fiscal_year", type=int, is_optional=True, is_list=False),
@@ -1112,6 +1122,7 @@ GENERATED_NESTED: dict[str, dict[str, FieldSchema]] = {
1112
1122
  "AwardingOffice": AWARDING_OFFICE_SCHEMA,
1113
1123
  "AwardingOffice2": AWARDING_OFFICE2_SCHEMA,
1114
1124
  "Awards": AWARDS_SCHEMA,
1125
+ "Awards2": AWARDS2_SCHEMA,
1115
1126
  "BudgetAppropriation": BUDGET_APPROPRIATION_SCHEMA,
1116
1127
  "BudgetSpending": BUDGET_SPENDING_SCHEMA,
1117
1128
  "Children": CHILDREN_SCHEMA,
@@ -1214,6 +1225,9 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
1214
1225
  ),
1215
1226
  },
1216
1227
  "BudgetAccount": {
1228
+ "account_category": FieldSchema(
1229
+ name="account_category", type=str, is_optional=True, is_list=False
1230
+ ),
1217
1231
  "account_narrative_excerpt": FieldSchema(
1218
1232
  name="account_narrative_excerpt", type=str, is_optional=True, is_list=False
1219
1233
  ),
@@ -1399,6 +1413,9 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
1399
1413
  "requested_personnel_share": FieldSchema(
1400
1414
  name="requested_personnel_share", type=str, is_optional=True, is_list=False
1401
1415
  ),
1416
+ "source_anomalies": FieldSchema(
1417
+ name="source_anomalies", type=dict, is_optional=True, is_list=True
1418
+ ),
1402
1419
  "subfunction_code": FieldSchema(
1403
1420
  name="subfunction_code", type=str, is_optional=True, is_list=False
1404
1421
  ),
@@ -2228,6 +2245,10 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
2228
2245
  is_list=True,
2229
2246
  nested_model="Attachments",
2230
2247
  ),
2248
+ "award_amount": FieldSchema(name="award_amount", type=str, is_optional=True, is_list=False),
2249
+ "award_date": FieldSchema(name="award_date", type=date, is_optional=True, is_list=False),
2250
+ "awardee": FieldSchema(name="awardee", type=str, is_optional=True, is_list=False),
2251
+ "awardee_uei": FieldSchema(name="awardee_uei", type=str, is_optional=True, is_list=False),
2231
2252
  "meta": FieldSchema(
2232
2253
  name="meta", type=dict, is_optional=True, is_list=False, nested_model="Meta"
2233
2254
  ),
@@ -2476,6 +2497,15 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
2476
2497
  is_list=False,
2477
2498
  nested_model="Attachments5",
2478
2499
  ),
2500
+ "award_amount": FieldSchema(name="award_amount", type=str, is_optional=True, is_list=False),
2501
+ "award_count": FieldSchema(name="award_count", type=int, is_optional=True, is_list=False),
2502
+ "award_date": FieldSchema(name="award_date", type=date, is_optional=True, is_list=False),
2503
+ "awarded": FieldSchema(name="awarded", type=bool, is_optional=True, is_list=False),
2504
+ "awardee": FieldSchema(name="awardee", type=str, is_optional=True, is_list=False),
2505
+ "awardee_uei": FieldSchema(name="awardee_uei", type=str, is_optional=True, is_list=False),
2506
+ "awards": FieldSchema(
2507
+ name="awards", type=dict, is_optional=True, is_list=True, nested_model="Awards2"
2508
+ ),
2479
2509
  "department": FieldSchema(
2480
2510
  name="department",
2481
2511
  type=dict,
@@ -2529,6 +2559,9 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
2529
2559
  nested_model="CodeDescription",
2530
2560
  ),
2531
2561
  "snippet": FieldSchema(name="snippet", type=str, is_optional=True, is_list=False),
2562
+ "solicitation_opportunity_id": FieldSchema(
2563
+ name="solicitation_opportunity_id", type=str, is_optional=True, is_list=False
2564
+ ),
2532
2565
  },
2533
2566
  "Organization": {
2534
2567
  "aac_code": FieldSchema(name="aac_code", type=str, is_optional=True, is_list=False),
@@ -271,6 +271,34 @@ class TestTangoClient:
271
271
  assert otas.results[0]["key"] == "OTA-1"
272
272
  assert otas.cursor == "next-page-token"
273
273
 
274
+ @pytest.mark.parametrize(
275
+ ("method", "args"),
276
+ [
277
+ ("list_idvs", ()),
278
+ ("list_idv_awards", ("IDV-1",)),
279
+ ("list_idv_child_idvs", ("IDV-1",)),
280
+ ("list_idv_transactions", ("IDV-1",)),
281
+ ],
282
+ )
283
+ @patch("tango.client.httpx.Client.request")
284
+ def test_idv_list_methods_return_cursor(self, mock_request, method, args):
285
+ mock_response = Mock()
286
+ mock_response.is_success = True
287
+ mock_response.json.return_value = {
288
+ "count": 250,
289
+ "next": "https://tango.makegov.com/api/idvs/?cursor=next-page-token",
290
+ "previous": None,
291
+ "results": [{"key": "IDV-1"}],
292
+ "cursor": "next-page-token",
293
+ }
294
+ mock_response.content = b'{"count": 250}'
295
+ mock_request.return_value = mock_response
296
+
297
+ client = TangoClient(api_key="test-key")
298
+ page = getattr(client, method)(*args, limit=1)
299
+
300
+ assert page.cursor == "next-page-token"
301
+
274
302
  @patch("tango.client.httpx.Client.request")
275
303
  def test_list_otidvs_with_default_shape(self, mock_request):
276
304
  """Test list_otidvs uses default minimal shape"""
@@ -573,6 +601,113 @@ class TestTangoClient:
573
601
  ]
574
602
  assert leaked == [], f"unexpected filter keys sent: {leaked}"
575
603
 
604
+ @patch("tango.client.httpx.Client.request")
605
+ def test_list_budget_accounts_account_category_filters(self, mock_request):
606
+ mock_response = Mock()
607
+ mock_response.is_success = True
608
+ mock_response.json.return_value = {
609
+ "count": 0,
610
+ "next": None,
611
+ "previous": None,
612
+ "results": [],
613
+ }
614
+ mock_response.content = b'{"count": 0, "results": []}'
615
+ mock_request.return_value = mock_response
616
+
617
+ client = TangoClient(api_key="test-key")
618
+ client.list_budget_accounts(
619
+ account_category="credit_financing",
620
+ account_category_in="budgetary,credit_financing",
621
+ )
622
+
623
+ params = mock_request.call_args[1]["params"]
624
+ assert params["account_category"] == "credit_financing"
625
+ assert params["account_category__in"] == "budgetary,credit_financing"
626
+
627
+ @patch("tango.client.httpx.Client.request")
628
+ def test_list_budget_accounts_default_shape_parses_source_anomalies(self, mock_request):
629
+ anomaly = {
630
+ "code": "contract_exceeds_obligations",
631
+ "field": "contract_obligated",
632
+ "bound_field": "obligated_total",
633
+ "action": "capped",
634
+ "reported_value": 1250.0,
635
+ "served_value": 1000.0,
636
+ "likely_cause": None,
637
+ "affected_fields": ["contract_obligated", "contract_share_of_obligated"],
638
+ "message": "Contract obligations exceed total obligations.",
639
+ "source": {
640
+ "dataset": "file_c",
641
+ "fiscal_year": 2025,
642
+ "rows": [
643
+ {
644
+ "fiscal_period": 12,
645
+ "piid": "ABC123",
646
+ "parent_piid": None,
647
+ "tas": "012-1234",
648
+ "reporting_agency_id": "012",
649
+ "transaction_obligated_amount": 1250.0,
650
+ "file_c_source": "award",
651
+ }
652
+ ],
653
+ },
654
+ }
655
+ mock_response = Mock()
656
+ mock_response.is_success = True
657
+ mock_response.json.return_value = {
658
+ "count": 2,
659
+ "next": None,
660
+ "previous": None,
661
+ "results": [
662
+ {
663
+ "id": 1,
664
+ "fiscal_year": 2025,
665
+ "account_category": "budgetary",
666
+ "source_anomalies": [anomaly],
667
+ },
668
+ {
669
+ "id": 2,
670
+ "fiscal_year": 2025,
671
+ "account_category": "credit_financing",
672
+ "enacted_ba": None,
673
+ "source_anomalies": [],
674
+ },
675
+ ],
676
+ }
677
+ mock_response.content = b"{}"
678
+ mock_request.return_value = mock_response
679
+
680
+ client = TangoClient(api_key="test-key")
681
+ page = client.list_budget_accounts()
682
+
683
+ shape_fields = mock_request.call_args[1]["params"]["shape"].split(",")
684
+ assert "account_category" in shape_fields
685
+ assert "source_anomalies" in shape_fields
686
+ first, second = page.results
687
+ assert first["account_category"] == "budgetary"
688
+ assert first["source_anomalies"] == [anomaly]
689
+ assert first["source_anomalies"][0]["source"]["rows"][0]["piid"] == "ABC123"
690
+ assert second["account_category"] == "credit_financing"
691
+ assert second["source_anomalies"] == []
692
+
693
+ @patch("tango.client.httpx.Client.request")
694
+ def test_get_budget_account_accepts_explicit_anomaly_shape(self, mock_request):
695
+ mock_response = Mock()
696
+ mock_response.is_success = True
697
+ mock_response.json.return_value = {
698
+ "id": 7,
699
+ "account_category": "budgetary",
700
+ "source_anomalies": [{"code": "some_future_code"}],
701
+ }
702
+ mock_response.content = b"{}"
703
+ mock_request.return_value = mock_response
704
+
705
+ client = TangoClient(api_key="test-key")
706
+ account = client.get_budget_account(7, shape="id,account_category,source_anomalies")
707
+
708
+ assert account["source_anomalies"] == [{"code": "some_future_code"}]
709
+ assert account["account_category"] == "budgetary"
710
+
576
711
 
577
712
  class TestShapeConfig:
578
713
  """Test ShapeConfig class"""