usaspending-orm 0.8.0__tar.gz → 0.9.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 (257) hide show
  1. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/CHANGELOG.md +90 -7
  2. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/PKG-INFO +68 -1
  3. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/README.md +67 -0
  4. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/api-docs-links.md +19 -1
  5. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/pyproject.toml +1 -1
  6. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/__init__.py +2 -0
  7. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/award.py +5 -5
  8. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/award_types.py +24 -1
  9. usaspending_orm-0.9.0/src/usaspending/models/transaction.py +582 -0
  10. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/__init__.py +2 -0
  11. usaspending_orm-0.8.0/src/usaspending/queries/transactions_search.py → usaspending_orm-0.9.0/src/usaspending/queries/award_transactions_search.py +24 -12
  12. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/awards_search.py +6 -31
  13. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/query_builder.py +107 -15
  14. usaspending_orm-0.9.0/src/usaspending/queries/transactions_search.py +239 -0
  15. usaspending_orm-0.9.0/src/usaspending/resources/transactions_resource.py +63 -0
  16. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/golden/award_contract.json +1 -1
  17. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/golden/award_grant.json +1 -1
  18. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/golden/award_idv.json +1 -1
  19. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/golden/award_loan.json +1 -1
  20. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/golden/award_parent_of_contract.json +1 -1
  21. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/golden/idv_child_award_first_result.json +1 -1
  22. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/golden/search_contract_first_result.json +1 -1
  23. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/public_api_surface.json +83 -5
  24. usaspending_orm-0.9.0/tests/fixtures/spending_by_transaction.json +214 -0
  25. usaspending_orm-0.9.0/tests/fixtures/spending_by_transaction_count.json +10 -0
  26. usaspending_orm-0.9.0/tests/fixtures/spending_by_transaction_mixed.json +210 -0
  27. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_transaction.py +42 -1
  28. usaspending_orm-0.9.0/tests/models/test_transaction_search_projection.py +456 -0
  29. usaspending_orm-0.8.0/tests/queries/test_transactions_search.py → usaspending_orm-0.9.0/tests/queries/test_award_transactions_search.py +54 -37
  30. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_count_respects_limits.py +5 -5
  31. usaspending_orm-0.9.0/tests/queries/test_transactions_search.py +504 -0
  32. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/resources/test_transactions_resource.py +22 -3
  33. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/test_client_transactions.py +1 -1
  34. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/test_integration.py +52 -0
  35. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/test_readme_examples.py +1 -1
  36. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/uv.lock +1 -1
  37. usaspending_orm-0.8.0/src/usaspending/models/transaction.py +0 -150
  38. usaspending_orm-0.8.0/src/usaspending/resources/transactions_resource.py +0 -39
  39. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/.gitignore +0 -0
  40. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/LICENSE +0 -0
  41. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/cli/__init__.py +0 -0
  42. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/cli/download_award.py +0 -0
  43. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/client.py +0 -0
  44. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/config.py +0 -0
  45. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/download/__init__.py +0 -0
  46. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/download/job.py +0 -0
  47. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/download/manager.py +0 -0
  48. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/exceptions.py +0 -0
  49. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/logging_config.py +0 -0
  50. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/__init__.py +0 -0
  51. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/agency.py +0 -0
  52. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/award_account.py +0 -0
  53. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/award_factory.py +0 -0
  54. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/award_identifier.py +0 -0
  55. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/base_model.py +0 -0
  56. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/contract.py +0 -0
  57. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/district_spending.py +0 -0
  58. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/download.py +0 -0
  59. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/federal_account.py +0 -0
  60. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/funding.py +0 -0
  61. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/grant.py +0 -0
  62. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/idv.py +0 -0
  63. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/lazy_record.py +0 -0
  64. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/loan.py +0 -0
  65. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/location.py +0 -0
  66. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/period_of_performance.py +0 -0
  67. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/procurement_award.py +0 -0
  68. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/recipient.py +0 -0
  69. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/recipient_business_categories.py +0 -0
  70. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/recipient_spending.py +0 -0
  71. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/spending.py +0 -0
  72. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/state_spending.py +0 -0
  73. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/subaward.py +0 -0
  74. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/subtier_agency.py +0 -0
  75. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/models/treasury_account_symbol.py +0 -0
  76. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/agencies_search.py +0 -0
  77. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/agency_award_summary.py +0 -0
  78. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/agency_query.py +0 -0
  79. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/award_accounts_query.py +0 -0
  80. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/award_query.py +0 -0
  81. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/awarding_agencies_search.py +0 -0
  82. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/base_query.py +0 -0
  83. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/client_side_query_builder.py +0 -0
  84. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/federal_accounts_query.py +0 -0
  85. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/filter_tree_query.py +0 -0
  86. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/filters.py +0 -0
  87. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/funding_agencies_search.py +0 -0
  88. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/funding_search.py +0 -0
  89. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/idv_child_awards.py +0 -0
  90. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/mixins.py +0 -0
  91. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/recipient_query.py +0 -0
  92. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/recipients_search.py +0 -0
  93. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/single_resource_base.py +0 -0
  94. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/spending_search.py +0 -0
  95. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/sub_agency_query.py +0 -0
  96. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/subawards_search.py +0 -0
  97. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/tas_agencies_query.py +0 -0
  98. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/queries/tas_codes_query.py +0 -0
  99. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/resources/__init__.py +0 -0
  100. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/resources/agency_resource.py +0 -0
  101. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/resources/award_accounts_resource.py +0 -0
  102. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/resources/award_resource.py +0 -0
  103. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/resources/base_resource.py +0 -0
  104. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/resources/download_resource.py +0 -0
  105. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/resources/funding_resource.py +0 -0
  106. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/resources/recipients_resource.py +0 -0
  107. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/resources/spending_resource.py +0 -0
  108. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/resources/subawards_resource.py +0 -0
  109. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/resources/tas_resource.py +0 -0
  110. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/utils/__init__.py +0 -0
  111. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/utils/dates.py +0 -0
  112. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/utils/numbers.py +0 -0
  113. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/utils/rate_limit.py +0 -0
  114. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/utils/retry.py +0 -0
  115. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/utils/special_cases.yaml +0 -0
  116. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/utils/textcase.py +0 -0
  117. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/src/usaspending/utils/validations.py +0 -0
  118. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/__init__.py +0 -0
  119. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/conftest.py +0 -0
  120. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/agency.json +0 -0
  121. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/agency_autocomplete.json +0 -0
  122. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/agency_award_summary.json +0 -0
  123. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/agency_subagencies.json +0 -0
  124. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/awards/accounts.json +0 -0
  125. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/awards/award_funding_grant.json +0 -0
  126. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/awards/contract.json +0 -0
  127. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/awards/grant.json +0 -0
  128. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/awards/idv.json +0 -0
  129. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/awards/idv_awards.json +0 -0
  130. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/awards/loan.json +0 -0
  131. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/awards/search_results_contracts.json +0 -0
  132. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/awards/search_results_grants.json +0 -0
  133. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/awards/search_results_idvs.json +0 -0
  134. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/awards/search_results_subawards.json +0 -0
  135. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/awards/transactions.json +0 -0
  136. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/download_assistance.json +0 -0
  137. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/download_search.json +0 -0
  138. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/download_status.json +0 -0
  139. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/golden/agency.json +0 -0
  140. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/golden/award_account_first_result.json +0 -0
  141. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/golden/award_contract_awarding_agency.json +0 -0
  142. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/golden/award_contract_period_of_performance.json +0 -0
  143. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/golden/award_contract_place_of_performance.json +0 -0
  144. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/golden/award_contract_recipient.json +0 -0
  145. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/golden/funding_first_result.json +0 -0
  146. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/golden/recipient.json +0 -0
  147. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/golden/search_subaward_first_result.json +0 -0
  148. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/golden/spending_by_recipient_first_result.json +0 -0
  149. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/golden/spending_by_state_first_result.json +0 -0
  150. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/golden/transaction_first_result.json +0 -0
  151. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/recipient_university.json +0 -0
  152. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/recipients_search.json +0 -0
  153. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/spending_by_award_contracts.json +0 -0
  154. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/spending_by_district.json +0 -0
  155. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/spending_by_recipient.json +0 -0
  156. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/spending_by_state.json +0 -0
  157. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/tas_agencies.json +0 -0
  158. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/tas_codes.json +0 -0
  159. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/tas_federal_accounts.json +0 -0
  160. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/top_recipients_params.json +0 -0
  161. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/fixtures/top_recipients_response.json +0 -0
  162. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/mocks/README.md +0 -0
  163. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/mocks/__init__.py +0 -0
  164. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/mocks/mock_client.py +0 -0
  165. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/mocks/response_builder.py +0 -0
  166. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/mocks/test_mock_client.py +0 -0
  167. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/__init__.py +0 -0
  168. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_agency.py +0 -0
  169. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_agency_subagencies.py +0 -0
  170. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_agency_tas_integration.py +0 -0
  171. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_award_account.py +0 -0
  172. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_award_agency_integration.py +0 -0
  173. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_award_category_registry.py +0 -0
  174. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_award_common.py +0 -0
  175. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_award_decimal_precision.py +0 -0
  176. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_award_factory.py +0 -0
  177. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_award_identifier.py +0 -0
  178. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_award_lazy_loading.py +0 -0
  179. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_award_office_integration.py +0 -0
  180. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_award_type_constants.py +0 -0
  181. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_award_types.py +0 -0
  182. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_base_model.py +0 -0
  183. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_district_spending.py +0 -0
  184. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_federal_account.py +0 -0
  185. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_funding.py +0 -0
  186. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_lazy_load.py +0 -0
  187. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_location.py +0 -0
  188. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_period_of_performance.py +0 -0
  189. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_recipient.py +0 -0
  190. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_recipient_business_categories.py +0 -0
  191. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_recipient_spending.py +0 -0
  192. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_search_projection.py +0 -0
  193. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_spending.py +0 -0
  194. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_state_spending.py +0 -0
  195. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_subaward.py +0 -0
  196. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_subtier_agency.py +0 -0
  197. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_treasury_account_symbol.py +0 -0
  198. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/models/test_volatile_field_mapping.py +0 -0
  199. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_agencies_search.py +0 -0
  200. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_agency_award_summary.py +0 -0
  201. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_agency_query.py +0 -0
  202. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_award_accounts_query.py +0 -0
  203. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_awards_search.py +0 -0
  204. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_client_side_query_builder.py +0 -0
  205. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_federal_accounts_query.py +0 -0
  206. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_filters.py +0 -0
  207. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_funding_search.py +0 -0
  208. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_idv_child_awards.py +0 -0
  209. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_query_builder.py +0 -0
  210. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_query_builder_list_behavior.py +0 -0
  211. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_query_builder_pagination.py +0 -0
  212. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_recipient_query.py +0 -0
  213. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_recipients_search.py +0 -0
  214. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_search_query_builder.py +0 -0
  215. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_single_resource_base.py +0 -0
  216. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_spending_search.py +0 -0
  217. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_spending_search_count.py +0 -0
  218. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_sub_agency_query.py +0 -0
  219. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_subawards_search.py +0 -0
  220. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_tas_agencies_query.py +0 -0
  221. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/queries/test_tas_codes_query.py +0 -0
  222. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/resources/test_agency_resource.py +0 -0
  223. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/resources/test_award_resource.py +0 -0
  224. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/resources/test_funding_resource.py +0 -0
  225. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/resources/test_recipients_resource.py +0 -0
  226. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/resources/test_spending_resource.py +0 -0
  227. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/resources/test_subawards_resource.py +0 -0
  228. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/resources/test_tas_resource.py +0 -0
  229. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/snapshot_support.py +0 -0
  230. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/test_award_accounts_integration.py +0 -0
  231. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/test_cache_fallback.py +0 -0
  232. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/test_characterization.py +0 -0
  233. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/test_client.py +0 -0
  234. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/test_client_caching.py +0 -0
  235. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/test_client_funding.py +0 -0
  236. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/test_client_rate_limit.py +0 -0
  237. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/test_client_security.py +0 -0
  238. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/test_config.py +0 -0
  239. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/test_download_resource.py +0 -0
  240. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/test_exceptions.py +0 -0
  241. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/test_golden_master_integration.py +0 -0
  242. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/test_logging_config.py +0 -0
  243. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/test_mock_client_example.py +0 -0
  244. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/test_public_api_surface.py +0 -0
  245. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/test_spending_integration.py +0 -0
  246. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/utils/__init__.py +0 -0
  247. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/utils/decimal_helpers.py +0 -0
  248. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/utils/test_dates.py +0 -0
  249. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/utils/test_doctests.py +0 -0
  250. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/utils/test_numbers.py +0 -0
  251. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/utils/test_rate_limit.py +0 -0
  252. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/utils/test_retry.py +0 -0
  253. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/utils/test_retry_session_reset.py +0 -0
  254. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/utils/test_special_cases_data.py +0 -0
  255. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/utils/test_textcase.py +0 -0
  256. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/utils/test_validations.py +0 -0
  257. {usaspending_orm-0.8.0 → usaspending_orm-0.9.0}/tests/volatile_fields.py +0 -0
