tango-python 1.4.0__tar.gz → 1.5.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 (205) hide show
  1. {tango_python-1.4.0 → tango_python-1.5.0}/CHANGELOG.md +15 -0
  2. {tango_python-1.4.0 → tango_python-1.5.0}/PKG-INFO +30 -2
  3. {tango_python-1.4.0 → tango_python-1.5.0}/README.md +29 -1
  4. {tango_python-1.4.0 → tango_python-1.5.0}/pyproject.toml +1 -1
  5. {tango_python-1.4.0 → tango_python-1.5.0}/tango/__init__.py +1 -1
  6. {tango_python-1.4.0 → tango_python-1.5.0}/tango/client.py +48 -0
  7. {tango_python-1.4.0 → tango_python-1.5.0}/tango/models.py +93 -1
  8. {tango_python-1.4.0 → tango_python-1.5.0}/tango/shapes/generated_overlay.py +2 -0
  9. {tango_python-1.4.0 → tango_python-1.5.0}/tests/test_client.py +137 -0
  10. {tango_python-1.4.0 → tango_python-1.5.0}/.github/workflows/docs-dispatch.yml +0 -0
  11. {tango_python-1.4.0 → tango_python-1.5.0}/.github/workflows/lint.yml +0 -0
  12. {tango_python-1.4.0 → tango_python-1.5.0}/.github/workflows/publish.yml +0 -0
  13. {tango_python-1.4.0 → tango_python-1.5.0}/.github/workflows/test.yml +0 -0
  14. {tango_python-1.4.0 → tango_python-1.5.0}/.gitignore +0 -0
  15. {tango_python-1.4.0 → tango_python-1.5.0}/LICENSE +0 -0
  16. {tango_python-1.4.0 → tango_python-1.5.0}/docs/API_REFERENCE.md +0 -0
  17. {tango_python-1.4.0 → tango_python-1.5.0}/docs/CLIENT.md +0 -0
  18. {tango_python-1.4.0 → tango_python-1.5.0}/docs/DYNAMIC_MODELS.md +0 -0
  19. {tango_python-1.4.0 → tango_python-1.5.0}/docs/ERRORS.md +0 -0
  20. {tango_python-1.4.0 → tango_python-1.5.0}/docs/PAGINATION.md +0 -0
  21. {tango_python-1.4.0 → tango_python-1.5.0}/docs/SHAPES.md +0 -0
  22. {tango_python-1.4.0 → tango_python-1.5.0}/docs/WEBHOOKS.md +0 -0
  23. {tango_python-1.4.0 → tango_python-1.5.0}/scripts/README.md +0 -0
  24. {tango_python-1.4.0 → tango_python-1.5.0}/scripts/check_filter_shape_conformance.py +0 -0
  25. {tango_python-1.4.0 → tango_python-1.5.0}/scripts/check_shape_coverage.py +0 -0
  26. {tango_python-1.4.0 → tango_python-1.5.0}/scripts/fetch_api_schema.py +0 -0
  27. {tango_python-1.4.0 → tango_python-1.5.0}/scripts/generate_schemas_from_api.py +0 -0
  28. {tango_python-1.4.0 → tango_python-1.5.0}/scripts/generate_shape_overlay.py +0 -0
  29. {tango_python-1.4.0 → tango_python-1.5.0}/scripts/pr_review.py +0 -0
  30. {tango_python-1.4.0 → tango_python-1.5.0}/scripts/probe_shape_types.py +0 -0
  31. {tango_python-1.4.0 → tango_python-1.5.0}/scripts/refresh_contract.py +0 -0
  32. {tango_python-1.4.0 → tango_python-1.5.0}/scripts/smoke_api_parity.py +0 -0
  33. {tango_python-1.4.0 → tango_python-1.5.0}/scripts/test_production.py +0 -0
  34. {tango_python-1.4.0 → tango_python-1.5.0}/tango/exceptions.py +0 -0
  35. {tango_python-1.4.0 → tango_python-1.5.0}/tango/shapes/__init__.py +0 -0
  36. {tango_python-1.4.0 → tango_python-1.5.0}/tango/shapes/explicit_schemas.py +0 -0
  37. {tango_python-1.4.0 → tango_python-1.5.0}/tango/shapes/factory.py +0 -0
  38. {tango_python-1.4.0 → tango_python-1.5.0}/tango/shapes/generator.py +0 -0
  39. {tango_python-1.4.0 → tango_python-1.5.0}/tango/shapes/models.py +0 -0
  40. {tango_python-1.4.0 → tango_python-1.5.0}/tango/shapes/parser.py +0 -0
  41. {tango_python-1.4.0 → tango_python-1.5.0}/tango/shapes/schema.py +0 -0
  42. {tango_python-1.4.0 → tango_python-1.5.0}/tango/shapes/types.py +0 -0
  43. {tango_python-1.4.0 → tango_python-1.5.0}/tango/webhooks/__init__.py +0 -0
  44. {tango_python-1.4.0 → tango_python-1.5.0}/tango/webhooks/cli.py +0 -0
  45. {tango_python-1.4.0 → tango_python-1.5.0}/tango/webhooks/receiver.py +0 -0
  46. {tango_python-1.4.0 → tango_python-1.5.0}/tango/webhooks/signing.py +0 -0
  47. {tango_python-1.4.0 → tango_python-1.5.0}/tango/webhooks/simulate.py +0 -0
  48. {tango_python-1.4.0 → tango_python-1.5.0}/tests/__init__.py +0 -0
  49. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestAgenciesIntegration.test_get_agency +0 -0
  50. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestAgenciesIntegration.test_list_agencies +0 -0
  51. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_field_type_validation +0 -0
  52. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestBusinessTypesIntegration.test_business_type_parsing_consistency +0 -0
  53. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestBusinessTypesIntegration.test_list_business_types +0 -0
  54. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_combined_filters_work_together +0 -0
  55. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_contract_cursor_pagination +0 -0
  56. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_contract_data_object_parsing +0 -0
  57. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_contract_field_types +0 -0
  58. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[keyword-software] +0 -0
  59. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_filter_parameter_mappings[psc_code-R425] +0 -0
  60. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_awarding_agency_filter +0 -0
  61. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_date_range_filter +0 -0
  62. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_flat +0 -0
  63. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_naics_code_filter +0 -0
  64. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[custom-key,piid,recipient(display_name),total_contract_value,award_date] +0 -0
  65. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[default-None] +0 -0
  66. {tango_python-1.4.0 → tango_python-1.5.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
  67. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_list_contracts_with_shapes[minimal-key,piid,award_date,recipient(display_name),description,total_contract_value] +0 -0
  68. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_new_expiring_filters +0 -0
  69. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_new_fiscal_year_range_filters +0 -0
  70. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_new_identifier_filters +0 -0
  71. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_search_contracts_with_filters +0 -0
  72. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_search_filters_object_with_new_parameters +0 -0
  73. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[asc-] +0 -0
  74. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestContractsIntegration.test_sort_and_order_mapped_to_ordering[desc--] +0 -0
  75. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_contracts +0 -0
  76. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_api_schema_stability_detection_entities +0 -0
  77. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_date_field_parsing_edge_cases +0 -0
  78. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_decimal_field_parsing_edge_cases +0 -0
  79. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_empty_list_responses +0 -0
  80. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_entity_parsing_with_various_address_formats +0 -0
  81. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_flattened_responses_with_flat_lists +0 -0
  82. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_list_field_parsing_consistency +0 -0
  83. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_nested_objects_with_missing_data +0 -0
  84. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_null_missing_fields_in_contracts +0 -0
  85. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEdgeCasesIntegration.test_parsing_with_minimal_shape_sparse_data +0 -0
  86. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEntitiesIntegration.test_entity_field_types +0 -0
  87. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEntitiesIntegration.test_entity_location_parsing +0 -0
  88. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEntitiesIntegration.test_entity_parsing_with_business_types +0 -0
  89. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEntitiesIntegration.test_entity_with_various_identifiers +0 -0
  90. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_budget_flows +0 -0
  91. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEntitiesIntegration.test_get_entity_by_uei +0 -0
  92. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_flat +0 -0
  93. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_search +0 -0
  94. {tango_python-1.4.0 → tango_python-1.5.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
  95. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[custom-uei,legal_business_name,cage_code] +0 -0
  96. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
  97. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestEntitiesIntegration.test_list_entities_with_shapes[with_address-uei,legal_business_name,cage_code,business_types,physical_address] +0 -0
  98. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestForecastsIntegration.test_forecast_field_types +0 -0
  99. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[custom-id,title,anticipated_award_date] +0 -0
  100. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[default-None] +0 -0
  101. {tango_python-1.4.0 → tango_python-1.5.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
  102. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestForecastsIntegration.test_list_forecasts_with_shapes[minimal-id,title,anticipated_award_date,fiscal_year,naics_code,status] +0 -0
  103. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestGrantsIntegration.test_grant_field_types +0 -0
  104. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestGrantsIntegration.test_grant_pagination +0 -0
  105. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[custom-grant_id,title,opportunity_number] +0 -0
  106. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[default-None] +0 -0
  107. {tango_python-1.4.0 → tango_python-1.5.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
  108. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestGrantsIntegration.test_list_grants_with_shapes[minimal-grant_id,opportunity_number,title,status(-),agency_code] +0 -0
  109. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestIDVsIntegration.test_get_idv_uses_default_shape +0 -0
  110. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_awards_uses_default_shape +0 -0
  111. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_child_idvs_uses_default_shape +0 -0
  112. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestIDVsIntegration.test_list_idv_transactions +0 -0
  113. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestIDVsIntegration.test_list_idvs_uses_default_shape_and_keyset_params +0 -0
  114. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_code +0 -0
  115. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_agency_name_text +0 -0
  116. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating +0 -0
  117. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_cio_rating_max +0 -0
  118. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_performance_risk +0 -0
  119. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_type_of_investment +0 -0
  120. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_filter_by_updated_time_range +0 -0
  121. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_funding_and_cio_evaluation_expansions +0 -0
  122. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_get_itdashboard_investment_by_uii +0 -0
  123. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_itdashboard_pagination +0 -0
  124. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_search +0 -0
  125. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[custom-uii,agency_name,investment_title,updated_time] +0 -0
  126. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestITDashboardIntegration.test_list_itdashboard_investments_with_shapes[default-None] +0 -0
  127. {tango_python-1.4.0 → tango_python-1.5.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
  128. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestNaicsIntegration.test_list_naics +0 -0
  129. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[custom-notice_id,title,solicitation_number] +0 -0
  130. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[default-None] +0 -0
  131. {tango_python-1.4.0 → tango_python-1.5.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
  132. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestNoticesIntegration.test_list_notices_with_shapes[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
  133. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestNoticesIntegration.test_notice_field_types +0 -0
  134. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestNoticesIntegration.test_notice_pagination +0 -0
  135. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestNoticesIntegration.test_notice_with_meta_fields +0 -0
  136. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOTAsIntegration.test_get_ota +0 -0
  137. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOTAsIntegration.test_list_otas +0 -0
  138. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOTIDVsIntegration.test_get_otidv +0 -0
  139. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOTIDVsIntegration.test_list_otidvs +0 -0
  140. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOfficesIntegration.test_get_office +0 -0
  141. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOfficesIntegration.test_list_offices +0 -0
  142. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[custom-opportunity_id,title,solicitation_number] +0 -0
  143. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[default-None] +0 -0
  144. {tango_python-1.4.0 → tango_python-1.5.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
  145. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOpportunitiesIntegration.test_list_opportunities_with_shapes[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
  146. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOpportunitiesIntegration.test_opportunity_field_types +0 -0
  147. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOrganizationsIntegration.test_get_organization +0 -0
  148. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestOrganizationsIntegration.test_list_organizations +0 -0
  149. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestProtestsIntegration.test_get_protest_by_case_id +0 -0
  150. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_filter +0 -0
  151. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[custom-case_id,title,source_system,outcome] +0 -0
  152. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[default-None] +0 -0
  153. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestProtestsIntegration.test_list_protests_with_shapes[minimal-case_id,case_number,title,source_system,outcome,filed_date] +0 -0
  154. {tango_python-1.4.0 → tango_python-1.5.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
  155. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestProtestsIntegration.test_protest_pagination +0 -0
  156. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestSubawardsIntegration.test_list_subawards +0 -0
  157. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[custom-key,piid,description] +0 -0
  158. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[minimal-key,piid,award_date,recipient(display_name),description,total_contract_value] +0 -0
  159. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestTypeHintsIntegration.test_contracts_dict_access[ultra_minimal-key,piid,recipient(display_name),total_contract_value] +0 -0
  160. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[minimal-uei,legal_business_name,cage_code,business_types] +0 -0
  161. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestTypeHintsIntegration.test_entities_dict_access[with_address-uei,legal_business_name,cage_code,business_types,physical_address] +0 -0
  162. {tango_python-1.4.0 → tango_python-1.5.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
  163. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestTypeHintsIntegration.test_notices_dict_access[minimal-notice_id,title,solicitation_number,posted_date] +0 -0
  164. {tango_python-1.4.0 → tango_python-1.5.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
  165. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestTypeHintsIntegration.test_opportunities_dict_access[minimal-opportunity_id,title,solicitation_number,response_deadline,active] +0 -0
  166. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_supports_joiner_and_flat_lists +0 -0
  167. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestVehiclesIntegration.test_get_vehicle_with_metrics_expansion +0 -0
  168. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_awardees_uses_default_shape +0 -0
  169. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicle_orders_uses_default_shape +0 -0
  170. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_uses_default_shape_and_search +0 -0
  171. {tango_python-1.4.0 → tango_python-1.5.0}/tests/cassettes/TestVehiclesIntegration.test_list_vehicles_with_ordering +0 -0
  172. {tango_python-1.4.0 → tango_python-1.5.0}/tests/conftest.py +0 -0
  173. {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/README.md +0 -0
  174. {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/__init__.py +0 -0
  175. {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/conftest.py +0 -0
  176. {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_agencies_integration.py +0 -0
  177. {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_contracts_integration.py +0 -0
  178. {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_edge_cases_integration.py +0 -0
  179. {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_entities_integration.py +0 -0
  180. {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_forecasts_integration.py +0 -0
  181. {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_grants_integration.py +0 -0
  182. {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_itdashboard_integration.py +0 -0
  183. {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_naics_integration.py +0 -0
  184. {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_notices_integration.py +0 -0
  185. {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_offices_integration.py +0 -0
  186. {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_opportunities_integration.py +0 -0
  187. {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_organizations_integration.py +0 -0
  188. {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_otas_otidvs_integration.py +0 -0
  189. {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_protests_integration.py +0 -0
  190. {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_reference_data_integration.py +0 -0
  191. {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_subawards_integration.py +0 -0
  192. {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/test_vehicles_idvs_integration.py +0 -0
  193. {tango_python-1.4.0 → tango_python-1.5.0}/tests/integration/validation.py +0 -0
  194. {tango_python-1.4.0 → tango_python-1.5.0}/tests/production/__init__.py +0 -0
  195. {tango_python-1.4.0 → tango_python-1.5.0}/tests/production/conftest.py +0 -0
  196. {tango_python-1.4.0 → tango_python-1.5.0}/tests/production/test_production_smoke.py +0 -0
  197. {tango_python-1.4.0 → tango_python-1.5.0}/tests/test_api_parity.py +0 -0
  198. {tango_python-1.4.0 → tango_python-1.5.0}/tests/test_dibbs_exclusions_sbir.py +0 -0
  199. {tango_python-1.4.0 → tango_python-1.5.0}/tests/test_models.py +0 -0
  200. {tango_python-1.4.0 → tango_python-1.5.0}/tests/test_shape_coverage_gate.py +0 -0
  201. {tango_python-1.4.0 → tango_python-1.5.0}/tests/test_shapes.py +0 -0
  202. {tango_python-1.4.0 → tango_python-1.5.0}/tests/test_webhooks_cli.py +0 -0
  203. {tango_python-1.4.0 → tango_python-1.5.0}/tests/test_webhooks_receiver.py +0 -0
  204. {tango_python-1.4.0 → tango_python-1.5.0}/tests/test_webhooks_signing.py +0 -0
  205. {tango_python-1.4.0 → tango_python-1.5.0}/tests/test_webhooks_simulate.py +0 -0
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.5.0] - 2026-08-04
11
+
12
+ ### Added
13
+ - **`relationships(type, source)` on entities.** Tango API 4.20.0 added two keys to each entry in the entity `relationships` expand: `type`, the stable relationship-type code (`prime_sub`, `parent_subsidiary`, `ultimate_parent`, `predecessor`), and `source`, where the tie came from (`sam`, `subawards`). Both are shape-selectable, so `shape="uei,relationships(type,source,uei)"` now resolves against the SDK's schema. Re-vendored the contract and regenerated the shape overlay; the coverage gate reports 0 gaps.
14
+ - `PaginatedResponse.meta` now carries the response-level `meta` block the API returns, with three accessors over it: `unresolved_agency_tokens` (agency tokens that matched nothing, per filter), `resolved_agencies` (the organization each token *did* match), and `agency_warnings` (the API's human-readable notes). Agency values resolve fuzzily, so a token can match an organization the caller did not intend and quietly scope the query to that subtree — and a token that resolves to nothing was previously dropped with no signal at all. Both cases were indistinguishable from "no such records exist". The API now reports both; until now the SDK read the envelope key-by-key and dropped `meta` on the floor, so SDK users were the one group that could not see it. `resolved_agencies` is the accessor that matters for the wrong-organization case: nothing is dropped there, so an unresolved-token check cannot detect it. All accessors return empty rather than raising when `meta` is absent (most responses) or malformed.
15
+ - A fully-unresolvable agency filter now raises `TangoValidationError` naming the offending value instead of returning an empty page. This needed no SDK change — the existing 400 handler already reads the API's `error` key — but it is new behavior for callers of `list_subawards()`, `list_opportunities()`, `list_notices()`, and `list_vehicles()`, which previously returned an empty result set. Contracts, IDVs, OTAs, and OTIDVs already behaved this way.
16
+
17
+ ### Changed
18
+ - **The `relation` value vocabulary changed upstream — match on `type` instead.** In Tango API 4.20.0 the `relation` label stopped collapsing to `affiliate` for subcontracting and corporate-succession ties and now names the partner's role: `subcontractor` / `prime`, `predecessor` / `successor`, and `descendant` (rather than `child`) on the far side of an ultimate-parent tie. `affiliate` survives only as a fallback for a type the API doesn't recognize. This affects the large majority of relationship entries.
19
+
20
+ No SDK code change is required — `relation` was and remains a `str`. But if you have application code branching on the string `affiliate`, switch it to `type`, which is stable and won't churn again. See the [entities data dictionary](https://docs.makegov.com/data-dictionary/entities/#relationships) for the full vocabulary table.
21
+
22
+ ### Notes
23
+ - `PaginatedResponse.page_metadata` is documented as always `None`: the API has never emitted a `page_metadata` key, so the field has only ever read a value that does not exist. It is retained so existing attribute access keeps working. Use `meta`.
24
+
10
25
  ## [1.4.0] - 2026-07-20
11
26
 
12
27
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tango-python
3
- Version: 1.4.0
3
+ Version: 1.5.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
@@ -191,9 +191,37 @@ contracts = client.list_contracts(
191
191
  - `expiring_gte`, `expiring_lte` - Contract expiration date range
192
192
 
193
193
  **Party Filters:**
194
- - `awarding_agency`, `funding_agency` - Agency codes
194
+ - `awarding_agency`, `funding_agency` - Agency codes, names, abbreviations, or organization UUIDs. Multi-value OR via `|`.
195
195
  - `recipient_name`, `recipient_uei` - Vendor/recipient filters
196
196
 
197
+ ### Checking how agency filters resolved
198
+
199
+ Agency values are resolved fuzzily, so a token can match an organization you did not
200
+ intend — which silently scopes the query to that organization's subtree. A short result
201
+ set is then indistinguishable from "no such records exist". Responses expose what
202
+ actually happened:
203
+
204
+ ```python
205
+ response = client.list_contracts(awarding_agency="HUD|HUDD")
206
+
207
+ # Tokens that matched nothing and were ignored.
208
+ if response.unresolved_agency_tokens:
209
+ raise SystemExit(f"dropped: {response.unresolved_agency_tokens}")
210
+ # {'awarding_agency': ['HUDD']}
211
+
212
+ # What the tokens that DID match resolved to — the only way to catch a
213
+ # plausible-but-wrong match, where nothing was dropped at all.
214
+ for org in response.resolved_agencies.get("awarding_agency", []):
215
+ print(org["name"], org["cgac"])
216
+ # Department of Housing and Urban Development 086
217
+
218
+ for warning in response.agency_warnings:
219
+ print(warning)
220
+ ```
221
+
222
+ If *every* token for a filter fails to resolve, the API returns `400` and the SDK raises
223
+ `TangoValidationError` naming the offending value, rather than an empty page.
224
+
197
225
  **Classification:**
198
226
  - `naics_code`, `psc_code` - Industry/product codes
199
227
  - `set_aside_type` - Set-aside type
@@ -137,9 +137,37 @@ contracts = client.list_contracts(
137
137
  - `expiring_gte`, `expiring_lte` - Contract expiration date range
138
138
 
139
139
  **Party Filters:**
140
- - `awarding_agency`, `funding_agency` - Agency codes
140
+ - `awarding_agency`, `funding_agency` - Agency codes, names, abbreviations, or organization UUIDs. Multi-value OR via `|`.
141
141
  - `recipient_name`, `recipient_uei` - Vendor/recipient filters
142
142
 
143
+ ### Checking how agency filters resolved
144
+
145
+ Agency values are resolved fuzzily, so a token can match an organization you did not
146
+ intend — which silently scopes the query to that organization's subtree. A short result
147
+ set is then indistinguishable from "no such records exist". Responses expose what
148
+ actually happened:
149
+
150
+ ```python
151
+ response = client.list_contracts(awarding_agency="HUD|HUDD")
152
+
153
+ # Tokens that matched nothing and were ignored.
154
+ if response.unresolved_agency_tokens:
155
+ raise SystemExit(f"dropped: {response.unresolved_agency_tokens}")
156
+ # {'awarding_agency': ['HUDD']}
157
+
158
+ # What the tokens that DID match resolved to — the only way to catch a
159
+ # plausible-but-wrong match, where nothing was dropped at all.
160
+ for org in response.resolved_agencies.get("awarding_agency", []):
161
+ print(org["name"], org["cgac"])
162
+ # Department of Housing and Urban Development 086
163
+
164
+ for warning in response.agency_warnings:
165
+ print(warning)
166
+ ```
167
+
168
+ If *every* token for a filter fails to resolve, the API returns `400` and the SDK raises
169
+ `TangoValidationError` naming the offending value, rather than an empty page.
170
+
143
171
  **Classification:**
144
172
  - `naics_code`, `psc_code` - Industry/product codes
145
173
  - `set_aside_type` - Set-aside type
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "tango-python"
7
- version = "1.4.0"
7
+ version = "1.5.0"
8
8
  description = "Python SDK for the Tango API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -50,7 +50,7 @@ from .webhooks import (
50
50
  )
51
51
  from .webhooks.receiver import Delivery, WebhookReceiver
52
52
 
53
- __version__ = "1.4.0"
53
+ __version__ = "1.5.0"
54
54
  __all__ = [
55
55
  "TangoClient",
56
56
  "TangoAPIError",
@@ -491,6 +491,7 @@ class TangoClient:
491
491
  for ag in (self._parse_agency(agency) for agency in data["results"])
492
492
  if ag is not None
493
493
  ],
494
+ meta=data.get("meta"),
494
495
  )
495
496
 
496
497
  def get_agency(self, code: str) -> Agency:
@@ -521,6 +522,7 @@ class TangoClient:
521
522
  next=data.get("next"),
522
523
  previous=data.get("previous"),
523
524
  results=data.get("results", []),
525
+ meta=data.get("meta"),
524
526
  )
525
527
 
526
528
  def get_office(self, code: str) -> dict[str, Any]:
@@ -573,6 +575,7 @@ class TangoClient:
573
575
  next=data.get("next"),
574
576
  previous=data.get("previous"),
575
577
  results=results,
578
+ meta=data.get("meta"),
576
579
  )
577
580
 
578
581
  def get_organization(
@@ -785,6 +788,7 @@ class TangoClient:
785
788
  previous=data.get("previous"),
786
789
  results=results,
787
790
  cursor=data.get("cursor"),
791
+ meta=data.get("meta"),
788
792
  )
789
793
 
790
794
  def get_contract(
@@ -846,6 +850,7 @@ class TangoClient:
846
850
  previous=data.get("previous"),
847
851
  results=results,
848
852
  cursor=data.get("cursor"),
853
+ meta=data.get("meta"),
849
854
  )
850
855
 
851
856
  def get_contract_transactions(
@@ -868,6 +873,7 @@ class TangoClient:
868
873
  previous=data.get("previous"),
869
874
  results=data.get("results") or [],
870
875
  cursor=data.get("cursor"),
876
+ meta=data.get("meta"),
871
877
  )
872
878
 
873
879
  # ============================================================================
@@ -971,6 +977,7 @@ class TangoClient:
971
977
  previous=data.get("previous"),
972
978
  results=results,
973
979
  page_metadata=data.get("page_metadata"),
980
+ meta=data.get("meta"),
974
981
  )
975
982
 
976
983
  def get_idv(
@@ -1072,6 +1079,7 @@ class TangoClient:
1072
1079
  previous=data.get("previous"),
1073
1080
  results=results,
1074
1081
  page_metadata=data.get("page_metadata"),
1082
+ meta=data.get("meta"),
1075
1083
  )
1076
1084
 
1077
1085
  def list_idv_child_idvs(
@@ -1116,6 +1124,7 @@ class TangoClient:
1116
1124
  previous=data.get("previous"),
1117
1125
  results=results,
1118
1126
  page_metadata=data.get("page_metadata"),
1127
+ meta=data.get("meta"),
1119
1128
  )
1120
1129
 
1121
1130
  def list_idv_transactions(
@@ -1132,6 +1141,7 @@ class TangoClient:
1132
1141
  previous=data.get("previous"),
1133
1142
  results=data.get("results") or [],
1134
1143
  page_metadata=data.get("page_metadata"),
1144
+ meta=data.get("meta"),
1135
1145
  )
1136
1146
 
1137
1147
  def list_otas(
@@ -1214,6 +1224,7 @@ class TangoClient:
1214
1224
  results=results,
1215
1225
  cursor=data.get("cursor"),
1216
1226
  page_metadata=data.get("page_metadata"),
1227
+ meta=data.get("meta"),
1217
1228
  )
1218
1229
 
1219
1230
  def get_ota(
@@ -1319,6 +1330,7 @@ class TangoClient:
1319
1330
  results=results,
1320
1331
  cursor=data.get("cursor"),
1321
1332
  page_metadata=data.get("page_metadata"),
1333
+ meta=data.get("meta"),
1322
1334
  )
1323
1335
 
1324
1336
  def get_otidv(
@@ -1395,6 +1407,7 @@ class TangoClient:
1395
1407
  results=results,
1396
1408
  cursor=data.get("cursor"),
1397
1409
  page_metadata=data.get("page_metadata"),
1410
+ meta=data.get("meta"),
1398
1411
  )
1399
1412
 
1400
1413
  def list_subawards(
@@ -1449,6 +1462,7 @@ class TangoClient:
1449
1462
  next=data.get("next"),
1450
1463
  previous=data.get("previous"),
1451
1464
  results=results,
1465
+ meta=data.get("meta"),
1452
1466
  )
1453
1467
 
1454
1468
  def get_subaward(
@@ -1533,6 +1547,7 @@ class TangoClient:
1533
1547
  next=data.get("next"),
1534
1548
  previous=data.get("previous"),
1535
1549
  results=results,
1550
+ meta=data.get("meta"),
1536
1551
  )
1537
1552
 
1538
1553
  def get_gsa_elibrary_contract(
@@ -1639,6 +1654,7 @@ class TangoClient:
1639
1654
  next=data.get("next"),
1640
1655
  previous=data.get("previous"),
1641
1656
  results=results,
1657
+ meta=data.get("meta"),
1642
1658
  )
1643
1659
 
1644
1660
  def get_itdashboard_investment(
@@ -1808,6 +1824,7 @@ class TangoClient:
1808
1824
  next=data.get("next"),
1809
1825
  previous=data.get("previous"),
1810
1826
  results=results,
1827
+ meta=data.get("meta"),
1811
1828
  )
1812
1829
 
1813
1830
  def get_vehicle(
@@ -1889,6 +1906,7 @@ class TangoClient:
1889
1906
  next=data.get("next"),
1890
1907
  previous=data.get("previous"),
1891
1908
  results=results,
1909
+ meta=data.get("meta"),
1892
1910
  )
1893
1911
 
1894
1912
  def list_vehicle_orders(
@@ -1937,6 +1955,7 @@ class TangoClient:
1937
1955
  next=data.get("next"),
1938
1956
  previous=data.get("previous"),
1939
1957
  results=results,
1958
+ meta=data.get("meta"),
1940
1959
  )
1941
1960
 
1942
1961
  # Business Types endpoints
@@ -1971,6 +1990,7 @@ class TangoClient:
1971
1990
  next=data.get("next"),
1972
1991
  previous=data.get("previous"),
1973
1992
  results=results,
1993
+ meta=data.get("meta"),
1974
1994
  )
1975
1995
 
1976
1996
  def list_naics(
@@ -2022,6 +2042,7 @@ class TangoClient:
2022
2042
  next=data.get("next"),
2023
2043
  previous=data.get("previous"),
2024
2044
  results=data.get("results", []),
2045
+ meta=data.get("meta"),
2025
2046
  )
2026
2047
 
2027
2048
  # Entity endpoints
@@ -2108,6 +2129,7 @@ class TangoClient:
2108
2129
  next=data.get("next"),
2109
2130
  previous=data.get("previous"),
2110
2131
  results=results,
2132
+ meta=data.get("meta"),
2111
2133
  )
2112
2134
 
2113
2135
  def get_entity(
@@ -2164,6 +2186,7 @@ class TangoClient:
2164
2186
  next=data.get("next"),
2165
2187
  previous=data.get("previous"),
2166
2188
  results=list(data.get("results") or []),
2189
+ meta=data.get("meta"),
2167
2190
  )
2168
2191
 
2169
2192
  # Forecast endpoints
@@ -2256,6 +2279,7 @@ class TangoClient:
2256
2279
  next=data.get("next"),
2257
2280
  previous=data.get("previous"),
2258
2281
  results=results,
2282
+ meta=data.get("meta"),
2259
2283
  )
2260
2284
 
2261
2285
  def get_forecast(
@@ -2379,6 +2403,7 @@ class TangoClient:
2379
2403
  next=data.get("next"),
2380
2404
  previous=data.get("previous"),
2381
2405
  results=results,
2406
+ meta=data.get("meta"),
2382
2407
  )
2383
2408
 
2384
2409
  def get_opportunity(
@@ -2493,6 +2518,7 @@ class TangoClient:
2493
2518
  next=data.get("next"),
2494
2519
  previous=data.get("previous"),
2495
2520
  results=results,
2521
+ meta=data.get("meta"),
2496
2522
  )
2497
2523
 
2498
2524
  def get_notice(
@@ -2614,6 +2640,7 @@ class TangoClient:
2614
2640
  next=data.get("next"),
2615
2641
  previous=data.get("previous"),
2616
2642
  results=results,
2643
+ meta=data.get("meta"),
2617
2644
  )
2618
2645
 
2619
2646
  def get_protest(
@@ -2750,6 +2777,7 @@ class TangoClient:
2750
2777
  next=data.get("next"),
2751
2778
  previous=data.get("previous"),
2752
2779
  results=results,
2780
+ meta=data.get("meta"),
2753
2781
  )
2754
2782
 
2755
2783
  def get_dibbs_rfq(
@@ -2869,6 +2897,7 @@ class TangoClient:
2869
2897
  next=data.get("next"),
2870
2898
  previous=data.get("previous"),
2871
2899
  results=results,
2900
+ meta=data.get("meta"),
2872
2901
  )
2873
2902
 
2874
2903
  def get_dibbs_rfp(
@@ -3006,6 +3035,7 @@ class TangoClient:
3006
3035
  next=data.get("next"),
3007
3036
  previous=data.get("previous"),
3008
3037
  results=results,
3038
+ meta=data.get("meta"),
3009
3039
  )
3010
3040
 
3011
3041
  def get_dibbs_award(
@@ -3151,6 +3181,7 @@ class TangoClient:
3151
3181
  next=data.get("next"),
3152
3182
  previous=data.get("previous"),
3153
3183
  results=results,
3184
+ meta=data.get("meta"),
3154
3185
  )
3155
3186
 
3156
3187
  def get_exclusion(
@@ -3278,6 +3309,7 @@ class TangoClient:
3278
3309
  next=data.get("next"),
3279
3310
  previous=data.get("previous"),
3280
3311
  results=results,
3312
+ meta=data.get("meta"),
3281
3313
  )
3282
3314
 
3283
3315
  def get_sbir_topic(
@@ -3398,6 +3430,7 @@ class TangoClient:
3398
3430
  next=data.get("next"),
3399
3431
  previous=data.get("previous"),
3400
3432
  results=results,
3433
+ meta=data.get("meta"),
3401
3434
  )
3402
3435
 
3403
3436
  def get_sbir_solicitation(
@@ -3772,6 +3805,7 @@ class TangoClient:
3772
3805
  next=data.get("next"),
3773
3806
  previous=data.get("previous"),
3774
3807
  results=results,
3808
+ meta=data.get("meta"),
3775
3809
  )
3776
3810
 
3777
3811
  def get_budget_account(
@@ -3823,6 +3857,7 @@ class TangoClient:
3823
3857
  next=data.get("next"),
3824
3858
  previous=data.get("previous"),
3825
3859
  results=data.get("results") or [],
3860
+ meta=data.get("meta"),
3826
3861
  )
3827
3862
 
3828
3863
  def get_budget_account_recipients(
@@ -3850,6 +3885,7 @@ class TangoClient:
3850
3885
  next=data.get("next"),
3851
3886
  previous=data.get("previous"),
3852
3887
  results=data.get("results") or [],
3888
+ meta=data.get("meta"),
3853
3889
  )
3854
3890
 
3855
3891
  # Grant endpoints
@@ -3943,6 +3979,7 @@ class TangoClient:
3943
3979
  next=data.get("next"),
3944
3980
  previous=data.get("previous"),
3945
3981
  results=results,
3982
+ meta=data.get("meta"),
3946
3983
  )
3947
3984
 
3948
3985
  def get_grant(
@@ -4035,6 +4072,7 @@ class TangoClient:
4035
4072
  next=data.get("next"),
4036
4073
  previous=data.get("previous"),
4037
4074
  results=results,
4075
+ meta=data.get("meta"),
4038
4076
  )
4039
4077
 
4040
4078
  def create_webhook_endpoint(
@@ -4219,6 +4257,7 @@ class TangoClient:
4219
4257
  next=data.get("next"),
4220
4258
  previous=data.get("previous"),
4221
4259
  results=results,
4260
+ meta=data.get("meta"),
4222
4261
  )
4223
4262
 
4224
4263
  def get_webhook_alert(self, alert_id: str) -> WebhookAlert:
@@ -4433,6 +4472,7 @@ class TangoClient:
4433
4472
  next=data.get("next"),
4434
4473
  previous=data.get("previous"),
4435
4474
  results=list(data.get("results") or []),
4475
+ meta=data.get("meta"),
4436
4476
  )
4437
4477
 
4438
4478
  def get_department(self, code: str) -> dict[str, Any]:
@@ -4470,6 +4510,7 @@ class TangoClient:
4470
4510
  next=data.get("next"),
4471
4511
  previous=data.get("previous"),
4472
4512
  results=list(data.get("results") or []),
4513
+ meta=data.get("meta"),
4473
4514
  )
4474
4515
 
4475
4516
  def get_psc(
@@ -4570,6 +4611,7 @@ class TangoClient:
4570
4611
  next=data.get("next"),
4571
4612
  previous=data.get("previous"),
4572
4613
  results=list(data.get("results") or []),
4614
+ meta=data.get("meta"),
4573
4615
  )
4574
4616
 
4575
4617
  def get_assistance_listing(
@@ -4616,6 +4658,7 @@ class TangoClient:
4616
4658
  next=data.get("next"),
4617
4659
  previous=data.get("previous"),
4618
4660
  results=list(data.get("results") or []),
4661
+ meta=data.get("meta"),
4619
4662
  )
4620
4663
 
4621
4664
  def get_mas_sin(
@@ -4685,6 +4728,7 @@ class TangoClient:
4685
4728
  results=results,
4686
4729
  cursor=data.get("cursor"),
4687
4730
  page_metadata=data.get("page_metadata"),
4731
+ meta=data.get("meta"),
4688
4732
  )
4689
4733
 
4690
4734
  def list_entity_contracts(
@@ -4860,6 +4904,7 @@ class TangoClient:
4860
4904
  next=data.get("next"),
4861
4905
  previous=data.get("previous"),
4862
4906
  results=results,
4907
+ meta=data.get("meta"),
4863
4908
  )
4864
4909
 
4865
4910
  def list_entity_lcats(
@@ -4890,6 +4935,7 @@ class TangoClient:
4890
4935
  next=data.get("next"),
4891
4936
  previous=data.get("previous"),
4892
4937
  results=list(data.get("results") or []),
4938
+ meta=data.get("meta"),
4893
4939
  )
4894
4940
 
4895
4941
  def get_entity_metrics(self, uei: str, months: int, period_grouping: str) -> dict[str, Any]:
@@ -4930,6 +4976,7 @@ class TangoClient:
4930
4976
  next=data.get("next"),
4931
4977
  previous=data.get("previous"),
4932
4978
  results=list(data.get("results") or []),
4979
+ meta=data.get("meta"),
4933
4980
  )
4934
4981
 
4935
4982
  # ============================================================================
@@ -4987,6 +5034,7 @@ class TangoClient:
4987
5034
  results=results,
4988
5035
  cursor=data.get("cursor"),
4989
5036
  page_metadata=data.get("page_metadata"),
5037
+ meta=data.get("meta"),
4990
5038
  )
4991
5039
 
4992
5040
  def list_agency_awarding_contracts(
@@ -1044,7 +1044,15 @@ class PaginatedResponse[T]:
1044
1044
  previous: URL for the previous page of results (None if first page)
1045
1045
  results: List of result items (type depends on shape parameter)
1046
1046
  cursor: Cursor token for cursor-based pagination (None if not available)
1047
- page_metadata: Optional metadata about the current page
1047
+ meta: Response-level metadata the API attached to this page, when present.
1048
+ Currently carries agency-filter diagnostics: ``resolved_filters`` maps
1049
+ each agency filter to the organizations its ``|``-separated tokens
1050
+ resolved to (or ``None``), and ``warnings`` lists human-readable notes
1051
+ about tokens that were dropped or matched loosely. See
1052
+ :meth:`agency_warnings` and :meth:`unresolved_agency_tokens`.
1053
+ page_metadata: Always ``None`` — the API has never emitted a
1054
+ ``page_metadata`` key. Retained so existing attribute access keeps
1055
+ working; use ``meta`` instead.
1048
1056
 
1049
1057
  Examples:
1050
1058
  >>> from tango import TangoClient, ShapeConfig
@@ -1063,8 +1071,92 @@ class PaginatedResponse[T]:
1063
1071
  previous: str | None
1064
1072
  results: list[T]
1065
1073
  cursor: str | None = None
1074
+ meta: dict[str, Any] | None = None
1066
1075
  page_metadata: dict[str, Any] | None = None
1067
1076
 
1077
+ @property
1078
+ def agency_warnings(self) -> list[str]:
1079
+ """Warnings the API raised about agency filters on this request.
1080
+
1081
+ Empty when every supplied agency token resolved cleanly. A non-empty list
1082
+ means part of the filter did not apply, so a small or empty ``results`` is
1083
+ not evidence that no such records exist.
1084
+
1085
+ Examples:
1086
+ >>> response = client.list_contracts(awarding_agency="GSA|NOTANAGENCY")
1087
+ >>> for warning in response.agency_warnings:
1088
+ ... print(warning)
1089
+ Agency filter 'awarding_agency': 'NOTANAGENCY' did not match any organization and was ignored.
1090
+ """
1091
+ if not self.meta:
1092
+ return []
1093
+ warnings = self.meta.get("warnings")
1094
+ return list(warnings) if isinstance(warnings, list) else []
1095
+
1096
+ @property
1097
+ def unresolved_agency_tokens(self) -> dict[str, list[str]]:
1098
+ """Agency tokens that matched no organization, keyed by filter name.
1099
+
1100
+ Empty when everything resolved. Use this to fail loudly in a pipeline
1101
+ rather than treating a silently-narrowed result set as an answer.
1102
+
1103
+ Examples:
1104
+ >>> response = client.list_contracts(awarding_agency="GSA|NOTANAGENCY")
1105
+ >>> response.unresolved_agency_tokens
1106
+ {'awarding_agency': ['NOTANAGENCY']}
1107
+ """
1108
+ if not self.meta:
1109
+ return {}
1110
+ resolved = self.meta.get("resolved_filters")
1111
+ if not isinstance(resolved, dict):
1112
+ return {}
1113
+ dropped: dict[str, list[str]] = {}
1114
+ for filter_name, entries in resolved.items():
1115
+ if not isinstance(entries, list):
1116
+ continue
1117
+ tokens = [
1118
+ entry["token"]
1119
+ for entry in entries
1120
+ if isinstance(entry, dict)
1121
+ and entry.get("resolved") is None
1122
+ and entry.get("token") is not None
1123
+ ]
1124
+ if tokens:
1125
+ dropped[filter_name] = tokens
1126
+ return dropped
1127
+
1128
+ @property
1129
+ def resolved_agencies(self) -> dict[str, list[dict[str, Any]]]:
1130
+ """What each agency token actually resolved to, keyed by filter name.
1131
+
1132
+ Agency resolution is fuzzy, so a token can match an organization the caller
1133
+ did not intend and scope the query to that subtree instead. Checking the
1134
+ resolved ``name`` is the only way to catch that from the client side — an
1135
+ unresolved-token check cannot, because nothing was dropped.
1136
+
1137
+ Examples:
1138
+ >>> response = client.list_contracts(awarding_agency="HUD")
1139
+ >>> [org["name"] for org in response.resolved_agencies["awarding_agency"]]
1140
+ ['Department of Housing and Urban Development']
1141
+ """
1142
+ if not self.meta:
1143
+ return {}
1144
+ resolved = self.meta.get("resolved_filters")
1145
+ if not isinstance(resolved, dict):
1146
+ return {}
1147
+ matched: dict[str, list[dict[str, Any]]] = {}
1148
+ for filter_name, entries in resolved.items():
1149
+ if not isinstance(entries, list):
1150
+ continue
1151
+ orgs = [
1152
+ entry["resolved"]
1153
+ for entry in entries
1154
+ if isinstance(entry, dict) and isinstance(entry.get("resolved"), dict)
1155
+ ]
1156
+ if orgs:
1157
+ matched[filter_name] = orgs
1158
+ return matched
1159
+
1068
1160
 
1069
1161
  class ShapeConfig:
1070
1162
  """Predefined response shape configurations used as defaults for API methods
@@ -716,6 +716,8 @@ PRIMARY_CONTACT_SCHEMA: dict[str, FieldSchema] = {
716
716
  RELATIONSHIPS_SCHEMA: dict[str, FieldSchema] = {
717
717
  "display_name": FieldSchema(name="display_name", type=str, is_optional=True, is_list=False),
718
718
  "relation": FieldSchema(name="relation", type=str, is_optional=True, is_list=False),
719
+ "source": FieldSchema(name="source", type=str, is_optional=True, is_list=False),
720
+ "type": FieldSchema(name="type", type=str, is_optional=True, is_list=False),
719
721
  "uei": FieldSchema(name="uei", type=str, is_optional=True, is_list=False),
720
722
  }
721
723