@@ -4,6 +4,90 @@ All notable changes to the USASpending ORM library are documented here.
4
4
  The format loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5
5
  and the project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.9.0] - 2026-07-30
8
+
9
+ Adds the global transaction search: `client.transactions.search()` covers the
10
+ `/search/spending_by_transaction/` endpoint with the full fluent filter set,
11
+ mixed award-type categories, exact bucketed counts, and a `Transaction` model
12
+ that reads both of the API's row shapes.
13
+
14
+ **Breaking changes**
15
+
16
+ - `TransactionsSearch` is renamed to `AwardTransactionsSearch`. The freed name is
17
+ reused in this release for the new global `/search/spending_by_transaction/`
18
+ builder, so code importing `TransactionsSearch` directly keeps importing
19
+ cleanly but gets a different class rather than an `ImportError`. No
20
+ deprecation alias is provided. `client.transactions.award_id()` and
21
+ `award.transactions` are unchanged in both behavior and return value; only the
22
+ class and module names moved.
23
+
24
+ - `Transaction.transaction_amount` and its alias `amt` now return the first
25
+ nonzero of `federal_action_obligation`, `face_value_loan_guarantee` and
26
+ `original_loan_subsidy_cost`, and return the zero when every figure the row
27
+ carries is zero. Previously a zero obligation read as no value at all, so a
28
+ genuine zero-dollar transaction was indistinguishable from a row carrying no
29
+ amount, and a loan row, which reports a zero obligation with the real figure
30
+ in the loan fields, reported nothing. Negative figures such as deobligations
31
+ are preserved rather than skipped over. `amt is None` used to be true for
32
+ every zero-dollar transaction and is now true only for a row carrying none of
33
+ the three fields, so an `is None` check standing in for "no money moved"
34
+ needs `not amt` instead, and loan rows now report their face value, which
35
+ changes any total taken over a loan result set. Note that USAspending.gov's
36
+ own internal "pragmatic obligation" convention uses the subsidy cost, not
37
+ the face value, as a loan transaction's budgetary figure; this library
38
+ reports the face value the website displays, and
39
+ `original_loan_subsidy_cost` remains directly readable for budgetary totals.
40
+
41
+ ### Added
42
+
43
+ - `client.transactions.search()`, returning the new `TransactionsSearch` query
44
+ builder over `/search/spending_by_transaction/`. It searches every
45
+ transaction the API holds rather than the transactions of one award, and
46
+ takes the same fluent filters the award search takes: `keywords()`,
47
+ `agency()`, `time_period()` and `fiscal_year()`, the location, code and
48
+ amount filters, plus `order_by()` over the endpoint's sort fields. An
49
+ `award_type_codes` filter is required, and unlike the award search this
50
+ endpoint accepts codes from more than one category at once, so
51
+ `.contracts().grants()` is a single valid query where the same chain on
52
+ `client.awards.search()` raises `ValidationError`. `count()` and `len()` read
53
+ `/search/spending_by_transaction_count/`, summing the buckets of the
54
+ categories the query selected. `TransactionsSearch` is exported from
55
+ `usaspending` and `usaspending.queries`.
56
+
57
+ - `Transaction` reads both row shapes the API sends: the snake_case keys of the
58
+ award-scoped `/transactions/` listing and the display-name keys of the global
59
+ search (`"Action Date"`, `"Transaction Amount"` and the rest). The existing
60
+ properties gained the second spelling, and the search rows carry fields the
61
+ award-scoped listing never had: `award_internal_id`,
62
+ `generated_unique_award_id`, `award_identifier`, `recipient_name`,
63
+ `recipient_uei`, `recipient_id`, the four agency-name properties,
64
+ `issued_date`, `last_date_to_order`, `naics_code`, `naics_description`,
65
+ `psc_code`, `psc_description`, `cfda_title`, `def_codes`,
66
+ `recipient_location` and `place_of_performance`. `id` is `None` for a global
67
+ search row, which identifies only the parent award, and
68
+ `transaction_description` is the preferred name for what `award_description`
69
+ has always returned. Every raw row key also has a getter under its
70
+ snake_cased name (`award_id`, `mod`, `award_type`, `loan_value`,
71
+ `subsidy_cost`, `description`, `primary_place_of_performance`, `amount`),
72
+ aliasing the normalized helpers, with the exceptions documented in the
73
+ model: agency names live under `*_agency_name`, dict-valued classifications
74
+ under their scalar helpers, and the server's award identifiers under
75
+ `award_internal_id` and `generated_unique_award_id`.
76
+
77
+ ### Known limitations
78
+
79
+ - `TransactionsSearch` is bounded by the API's 50,000-row result window.
80
+ Iterating past that point raises `APIError` rather than truncating silently.
81
+ Narrow the filters, set `limit()`, or use `client.downloads` for larger
82
+ result sets; `count()` is not subject to the window.
83
+
84
+ - With a `program_activities()` filter set, `TransactionsSearch.count()` and
85
+ `len()` raise `ValidationError`: the count endpoint silently ignores that
86
+ filter while the search endpoint honors it, so passing it through would
87
+ report a confidently wrong count. `list(query)`, indexing and slicing
88
+ consult the count and so raise too; iteration, `all()`, `first()` and
89
+ `bool()` need no count and work regardless.
90
+
7
91
  ## [0.8.0] - 2026-07-28
8
92
 
9
93
  Mostly refactoring internals to DRY the codebase and standardize the query interface.
@@ -262,7 +346,7 @@ truthiness agree under the bounds you set. Adding CI tests to verify compatabili
262
346
  `q1 = search().keywords("alpha")` followed by `q2 = q1.keywords("beta")`
263
347
  left `q1` sending `["alpha", "beta", "beta"]`. Affected chained `keywords`,
264
348
  `award_type_codes`, simple `psc_codes` and `treasury_account_components`
265
- filters.
349
+ filters.
266
350
 
267
351
  - `client.recipients.find_by_recipient_id()` no longer addresses the wrong
268
352
  record for a recipient ID carrying several levels, such as
@@ -274,7 +358,7 @@ truthiness agree under the bounds you set. Adding CI tests to verify compatabili
274
358
  pass the suffix yourself (see `Recipient.recipient_level` under Added).
275
359
  `client.spending.search().recipient_id()` also normalizes its argument now,
276
360
  so a multi-level ID copied out of `raw` produces a filter the API can
277
- match.
361
+ match.
278
362
 
279
363
  - `award.transactions` no longer reports a count that disagrees with what it
280
364
  yields. `since()` and `until()` filter in memory, but the count came from
@@ -282,7 +366,7 @@ truthiness agree under the bounds you set. Adding CI tests to verify compatabili
282
366
  and `len()` said three where iteration yielded two, slices disagreed with
283
367
  `len()`, and `query[-1]` raised `IndexError`. All of them now agree, and
284
368
  `limit(1)` means one matching row rather than one row that may then be
285
- discarded.
369
+ discarded.
286
370
 
287
371
  - A `Recipient` built directly from an award search result now reports its
288
372
  `uei` and `location`, which read only the nested spellings a detail
@@ -291,7 +375,7 @@ truthiness agree under the bounds you set. Adding CI tests to verify compatabili
291
375
  not. `PeriodOfPerformance` had the same gap for `"Base Obligation Date"`.
292
376
  Both models now read every spelling of their own fields, and `Award` hands
293
377
  its payload over rather than reproducing the mapping; nested spellings
294
- still take precedence where a payload carries both.
378
+ still take precedence where a payload carries both.
295
379
 
296
380
  - Indexing and slicing an in-memory query now read the limited collection:
297
381
  `agency.federal_accounts.limit(3)[:]` returned every account and
@@ -303,10 +387,10 @@ truthiness agree under the bounds you set. Adding CI tests to verify compatabili
303
387
  fieldless `@dataclass` declaration generated an `__eq__` comparing empty
304
388
  tuples, so any two transactions were equal and none was hashable. They now
305
389
  compare by identity, like every other model, and can be used in sets and as
306
- dict keys.
390
+ dict keys.
307
391
 
308
392
  - `Recipient.parents` returns a fresh list on each read, so sorting or
309
- popping the result no longer corrupts the model.
393
+ popping the result no longer corrupts the model.
310
394
 
311
395
  - Iterating `idv.child_awards` no longer raises
312
396
  `AttributeError: 'str' object has no attribute 'get'` when reading the four
@@ -344,7 +428,6 @@ truthiness agree under the bounds you set. Adding CI tests to verify compatabili
344
428
  - `FederalAccount.count` no longer fires an API request when the count is
345
429
  already in the response; the fallback was an eagerly evaluated default
346
430
  argument, so every access paid for a request whose result was discarded.
347
-
348
431
 
349
432
  - `query.limit(0).first()` returned a row, contradicting `.all()` and
350
433
  `len()`. It now respects the zero, which is also what makes `__bool__`
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: usaspending-orm
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: A Python ORM wrapper for the USAspending.gov API with intuitive queries, automatic pagination, and smart caching
5
5
  Project-URL: Homepage, https://github.com/planetary-society/usaspending-orm
6
6
  Project-URL: Repository, https://github.com/planetary-society/usaspending-orm
@@ -168,6 +168,73 @@ with USASpendingClient() as client:
168
168
 
169
169
  ```
170
170
 
171
+ ### Searching Transactions Across Awards
172
+
173
+ `award.transactions` lists the modifications of a single award. `client.transactions.search()` searches every transaction the API holds, takes the same fluent filters the award search takes, and returns one row per transaction rather than one row per award:
174
+
175
+ ```python
176
+ with USASpendingClient() as client:
177
+
178
+ # Award-level keyword search: one row per matching award
179
+ awards = client.awards.search() \
180
+ .contracts() \
181
+ .agency("National Aeronautics and Space Administration") \
182
+ .keywords("Mars") \
183
+ .fiscal_year(2024)
184
+
185
+ print(awards.count()) # -> 106
186
+
187
+ # Transaction-level keyword search: one row per matching transaction,
188
+ # across every award rather than within one
189
+ transactions = client.transactions.search() \
190
+ .contracts() \
191
+ .agency("National Aeronautics and Space Administration") \
192
+ .keywords("Mars") \
193
+ .fiscal_year(2024) \
194
+ .order_by("transaction_amount", "desc")
195
+
196
+ print(transactions.count()) # -> 264
197
+
198
+ for transaction in transactions.limit(3):
199
+ print(f"{transaction.action_date} {transaction.award_identifier}: ${transaction.amount or 0:,.2f}")
200
+
201
+ # 2023-12-15 80NM0021F0008: $45,874,344.00
202
+ # 2023-11-01 80NM0021F0008: $39,011,361.00
203
+ # 2024-04-12 80NM0021F0008: $36,306,819.00
204
+ ```
205
+
206
+ An award type filter is required. Unlike the award search, this endpoint accepts codes from more than one category at once, so contracts and grants can be searched in a single query:
207
+
208
+ ```python
209
+ with USASpendingClient() as client:
210
+
211
+ mixed = client.transactions.search() \
212
+ .contracts() \
213
+ .grants() \
214
+ .agency("National Aeronautics and Space Administration") \
215
+ .keywords("asteroid") \
216
+ .fiscal_year(2024) \
217
+ .order_by("action_date", "desc")
218
+
219
+ print(mixed.count()) # -> 115
220
+
221
+ for transaction in mixed.limit(5):
222
+ print(f"{transaction.action_date} {transaction.type_description}: ${transaction.amount or 0:,.2f}")
223
+
224
+ # 2024-09-25 COOPERATIVE AGREEMENT (B): $319,834.00
225
+ # 2024-09-23 PROJECT GRANT (B): $-0.26
226
+ # 2024-09-19 COOPERATIVE AGREEMENT (B): $0.00
227
+ # 2024-09-18 COOPERATIVE AGREEMENT (B): $0.00
228
+ # 2024-09-18 DEFINITIVE CONTRACT: $3,815,000.00
229
+ ```
230
+
231
+ The same chain on `client.awards.search()` raises `ValidationError`, because `spending_by_award` offers different fields and filters per category and so accepts only one at a time.
232
+
233
+ Two limits are worth knowing:
234
+
235
+ - The API serves at most the first 50,000 matching rows. Iterating past that point raises `APIError` rather than truncating silently, so narrow the filters, set a `limit()`, or use `client.downloads` for larger result sets. `count()` is not subject to the window.
236
+ - `count()` reads the dedicated transaction count endpoint, which reports one bucket per award-type category, so the figure is the sum of the buckets the query selected. That endpoint does not accept the `program_activities` filter, so with one set `count()` and `len()` raise `ValidationError`; iterating the query and calling `.all()` work regardless, as neither needs a count.
237
+
171
238
  ### Example: Top National Aeronautics and Space Administration Contractors in Fiscal Year 2024
172
239
 
173
240
  Aggregation endpoints are available through `client.spending`. The same fluent filters
@@ -139,6 +139,73 @@ with USASpendingClient() as client:
139
139
 
140
140
  ```
141
141
 
142
+ ### Searching Transactions Across Awards
143
+
144
+ `award.transactions` lists the modifications of a single award. `client.transactions.search()` searches every transaction the API holds, takes the same fluent filters the award search takes, and returns one row per transaction rather than one row per award:
145
+
146
+ ```python
147
+ with USASpendingClient() as client:
148
+
149
+ # Award-level keyword search: one row per matching award
150
+ awards = client.awards.search() \
151
+ .contracts() \
152
+ .agency("National Aeronautics and Space Administration") \
153
+ .keywords("Mars") \
154
+ .fiscal_year(2024)
155
+
156
+ print(awards.count()) # -> 106
157
+
158
+ # Transaction-level keyword search: one row per matching transaction,
159
+ # across every award rather than within one
160
+ transactions = client.transactions.search() \
161
+ .contracts() \
162
+ .agency("National Aeronautics and Space Administration") \
163
+ .keywords("Mars") \
164
+ .fiscal_year(2024) \
165
+ .order_by("transaction_amount", "desc")
166
+
167
+ print(transactions.count()) # -> 264
168
+
169
+ for transaction in transactions.limit(3):
170
+ print(f"{transaction.action_date} {transaction.award_identifier}: ${transaction.amount or 0:,.2f}")
171
+
172
+ # 2023-12-15 80NM0021F0008: $45,874,344.00
173
+ # 2023-11-01 80NM0021F0008: $39,011,361.00
174
+ # 2024-04-12 80NM0021F0008: $36,306,819.00
175
+ ```
176
+
177
+ An award type filter is required. Unlike the award search, this endpoint accepts codes from more than one category at once, so contracts and grants can be searched in a single query:
178
+
179
+ ```python
180
+ with USASpendingClient() as client:
181
+
182
+ mixed = client.transactions.search() \
183
+ .contracts() \
184
+ .grants() \
185
+ .agency("National Aeronautics and Space Administration") \
186
+ .keywords("asteroid") \
187
+ .fiscal_year(2024) \
188
+ .order_by("action_date", "desc")
189
+
190
+ print(mixed.count()) # -> 115
191
+
192
+ for transaction in mixed.limit(5):
193
+ print(f"{transaction.action_date} {transaction.type_description}: ${transaction.amount or 0:,.2f}")
194
+
195
+ # 2024-09-25 COOPERATIVE AGREEMENT (B): $319,834.00
196
+ # 2024-09-23 PROJECT GRANT (B): $-0.26
197
+ # 2024-09-19 COOPERATIVE AGREEMENT (B): $0.00
198
+ # 2024-09-18 COOPERATIVE AGREEMENT (B): $0.00
199
+ # 2024-09-18 DEFINITIVE CONTRACT: $3,815,000.00
200
+ ```
201
+
202
+ The same chain on `client.awards.search()` raises `ValidationError`, because `spending_by_award` offers different fields and filters per category and so accepts only one at a time.
203
+
204
+ Two limits are worth knowing:
205
+
206
+ - The API serves at most the first 50,000 matching rows. Iterating past that point raises `APIError` rather than truncating silently, so narrow the filters, set a `limit()`, or use `client.downloads` for larger result sets. `count()` is not subject to the window.
207
+ - `count()` reads the dedicated transaction count endpoint, which reports one bucket per award-type category, so the figure is the sum of the buckets the query selected. That endpoint does not accept the `program_activities` filter, so with one set `count()` and `len()` raise `ValidationError`; iterating the query and calling `.all()` work regardless, as neither needs a count.
208
+
142
209
  ### Example: Top National Aeronautics and Space Administration Contractors in Fiscal Year 2024
143
210
 
144
211
  Aggregation endpoints are available through `client.spending`. The same fluent filters
@@ -83,7 +83,7 @@ These documentation links provide detailed information about request parameters,
83
83
  - **Method**: POST
84
84
  - **Documentation**: https://raw.githubusercontent.com/fedspendingtransparency/usaspending-api/refs/heads/master/usaspending_api/api_contracts/contracts/v2/transactions.md
85
85
  - **Purpose**: List obligations and modifications for a given award
86
- - **Corresponding Query Builder**: `src/usaspending/queries/transactions_search.py`
86
+ - **Corresponding Query Builder**: `src/usaspending/queries/award_transactions_search.py`
87
87
 
88
88
  ### Award Transaction Count
89
89
 
@@ -91,7 +91,25 @@ These documentation links provide detailed information about request parameters,
91
91
  - **Method**: GET
92
92
  - **Documentation**: https://raw.githubusercontent.com/fedspendingtransparency/usaspending-api/refs/heads/master/usaspending_api/api_contracts/contracts/v2/awards/count/transaction/award_id.md
93
93
  - **Purpose**: Return the number of transactions associated with a given award
94
+ - **Corresponding Query Builder**: `src/usaspending/queries/award_transactions_search.py`
95
+
96
+ ### Transaction Search
97
+
98
+ - **Endpoint**: `/api/v2/search/spending_by_transaction/`
99
+ - **Method**: POST
100
+ - **Documentation**: https://raw.githubusercontent.com/fedspendingtransparency/usaspending-api/refs/heads/master/usaspending_api/api_contracts/contracts/v2/search/spending_by_transaction.md
101
+ - **Purpose**: Search transactions across all awards using the standard search filters
102
+ - **Corresponding Query Builder**: `src/usaspending/queries/transactions_search.py`
103
+ - **Note**: `filters.award_type_codes`, `fields`, `sort` and `order` are all required, and the sort field must be one of the requested `fields`. Unlike `/api/v2/search/spending_by_award/`, this endpoint accepts award type codes from more than one category in a single request. The API serves at most the first 50,000 matching rows and returns an error past that point.
104
+
105
+ ### Transaction Search Count
106
+
107
+ - **Endpoint**: `/api/v2/search/spending_by_transaction_count/`
108
+ - **Method**: POST
109
+ - **Documentation**: https://raw.githubusercontent.com/fedspendingtransparency/usaspending-api/refs/heads/master/usaspending_api/api_contracts/contracts/v2/search/spending_by_transaction_count.md
110
+ - **Purpose**: Return transaction counts by award-type category for a set of search filters
94
111
  - **Corresponding Query Builder**: `src/usaspending/queries/transactions_search.py`
112
+ - **Note**: The response reports one bucket per category (`contracts`, `idvs`, `grants`, `loans`, `direct_payments`, `other`), so a count for a query is the sum of the buckets its award type codes select. This endpoint's validator does not accept the `program_activities` filter, which the search endpoint does accept.
95
113
 
96
114
  ### Award funding history
97
115
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "usaspending-orm"
3
- version = "0.8.0"
3
+ version = "0.9.0"
4
4
  description = "A Python ORM wrapper for the USAspending.gov API with intuitive queries, automatic pagination, and smart caching"
5
5
  authors = [
6
6
  {name = "Casey Dreier", email = "casey.dreier@planetary.org"}
@@ -60,6 +60,7 @@ from .queries import (
60
60
  AgenciesSearch,
61
61
  AwardingAgenciesSearch,
62
62
  AwardsSearch,
63
+ AwardTransactionsSearch,
63
64
  FundingAgenciesSearch,
64
65
  FundingSearch,
65
66
  RecipientsSearch,
@@ -98,6 +99,7 @@ __all__ = [
98
99
  "Award",
99
100
  "AwardAmountFilter",
100
101
  "AwardDateType",
102
+ "AwardTransactionsSearch",
101
103
  "AwardingAgenciesSearch",
102
104
  # Query builders
103
105
  "AwardsSearch",
@@ -27,9 +27,9 @@ if TYPE_CHECKING:
27
27
  from ..client import USASpendingClient
28
28
  from ..download.job import DownloadJob
29
29
  from ..queries.award_accounts_query import AwardAccountsQuery
30
+ from ..queries.award_transactions_search import AwardTransactionsSearch
30
31
  from ..queries.funding_search import FundingSearch
31
32
  from ..queries.subawards_search import SubAwardsSearch
32
- from ..queries.transactions_search import TransactionsSearch
33
33
 
34
34
  logger = USASpendingLogger.get_logger(__name__)
35
35
 
@@ -488,7 +488,7 @@ class Award(LazyRecord):
488
488
  # - place_of_performance (Location object: location where the work is performed)
489
489
  #
490
490
  # Has Many (one-to-many relationships):
491
- # - transactions (TransactionsSearch object: query builder for transactions associated with the award)
491
+ # - transactions (AwardTransactionsSearch object: query builder for transactions associated with the award)
492
492
  # - funding (FundingSearch object: query builder for treasury funding records (outlay and obligation) associated with the award)
493
493
  # - subawards (SubAwardsSearch object: query builder for subawards associated with the award)
494
494
 
@@ -702,10 +702,10 @@ class Award(LazyRecord):
702
702
  return self._build_subtier_agency("awarding")
703
703
 
704
704
  @property
705
- def transactions(self) -> TransactionsSearch:
705
+ def transactions(self) -> AwardTransactionsSearch:
706
706
  """Get transactions query builder for this award.
707
707
 
708
- Returns a TransactionsSearch object that can be further filtered and chained.
708
+ Returns an AwardTransactionsSearch object that can be further filtered and chained.
709
709
 
710
710
  Examples:
711
711
  >>> award.transactions.count() # Get count without loading all data
@@ -713,7 +713,7 @@ class Award(LazyRecord):
713
713
  >>> award.transactions.all() # Every transaction, as a list
714
714
 
715
715
  Returns:
716
- TransactionsSearch: The query builder for transactions.
716
+ AwardTransactionsSearch: The query builder for transactions.
717
717
  """
718
718
  return self._client.transactions.award_id(self.generated_unique_award_id)
719
719
 
@@ -23,11 +23,13 @@ missing, so the omission surfaces immediately.
23
23
 
24
24
  from __future__ import annotations
25
25
 
26
- from collections.abc import Mapping
26
+ from collections.abc import Iterable, Mapping
27
27
  from dataclasses import dataclass
28
28
  from functools import cached_property
29
29
  from types import MappingProxyType
30
30
 
31
+ from ..exceptions import ValidationError
32
+
31
33
 
32
34
  @dataclass(frozen=True)
33
35
  class AwardCategory:
@@ -308,6 +310,27 @@ def is_valid_award_type(code: str) -> bool:
308
310
  return code in ALL_AWARD_CODES
309
311
 
310
312
 
313
+ def validate_award_type_codes(codes: Iterable[str]) -> None:
314
+ """Raise if any supplied code is not a valid award type code.
315
+
316
+ The award-search and transaction-search builders both accept caller-supplied
317
+ codes and share this check, so the error message cannot drift between them.
318
+
319
+ Args:
320
+ codes: Award type codes to validate.
321
+
322
+ Raises:
323
+ ValidationError: If any code is not in :data:`ALL_AWARD_CODES`, naming
324
+ the offending codes and listing the valid ones.
325
+ """
326
+ invalid_codes = [code for code in codes if code not in ALL_AWARD_CODES]
327
+ if invalid_codes:
328
+ raise ValidationError(
329
+ f"Invalid award type code(s): {', '.join(sorted(invalid_codes))}. "
330
+ f"Valid codes are: {', '.join(sorted(ALL_AWARD_CODES))}"
331
+ )
332
+
333
+
311
334
  def get_description(code: str) -> str:
312
335
  """Get the description for a given award type code.
313
336