usaspending-orm 0.7.3__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 (278) hide show
  1. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/.gitignore +3 -0
  2. usaspending_orm-0.9.0/CHANGELOG.md +551 -0
  3. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/PKG-INFO +83 -9
  4. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/README.md +80 -8
  5. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/api-docs-links.md +19 -1
  6. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/pyproject.toml +18 -1
  7. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/__init__.py +2 -0
  8. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/client.py +2 -2
  9. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/logging_config.py +0 -21
  10. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/agency.py +41 -29
  11. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/award.py +186 -243
  12. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/award_account.py +10 -9
  13. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/award_factory.py +46 -13
  14. usaspending_orm-0.9.0/src/usaspending/models/award_identifier.py +73 -0
  15. usaspending_orm-0.9.0/src/usaspending/models/award_types.py +379 -0
  16. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/base_model.py +79 -46
  17. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/contract.py +13 -80
  18. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/district_spending.py +2 -22
  19. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/download.py +1 -1
  20. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/federal_account.py +37 -9
  21. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/funding.py +12 -15
  22. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/grant.py +2 -2
  23. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/idv.py +14 -105
  24. usaspending_orm-0.9.0/src/usaspending/models/lazy_record.py +151 -0
  25. usaspending_orm-0.9.0/src/usaspending/models/loan.py +83 -0
  26. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/location.py +7 -6
  27. usaspending_orm-0.9.0/src/usaspending/models/period_of_performance.py +121 -0
  28. usaspending_orm-0.9.0/src/usaspending/models/procurement_award.py +117 -0
  29. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/recipient.py +79 -49
  30. usaspending_orm-0.9.0/src/usaspending/models/recipient_spending.py +72 -0
  31. usaspending_orm-0.9.0/src/usaspending/models/spending.py +143 -0
  32. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/state_spending.py +2 -22
  33. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/subaward.py +11 -17
  34. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/subtier_agency.py +10 -3
  35. usaspending_orm-0.9.0/src/usaspending/models/transaction.py +582 -0
  36. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/treasury_account_symbol.py +3 -4
  37. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/__init__.py +5 -1
  38. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/agencies_search.py +12 -35
  39. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/agency_award_summary.py +17 -51
  40. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/agency_query.py +3 -11
  41. usaspending_orm-0.9.0/src/usaspending/queries/award_accounts_query.py +85 -0
  42. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/award_query.py +1 -1
  43. usaspending_orm-0.7.3/src/usaspending/queries/transactions_search.py → usaspending_orm-0.9.0/src/usaspending/queries/award_transactions_search.py +110 -114
  44. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/awarding_agencies_search.py +7 -14
  45. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/awards_search.py +46 -139
  46. usaspending_orm-0.9.0/src/usaspending/queries/base_query.py +239 -0
  47. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/client_side_query_builder.py +51 -28
  48. usaspending_orm-0.9.0/src/usaspending/queries/federal_accounts_query.py +112 -0
  49. usaspending_orm-0.9.0/src/usaspending/queries/filter_tree_query.py +263 -0
  50. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/filters.py +80 -6
  51. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/funding_agencies_search.py +7 -14
  52. usaspending_orm-0.9.0/src/usaspending/queries/funding_search.py +66 -0
  53. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/idv_child_awards.py +14 -61
  54. usaspending_orm-0.9.0/src/usaspending/queries/mixins.py +154 -0
  55. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/query_builder.py +415 -171
  56. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/recipient_query.py +3 -31
  57. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/recipients_search.py +7 -6
  58. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/single_resource_base.py +4 -2
  59. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/spending_search.py +15 -62
  60. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/sub_agency_query.py +19 -57
  61. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/subawards_search.py +7 -27
  62. usaspending_orm-0.9.0/src/usaspending/queries/tas_agencies_query.py +72 -0
  63. usaspending_orm-0.9.0/src/usaspending/queries/tas_codes_query.py +122 -0
  64. usaspending_orm-0.9.0/src/usaspending/queries/transactions_search.py +239 -0
  65. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/resources/agency_resource.py +30 -22
  66. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/resources/award_accounts_resource.py +1 -1
  67. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/resources/award_resource.py +12 -22
  68. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/resources/funding_resource.py +4 -4
  69. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/resources/recipients_resource.py +48 -21
  70. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/resources/spending_resource.py +7 -3
  71. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/resources/subawards_resource.py +5 -4
  72. usaspending_orm-0.9.0/src/usaspending/resources/tas_resource.py +65 -0
  73. usaspending_orm-0.9.0/src/usaspending/resources/transactions_resource.py +63 -0
  74. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/utils/__init__.py +2 -0
  75. usaspending_orm-0.9.0/src/usaspending/utils/dates.py +139 -0
  76. usaspending_orm-0.9.0/src/usaspending/utils/numbers.py +99 -0
  77. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/utils/retry.py +4 -9
  78. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/utils/special_cases.yaml +1 -2
  79. usaspending_orm-0.9.0/src/usaspending/utils/textcase.py +379 -0
  80. usaspending_orm-0.9.0/src/usaspending/utils/validations.py +388 -0
  81. usaspending_orm-0.9.0/tests/fixtures/golden/agency.json +74 -0
  82. usaspending_orm-0.9.0/tests/fixtures/golden/award_account_first_result.json +29 -0
  83. usaspending_orm-0.9.0/tests/fixtures/golden/award_contract.json +190 -0
  84. usaspending_orm-0.9.0/tests/fixtures/golden/award_contract_awarding_agency.json +74 -0
  85. usaspending_orm-0.9.0/tests/fixtures/golden/award_contract_period_of_performance.json +15 -0
  86. usaspending_orm-0.9.0/tests/fixtures/golden/award_contract_place_of_performance.json +23 -0
  87. usaspending_orm-0.9.0/tests/fixtures/golden/award_contract_recipient.json +51 -0
  88. usaspending_orm-0.9.0/tests/fixtures/golden/award_grant.json +198 -0
  89. usaspending_orm-0.9.0/tests/fixtures/golden/award_idv.json +190 -0
  90. usaspending_orm-0.9.0/tests/fixtures/golden/award_loan.json +191 -0
  91. usaspending_orm-0.9.0/tests/fixtures/golden/award_parent_of_contract.json +136 -0
  92. usaspending_orm-0.9.0/tests/fixtures/golden/funding_first_result.json +35 -0
  93. usaspending_orm-0.9.0/tests/fixtures/golden/idv_child_award_first_result.json +192 -0
  94. usaspending_orm-0.9.0/tests/fixtures/golden/recipient.json +60 -0
  95. usaspending_orm-0.9.0/tests/fixtures/golden/search_contract_first_result.json +190 -0
  96. usaspending_orm-0.9.0/tests/fixtures/golden/search_subaward_first_result.json +91 -0
  97. usaspending_orm-0.9.0/tests/fixtures/golden/spending_by_recipient_first_result.json +68 -0
  98. usaspending_orm-0.9.0/tests/fixtures/golden/spending_by_state_first_result.json +17 -0
  99. usaspending_orm-0.9.0/tests/fixtures/golden/transaction_first_result.json +18 -0
  100. usaspending_orm-0.9.0/tests/fixtures/public_api_surface.json +1489 -0
  101. usaspending_orm-0.9.0/tests/fixtures/spending_by_transaction.json +214 -0
  102. usaspending_orm-0.9.0/tests/fixtures/spending_by_transaction_count.json +10 -0
  103. usaspending_orm-0.9.0/tests/fixtures/spending_by_transaction_mixed.json +210 -0
  104. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/mocks/README.md +29 -3
  105. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/mocks/mock_client.py +70 -9
  106. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/mocks/response_builder.py +5 -0
  107. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_agency.py +11 -38
  108. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_agency_subagencies.py +5 -5
  109. usaspending_orm-0.9.0/tests/models/test_agency_tas_integration.py +364 -0
  110. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_award_account.py +16 -2
  111. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_award_agency_integration.py +63 -0
  112. usaspending_orm-0.9.0/tests/models/test_award_category_registry.py +159 -0
  113. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_award_common.py +14 -62
  114. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_award_decimal_precision.py +39 -12
  115. usaspending_orm-0.9.0/tests/models/test_award_identifier.py +81 -0
  116. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_award_types.py +10 -4
  117. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_base_model.py +60 -2
  118. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_federal_account.py +130 -0
  119. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_funding.py +22 -8
  120. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_lazy_load.py +161 -1
  121. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_location.py +9 -11
  122. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_period_of_performance.py +113 -1
  123. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_recipient.py +188 -37
  124. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_recipient_spending.py +6 -4
  125. usaspending_orm-0.9.0/tests/models/test_search_projection.py +175 -0
  126. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_subaward.py +16 -23
  127. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_subtier_agency.py +4 -4
  128. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_transaction.py +72 -1
  129. usaspending_orm-0.9.0/tests/models/test_transaction_search_projection.py +456 -0
  130. usaspending_orm-0.9.0/tests/models/test_volatile_field_mapping.py +177 -0
  131. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_agency_award_summary.py +17 -17
  132. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_award_accounts_query.py +28 -4
  133. usaspending_orm-0.9.0/tests/queries/test_award_transactions_search.py +492 -0
  134. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_client_side_query_builder.py +94 -2
  135. usaspending_orm-0.9.0/tests/queries/test_count_respects_limits.py +217 -0
  136. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_federal_accounts_query.py +8 -1
  137. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_query_builder.py +52 -0
  138. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_query_builder_list_behavior.py +98 -49
  139. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_query_builder_pagination.py +3 -2
  140. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_recipient_query.py +6 -2
  141. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_search_query_builder.py +38 -0
  142. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_spending_search.py +12 -0
  143. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_sub_agency_query.py +2 -2
  144. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_subawards_search.py +2 -2
  145. usaspending_orm-0.9.0/tests/queries/test_tas_agencies_query.py +84 -0
  146. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_tas_codes_query.py +61 -1
  147. usaspending_orm-0.9.0/tests/queries/test_transactions_search.py +504 -0
  148. usaspending_orm-0.9.0/tests/resources/test_recipients_resource.py +71 -0
  149. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/resources/test_tas_resource.py +56 -8
  150. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/resources/test_transactions_resource.py +22 -3
  151. usaspending_orm-0.9.0/tests/snapshot_support.py +70 -0
  152. usaspending_orm-0.9.0/tests/test_characterization.py +282 -0
  153. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_client_transactions.py +1 -1
  154. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_download_resource.py +97 -0
  155. usaspending_orm-0.9.0/tests/test_golden_master_integration.py +707 -0
  156. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_integration.py +52 -0
  157. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_logging_config.py +0 -20
  158. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_mock_client_example.py +11 -8
  159. usaspending_orm-0.9.0/tests/test_public_api_surface.py +388 -0
  160. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_readme_examples.py +1 -1
  161. usaspending_orm-0.9.0/tests/utils/test_dates.py +288 -0
  162. usaspending_orm-0.9.0/tests/utils/test_doctests.py +66 -0
  163. usaspending_orm-0.9.0/tests/utils/test_numbers.py +73 -0
  164. usaspending_orm-0.9.0/tests/utils/test_special_cases_data.py +118 -0
  165. usaspending_orm-0.7.3/tests/utils/test_text_formatter.py → usaspending_orm-0.9.0/tests/utils/test_textcase.py +267 -40
  166. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/utils/test_validations.py +153 -0
  167. usaspending_orm-0.9.0/tests/volatile_fields.py +293 -0
  168. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/uv.lock +244 -2
  169. usaspending_orm-0.7.3/CHANGELOG.md +0 -81
  170. usaspending_orm-0.7.3/CLAUDE.md +0 -243
  171. usaspending_orm-0.7.3/poetry.lock +0 -585
  172. usaspending_orm-0.7.3/src/usaspending/models/award_types.py +0 -145
  173. usaspending_orm-0.7.3/src/usaspending/models/lazy_record.py +0 -90
  174. usaspending_orm-0.7.3/src/usaspending/models/loan.py +0 -115
  175. usaspending_orm-0.7.3/src/usaspending/models/period_of_performance.py +0 -75
  176. usaspending_orm-0.7.3/src/usaspending/models/recipient_spending.py +0 -84
  177. usaspending_orm-0.7.3/src/usaspending/models/spending.py +0 -103
  178. usaspending_orm-0.7.3/src/usaspending/models/transaction.py +0 -154
  179. usaspending_orm-0.7.3/src/usaspending/queries/award_accounts_query.py +0 -194
  180. usaspending_orm-0.7.3/src/usaspending/queries/base_query.py +0 -141
  181. usaspending_orm-0.7.3/src/usaspending/queries/federal_accounts_query.py +0 -205
  182. usaspending_orm-0.7.3/src/usaspending/queries/funding_search.py +0 -174
  183. usaspending_orm-0.7.3/src/usaspending/queries/tas_codes_query.py +0 -213
  184. usaspending_orm-0.7.3/src/usaspending/resources/tas_resource.py +0 -77
  185. usaspending_orm-0.7.3/src/usaspending/resources/transactions_resource.py +0 -40
  186. usaspending_orm-0.7.3/src/usaspending/utils/formatter.py +0 -449
  187. usaspending_orm-0.7.3/src/usaspending/utils/validations.py +0 -148
  188. usaspending_orm-0.7.3/tests/models/test_agency_tas_integration.py +0 -172
  189. usaspending_orm-0.7.3/tests/queries/test_transactions_search.py +0 -126
  190. usaspending_orm-0.7.3/tests/resources/test_recipients_resource.py +0 -29
  191. usaspending_orm-0.7.3/tests/utils/test_formatter.py +0 -301
  192. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/LICENSE +0 -0
  193. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/cli/__init__.py +0 -0
  194. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/cli/download_award.py +0 -0
  195. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/config.py +0 -0
  196. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/download/__init__.py +0 -0
  197. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/download/job.py +0 -0
  198. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/download/manager.py +0 -0
  199. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/exceptions.py +0 -0
  200. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/__init__.py +0 -0
  201. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/recipient_business_categories.py +0 -0
  202. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/resources/__init__.py +0 -0
  203. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/resources/base_resource.py +0 -0
  204. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/resources/download_resource.py +0 -0
  205. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/utils/rate_limit.py +0 -0
  206. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/__init__.py +0 -0
  207. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/conftest.py +0 -0
  208. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/agency.json +0 -0
  209. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/agency_autocomplete.json +0 -0
  210. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/agency_award_summary.json +0 -0
  211. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/agency_subagencies.json +0 -0
  212. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/accounts.json +0 -0
  213. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/award_funding_grant.json +0 -0
  214. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/contract.json +0 -0
  215. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/grant.json +0 -0
  216. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/idv.json +0 -0
  217. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/idv_awards.json +0 -0
  218. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/loan.json +0 -0
  219. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/search_results_contracts.json +0 -0
  220. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/search_results_grants.json +0 -0
  221. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/search_results_idvs.json +0 -0
  222. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/search_results_subawards.json +0 -0
  223. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/transactions.json +0 -0
  224. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/download_assistance.json +0 -0
  225. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/download_search.json +0 -0
  226. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/download_status.json +0 -0
  227. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/recipient_university.json +0 -0
  228. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/recipients_search.json +0 -0
  229. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/spending_by_award_contracts.json +0 -0
  230. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/spending_by_district.json +0 -0
  231. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/spending_by_recipient.json +0 -0
  232. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/spending_by_state.json +0 -0
  233. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/tas_agencies.json +0 -0
  234. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/tas_codes.json +0 -0
  235. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/tas_federal_accounts.json +0 -0
  236. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/top_recipients_params.json +0 -0
  237. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/top_recipients_response.json +0 -0
  238. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/mocks/__init__.py +0 -0
  239. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/mocks/test_mock_client.py +0 -0
  240. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/__init__.py +0 -0
  241. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_award_factory.py +0 -0
  242. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_award_lazy_loading.py +0 -0
  243. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_award_office_integration.py +0 -0
  244. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_award_type_constants.py +0 -0
  245. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_district_spending.py +0 -0
  246. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_recipient_business_categories.py +0 -0
  247. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_spending.py +0 -0
  248. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_state_spending.py +0 -0
  249. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_treasury_account_symbol.py +0 -0
  250. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_agencies_search.py +0 -0
  251. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_agency_query.py +0 -0
  252. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_awards_search.py +0 -0
  253. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_filters.py +0 -0
  254. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_funding_search.py +0 -0
  255. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_idv_child_awards.py +0 -0
  256. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_recipients_search.py +0 -0
  257. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_single_resource_base.py +0 -0
  258. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_spending_search_count.py +0 -0
  259. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/resources/test_agency_resource.py +0 -0
  260. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/resources/test_award_resource.py +0 -0
  261. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/resources/test_funding_resource.py +0 -0
  262. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/resources/test_spending_resource.py +0 -0
  263. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/resources/test_subawards_resource.py +0 -0
  264. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_award_accounts_integration.py +0 -0
  265. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_cache_fallback.py +0 -0
  266. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_client.py +0 -0
  267. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_client_caching.py +0 -0
  268. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_client_funding.py +0 -0
  269. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_client_rate_limit.py +0 -0
  270. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_client_security.py +0 -0
  271. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_config.py +0 -0
  272. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_exceptions.py +0 -0
  273. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_spending_integration.py +0 -0
  274. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/utils/__init__.py +0 -0
  275. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/utils/decimal_helpers.py +0 -0
  276. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/utils/test_rate_limit.py +0 -0
  277. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/utils/test_retry.py +0 -0
  278. {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/utils/test_retry_session_reset.py +0 -0
@@ -4,3 +4,6 @@ __pycache__
4
4
  .*_cache
5
5
  repomix*
6
6
  dist/
7
+ .claude/
8
+ /~/
9
+ .coverage
@@ -0,0 +1,551 @@
1
+ # Changelog
2
+
3
+ All notable changes to the USASpending ORM library are documented here.
4
+ The format loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5
+ and the project adheres to [Semantic Versioning](https://semver.org/).
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
+
91
+ ## [0.8.0] - 2026-07-28
92
+
93
+ Mostly refactoring internals to DRY the codebase and standardize the query interface.
94
+ It now keeps one set of promises everywhere: `count()`, `len()`, iteration, indexing, slicing and
95
+ truthiness agree under the bounds you set. Adding CI tests to verify compatability for Pythons 3.9 - 3.14.
96
+
97
+ **Breaking changes**
98
+
99
+ - Optional money and string getters return `None` when the API reports no
100
+ value; a reported zero is still `Decimal("0.00")`. Format with a fallback:
101
+ `f"{award.total_obligation or 0:,.2f}"`.
102
+ - `count()` honors `limit()` and `max_pages()` on every query. Read the server
103
+ total by counting before bounding (i.e. it will never return a count greater
104
+ that your set limit).
105
+ - A lazy load that fails now raises instead of silently answering `None`
106
+ forever. Only the API's "no such record" answers still read as absent data.
107
+ - `since()` and `until()` raise `ValidationError` for a pre-FY2008 bound (the lower
108
+ limit for USASpending data) or given an inverted range instead of silently matching
109
+ nothing useful.
110
+ - `order_by()` raises `ValidationError` on every builder for a direction other
111
+ than `"asc"` or `"desc"`.
112
+ - `client.tas.agencies` returns a query object rather than a list. So `== []` no
113
+ longer works as an emptiness check.
114
+ - A multi-level recipient ID now resolves away from the often-empty `R` level,
115
+ changing which record `find_by_recipient_id()` returns for it.
116
+ - Removed without alias: the internal `usaspending.utils.formatter` module,
117
+ module-level `logging_config.get_logger`, `Agency.__init__`'s unused
118
+ `subtier_data` parameter, and `AgencyAwardSummary.find_by_id`.
119
+ - `Award.start_date` and `end_date` changed precedence for hand-built payloads
120
+ carrying several date spellings; live API data is unaffected.
121
+
122
+ ### Added
123
+
124
+ - `Recipient.recipient_level`: which level of the recipient hierarchy a record
125
+ describes, as `"C"` (child), `"P"` (parent) or `"R"` (no parent).
126
+
127
+ - `usaspending.utils.current_fiscal_year()`: the federal fiscal year today
128
+ falls in, as an int. October 1 opens the fiscal year named for the calendar
129
+ year it ends in, so it reports 2026 on 2026-09-30 and 2027 on 2026-10-01.
130
+ Relocated from the removed `usaspending.utils.formatter.current_fiscal_year`.
131
+
132
+ ### Changed
133
+
134
+ - Optional money and string getters return `None` when the API reports no
135
+ value, instead of a fabricated `Decimal("0.00")` or `""`. A value the API
136
+ actually reports as zero still returns `Decimal("0.00")`, so callers can
137
+ finally tell "this award has no such figure" from "this figure is zero";
138
+ previously both produced `Decimal("0.00")` and the distinction was
139
+ unrecoverable.
140
+
141
+ Affected properties:
142
+
143
+ - `Award.total_obligation`, `Award.award_amount`
144
+ - `Award.covid19_obligations`, `Award.covid19_outlays`
145
+ - `Award.infrastructure_obligations`, `Award.infrastructure_outlays`
146
+ - `AwardAccount.total_transaction_obligated_amount` and its alias
147
+ `AwardAccount.obligated_amount`
148
+ - `Funding.transaction_obligated_amount` and `Funding.gross_outlay_amount`
149
+ - `Location.zip5` and `Location.district`
150
+ - `Award.type`, its alias `Award.award_type_code`, `Award.type_description`,
151
+ and `Contract.contract_award_type`, which delegates to it. Award-type
152
+ dispatch is unaffected: `create_award` reads the raw payload, not these
153
+ properties.
154
+
155
+ **Migrating.** Any f-string or arithmetic on these needs a fallback:
156
+
157
+ ```python
158
+ # Before
159
+ print(f"${award.total_obligation:,.2f}")
160
+ # After
161
+ print(f"${award.total_obligation or 0:,.2f}")
162
+ ```
163
+
164
+ **This can break working code.** Comparisons against zero change meaning
165
+ deliberately: `award.covid19_obligations == 0` was true for every award
166
+ without COVID-19 funding and is now false, while `is None` identifies
167
+ exactly those awards. On live golden-master data the COVID-19 and
168
+ infrastructure properties and `Funding.transaction_obligated_amount` are the
169
+ ones that move in practice; `total_obligation`, `award_amount`, `zip5` and
170
+ `district` were unchanged there, because the API does report them.
171
+ (`Grant.transaction_obligated_amount` already returned `None` for the same
172
+ property name.) `Award.award_identifier` deliberately still
173
+ returns `""`: it is annotated `-> str`, so there was no `Optional` contract
174
+ to honor. Documentation examples that formatted money were given the same
175
+ `or 0` fallback, and three examples referencing nonexistent properties
176
+ (`award.amount`, `award.recipient_name`, `child.obligated_amount`) were
177
+ corrected along the way.
178
+
179
+ - `count()` now honors `limit()` and `max_pages()` on every query, so
180
+ `count()`, `len(query)` and `len(query.all())` agree under the bounds a
181
+ caller sets. Which answer
182
+ you got used to depend on how the endpoint reports a total: builders backed
183
+ by a count endpoint or page metadata returned the server's figure whatever
184
+ the caller asked for (`limit(3).count()` said 510 while the query yielded
185
+ three rows), while builders that count by walking pages already stopped at
186
+ the bound.
187
+
188
+ **This can break working code** that used `count()` on a bounded query to
189
+ read the server's total. Count before bounding: `search.count()` is the
190
+ total under those filters, `search.limit(3).count()` is what the bounded
191
+ query yields.
192
+
193
+ `config.default_result_limit` is deliberately excluded from counts; it
194
+ still caps iteration, so an unbounded query over more than 10,000 rows
195
+ counts higher than it yields. That exclusion is itself a change for the
196
+ builders whose count pages the result set: `len(award.funding)` on a result
197
+ set larger than the default used to report the cap and now reports the true
198
+ total. Bounds that forbid every result are answered without a request:
199
+ `limit(0)` and `max_pages(0)` count 0 without touching the API, and for an
200
+ in-memory query `bool(query.limit(0))` was True and is now False.
201
+
202
+ - A lazy load that fails no longer turns a model into a permanent source of
203
+ `None`. `Agency` and `Recipient` reported every failure as absent data and
204
+ latched, so one 500, one dropped connection or one read after
205
+ `client.close()` left every lazy property answering `None` for the rest of
206
+ the model's life, with no way to ask again.
207
+
208
+ **This can break working code**, since a failure that used to be silent now
209
+ raises. Only the API's answer that there is no such record is still reported
210
+ as absent data: HTTP 400, 404 and 422 leave the model fetched and answering
211
+ `None`, as does a model built with no id. Everything else now reaches the
212
+ caller, including server errors, rate limits, connection errors, timeouts,
213
+ and the `DetachedInstanceError` from a closed session. The model is left
214
+ unfetched, so the next access retries, and a model whose load failed while
215
+ detached now loads after `reattach()`. `Award` already raised, so it is
216
+ unchanged.
217
+
218
+ **Migrating.** Force the load once rather than guarding each property read,
219
+ which would pay the full retry ladder per read (up to about 88 seconds of
220
+ backoff at default settings):
221
+
222
+ ```python
223
+ try:
224
+ agency.fetch_all_details()
225
+ except (USASpendingError, requests.RequestException):
226
+ logger.warning("Agency details unavailable")
227
+ return None
228
+ print(agency.mission)
229
+ ```
230
+
231
+ - `award.transactions.since()` and `until()` now reject a bound the API cannot
232
+ answer, matching what `time_period()` already enforced. A bound before
233
+ FY2008 begins (2007-10-01) matched every row, so `since("1999-01-01")`
234
+ looked like a filter and did nothing; an inverted range yielded no rows,
235
+ indistinguishable from an award with no transactions in range. Both now
236
+ raise `ValidationError`, checked by whichever of the two calls comes second.
237
+
238
+ **This can break working code**: if you relied on an inverted range to
239
+ select nothing, use `limit(0)` or skip the query; if you relied on a
240
+ pre-FY2008 bound as a harmless no-op, clamp it to `2007-10-01` or drop it.
241
+ Equal bounds still select that single day, and 2007-10-01 itself is valid.
242
+
243
+ - `order_by()` now validates its direction on every builder. Some builders
244
+ already raised, the client-side queries accepted any string and silently
245
+ sorted ascending, and the paginated searches passed the caller's spelling
246
+ through to the API, so `order_by("amount", "sideways")` put
247
+ `"order": "sideways"` on the wire. Every builder now raises
248
+ `ValidationError` for a direction other than `"asc"` or `"desc"`.
249
+
250
+ **This can break working code** that passed an unrecognized direction and
251
+ relied on whatever came back: pass `"asc"` or `"desc"`. Which fields each
252
+ builder accepts is unchanged; a few validator messages were unified in
253
+ wording only, and `time_period()`'s error messages are unchanged.
254
+
255
+ - `client.tas.agencies` returns a `TASAgenciesQuery` instead of
256
+ `list[Agency]`, matching `Agency.federal_accounts` and
257
+ `FederalAccount.tas_codes`. Iteration, `len()`, indexing and slicing work
258
+ unchanged, so most code needs no edit.
259
+
260
+ **This can break working code** that requires a real list: call `.all()`,
261
+ and use `if not query:` rather than `== []` as the emptiness check.
262
+
263
+ In exchange the level is fetched once, filtering costs no further requests,
264
+ and there is no shared list for one caller to corrupt for the next.
265
+ `TASAgenciesQuery` is exported from `usaspending.queries`, with `code()`,
266
+ `codes()` and `description()` filters that were previously unreachable.
267
+
268
+ - `Agency.federal_accounts` and `FederalAccount.tas_codes` fetch their level
269
+ of the TAS tree once per model instead of once per read. No call site
270
+ changes; the request count does. Walking an agency's 16 accounts and
271
+ reading each one's TAS codes cost 33 requests and now costs 17, the floor
272
+ set by the API's one-request-per-level shape; the same walk with in-memory
273
+ `fiscal_year()` filters fell from 49 or 99 requests to the same 17.
274
+
275
+ The tradeoff is staleness: a long-lived `Agency` keeps reporting the
276
+ accounts it first saw. Construct a fresh model to pick up changes, or
277
+ `del agency._federal_accounts_level` to drop the cache; a non-recursive
278
+ `reattach()` also discards it, while a recursive one rebinds the cached
279
+ models in place. This cache is not the one `config` describes:
280
+ `cache_enabled` governs HTTP responses with a TTL, while this one is always
281
+ on, per model instance, without one, so turning response caching off does
282
+ not make these properties re-read. The retained level costs about 0.2 MB
283
+ for a 16-account agency, so a walk over many agencies should drop each
284
+ model as it goes.
285
+
286
+ - `Award.start_date` and `end_date` delegate to `period_of_performance`
287
+ rather than keeping their own copy of the key list, which had drifted from
288
+ the period model's. Three behaviors moved: where a payload carries both the
289
+ flat and the nested spelling, the nested `period_of_performance` object now
290
+ wins; the `"Period of Performance End Date"` alias, which no recorded API
291
+ response carries, is no longer read; and `"Base Obligation Date"` now
292
+ yields to `"Period of Performance Start Date"` where a payload carries
293
+ both.
294
+
295
+ **This can break working code** that feeds `Award` hand-built payloads and
296
+ relies on any old precedence; read `award.raw` directly where the payload's
297
+ spelling matters. Live API responses never carry disagreeing spellings.
298
+ Cost moved rather than vanished: dates parse once on construction, so the
299
+ first read of a single date is slower, repeat reads are roughly four times
300
+ faster, and the crossover lands around six reads; against 0.7.3 every
301
+ measured pattern is still faster.
302
+
303
+ - Date parsing reads the shape the API actually sends about 11 times faster.
304
+ The converter tried seven `strptime` formats in order; plain `YYYY-MM-DD`
305
+ accounts for nearly all API date values and now reads through
306
+ `date.fromisoformat` behind a shape guard, taking 0.18 us rather than
307
+ 2.06 us. Reading `action_date` across a 5000-transaction page falls from
308
+ 11.0 ms to 1.7 ms. CPU only: no request or value changes, and every format
309
+ accepted before is still accepted, verified on Python 3.9 through 3.14.
310
+ The guard exists because 3.11 widened `fromisoformat` to accept forms the
311
+ 3.9 floor refuses, such as `20250829` and ISO week dates; without it the
312
+ accepted set would depend on the interpreter.
313
+
314
+ - Title casing now emits a `UserWarning` when `special_cases.yaml` exists but
315
+ cannot be read as a list, and another naming colliding entries (two
316
+ spellings that lowercase to the same key), where before the problem went
317
+ only to the log and names were silently mis-cased. It still degrades to no
318
+ special casing rather than raising, because casing runs inside `__repr__`
319
+ and a cosmetic problem must not break debuggers or logging; escalate with
320
+ `-W error::UserWarning` if wanted. A merely absent file still warns only in
321
+ the log, since an installation can legitimately lack it. The one duplicate
322
+ entry in the shipped data file was removed.
323
+
324
+ - `to_int` reads numeric API fields leniently: a non-numeric value now yields
325
+ `None` where the integer counts on `Funding`, `SubAward`,
326
+ `TreasuryAccountSymbol` and `Award.subaward_count` previously raised
327
+ `ValueError`.
328
+
329
+ - `SubAward.amount`'s return annotation is corrected from `float | None` to
330
+ `Decimal | None` to match its actual runtime type; no runtime change.
331
+
332
+ ### Deprecated
333
+
334
+ - `parse_date_string`'s `format_str` parameter, scheduled for removal in a
335
+ future release. A non-default pattern still parses with the pattern the
336
+ caller supplied, still raises `ValidationError` for a value it cannot read,
337
+ and now emits a `DeprecationWarning`. Omitting the parameter, or passing
338
+ its default, is unchanged and warns about nothing.
339
+
340
+ ### Fixed
341
+
342
+ - Building a query's payload no longer mutates the filters that built it.
343
+ When two filters serialized under one key, aggregation extended the first
344
+ filter's own list in place, and since clones share filter objects, a
345
+ derived query's payload grew on every build and corrupted its parent:
346
+ `q1 = search().keywords("alpha")` followed by `q2 = q1.keywords("beta")`
347
+ left `q1` sending `["alpha", "beta", "beta"]`. Affected chained `keywords`,
348
+ `award_type_codes`, simple `psc_codes` and `treasury_account_components`
349
+ filters.
350
+
351
+ - `client.recipients.find_by_recipient_id()` no longer addresses the wrong
352
+ record for a recipient ID carrying several levels, such as
353
+ `"<hash>-['C', 'R']"`. Normalization was implemented twice with algorithms
354
+ that disagreed, and the finder's choice, `R`, is the record that reports
355
+ little or no spending for most such IDs, so it could report a recipient
356
+ with real spending as having none and no parent. Both paths now avoid the
357
+ `R` level whenever another is available; to address a level explicitly,
358
+ pass the suffix yourself (see `Recipient.recipient_level` under Added).
359
+ `client.spending.search().recipient_id()` also normalizes its argument now,
360
+ so a multi-level ID copied out of `raw` produces a filter the API can
361
+ match.
362
+
363
+ - `award.transactions` no longer reports a count that disagrees with what it
364
+ yields. `since()` and `until()` filter in memory, but the count came from
365
+ the API, so it included transactions the caller would never see: `count()`
366
+ and `len()` said three where iteration yielded two, slices disagreed with
367
+ `len()`, and `query[-1]` raised `IndexError`. All of them now agree, and
368
+ `limit(1)` means one matching row rather than one row that may then be
369
+ discarded.
370
+
371
+ - A `Recipient` built directly from an award search result now reports its
372
+ `uei` and `location`, which read only the nested spellings a detail
373
+ response sends. `award.recipient.uei` worked because `Award` repaired the
374
+ keys on the way past; `Recipient(row, client).uei` for the same row did
375
+ not. `PeriodOfPerformance` had the same gap for `"Base Obligation Date"`.
376
+ Both models now read every spelling of their own fields, and `Award` hands
377
+ its payload over rather than reproducing the mapping; nested spellings
378
+ still take precedence where a payload carries both.
379
+
380
+ - Indexing and slicing an in-memory query now read the limited collection:
381
+ `agency.federal_accounts.limit(3)[:]` returned every account and
382
+ `limit(3)[5]` returned a row; the slice now holds three rows and the index
383
+ raises `IndexError`, agreeing with `len()` and `.all()`. Present in 0.7.3
384
+ for every in-memory query; the new `tas.agencies` query made it visible.
385
+
386
+ - `Transaction` instances no longer compare equal regardless of their data. A
387
+ fieldless `@dataclass` declaration generated an `__eq__` comparing empty
388
+ tuples, so any two transactions were equal and none was hashable. They now
389
+ compare by identity, like every other model, and can be used in sets and as
390
+ dict keys.
391
+
392
+ - `Recipient.parents` returns a fresh list on each read, so sorting or
393
+ popping the result no longer corrupts the model.
394
+
395
+ - Iterating `idv.child_awards` no longer raises
396
+ `AttributeError: 'str' object has no attribute 'get'` when reading the four
397
+ agency properties. The `/idvs/awards/` endpoint reuses those keys for a
398
+ plain agency-name string, which the model accepted as a record; all four
399
+ now return `None` there, with the name still available via `raw`. Present
400
+ in 0.7.3.
401
+
402
+ - `reattach(recursive=True)` now reaches every nested model that holds the
403
+ client. It recognized only lazy-loading models, so `AwardAccount`,
404
+ `FederalAccount`, `Funding`, `SubAward` and `TreasuryAccountSymbol` were
405
+ stepped over and could raise `DetachedInstanceError` after the old client
406
+ was collected. The walk now tests for holding a client, and a recursive
407
+ reattach rebinds cached TAS levels in place: a 16-account agency costs no
408
+ requests after a reattach, where discarding the caches cost 17.
409
+
410
+ - `.all()` no longer spends an API request on a count it discards. `list()`
411
+ asks for a length hint, which called `count()` purely to size the list, so
412
+ every `.all()` paid one extra request, and on the five builders whose count
413
+ pages the result set (award funding, spending search, IDV child awards,
414
+ unscoped subaward search, and transactions with a date bound set), a second
415
+ complete pass: 250 date-bounded transactions cost 6 requests, now 3.
416
+ `len(query)` still counts, as must everything that consults the hint
417
+ (`list(query)`, `tuple(query)`, `sorted(query)`, `[*query]`); `set(query)`,
418
+ `sum(...)`, `in` and comprehensions were never affected. Prefer `.all()` or
419
+ a plain loop. The 13 docstring examples that taught `list(...)` now show
420
+ `.all()`.
421
+
422
+ - `if query:` no longer costs a full pagination. Truthiness fell through to
423
+ `__len__`, which on the five paging-count builders walked every page to
424
+ settle a single bit (250 date-bounded transactions: 3 requests, now 1). It
425
+ reads one row via `first()`; in-memory queries answer from their own count
426
+ instead.
427
+
428
+ - `FederalAccount.count` no longer fires an API request when the count is
429
+ already in the response; the fallback was an eagerly evaluated default
430
+ argument, so every access paid for a request whose result was discarded.
431
+
432
+ - `query.limit(0).first()` returned a row, contradicting `.all()` and
433
+ `len()`. It now respects the zero, which is also what makes `__bool__`
434
+ correct for a zero-limit query.
435
+
436
+ - The two date parsers now honor their documented contracts for unusable
437
+ values. The strict filter parser raised `TypeError` from inside `strptime`
438
+ for a non-string (most often a threaded-through `None`) rather than the
439
+ promised `ValidationError` naming the field; the lenient API-payload
440
+ converter could leak the same `TypeError` rather than answering `None`.
441
+ The error now quotes the documented `YYYY-MM-DD` form rather than a
442
+ strftime pattern, and shows the offending value with `repr`.
443
+
444
+ - Passing a `datetime` where a date filter documents accepting a `date` no
445
+ longer raises `TypeError` from the FY2008 floor check. `time_period()`,
446
+ `since()` and `until()` narrow a datetime to its date portion; the wire
447
+ payload was never affected.
448
+
449
+ - A `TransactionsSearch` with no award set raises `ValidationError` from
450
+ `count()` instead of requesting `/awards/count/transaction/None/`.
451
+
452
+ - `Award._load_agency_data` raises `ValidationError` rather than a bare
453
+ `ValueError` for an invalid `agency_type`; `ValidationError` subclasses
454
+ `ValueError`, so existing handlers are unaffected.
455
+
456
+ ### Removed
457
+
458
+ - The internal `usaspending.utils.formatter` module, split into
459
+ `usaspending.utils.dates`, `usaspending.utils.numbers` and
460
+ `usaspending.utils.textcase`. Every symbol it held was absent from
461
+ `__all__` and the README, so there is no compatibility shim. Only
462
+ `current_fiscal_year` gets a documented new home, in `usaspending.utils`,
463
+ as noted under Added.
464
+
465
+ - `Agency.__init__`'s third parameter, `subtier_data`. It was stored and
466
+ never read; drop the argument, and use `Award.funding_subtier_agency` or
467
+ `Award.awarding_subtier_agency` to reach subtier data.
468
+
469
+ - Module-level `usaspending.logging_config.get_logger`; the classmethod
470
+ `USASpendingLogger.get_logger` is the one entry point.
471
+
472
+ - `AgencyAwardSummary.find_by_id`, which only ever raised
473
+ `NotImplementedError` pointing at `get_awards_summary()`. The class is no
474
+ longer a `SingleResourceBase`; that base class remains, unchanged, under
475
+ the query classes that use it.
476
+
477
+ ## [0.7.3] - 2026-07-05
478
+
479
+ ### Added
480
+
481
+ - `AwardsSearch.object_classes(*codes)`: filter awards by federal object class codes (per Office of Management and Budget Circular A-11) on the `/search/spending_by_award/` and `/search/spending_by_award_count/` endpoints. Pass codes such as `"10"` or `"252"`, not names; multiple codes use OR logic.
482
+ - `SubAwardsSearch.object_classes()` raises `ValidationError`. The USASpending API accepts the `object_classes` filter for award searches only and returns HTTP 422 when it is combined with `spending_level=subawards`.
483
+ - `client.downloads.search()` emits a `UserWarning` when the query contains an `object_classes` filter, because the `/api/v2/download/search/` endpoint drops the key. The filter is still forwarded unmodified.
484
+ - Known upstream limitation as of 2026-07-05: the production API accepts `object_classes` but the rollout is incomplete. The search endpoint returns zero results for any object class, the count endpoint does not narrow, and the subawards rejection is not yet enforced server side. The filter should begin returning data once USAspending finishes deploying the feature and populating its award index.
485
+ - `tests/test_readme_examples.py`: integration tests that extract the README's Python code blocks and execute the runnable ones verbatim against the live API, so broken or rotted examples fail mechanically. A network-free guard in the default suite catches extraction drift. The live `client` fixture moved to `tests/conftest.py` and is now shared with `tests/test_integration.py`.
486
+
487
+ ### Documentation
488
+
489
+ - Corrected the `api-docs-links.md` subawards entry: `SubAwardsSearch` POSTs `/api/v2/search/spending_by_award/` with `subawards=true` and `spending_level=subawards` (inheriting `AwardsSearch`) rather than calling `/api/v2/subawards/`.
490
+ - Added missing `api-docs-links.md` entries for `/api/v2/idvs/awards/` (IDV child awards) and the TAS filter-tree endpoints (`/api/v2/references/filter_tree/tas/` and its `{toptier_code}/` and `{toptier_code}/{federal_account}/` variants).
491
+ - An upstream API compatibility review of USASpending API releases from April through June 2026 found no breaking changes for this library. The 0.7.2 release already handles the `spending_by_award` Pydantic migration (HTTP 422 validation errors), the `program_activities` filter rename, and the new F001 through F010 assistance type codes.
492
+ - Revalidated all README examples against live USAspending data. Fixed two broken chains (`location.full_address` is actually `location.formatted_address`; a subaward's place of performance is on the subaward, not its recipient), refreshed stale sample outputs, and added examples for the `client.spending` category rollups and the list-style `len(query)` interface.
493
+
494
+ ## [0.7.2] - 2026-06-05
495
+
496
+ ### Added
497
+
498
+ - `client.downloads.search(query, ...)` backed by the new `/api/v2/download/search/` endpoint, which bundles award, transaction, and subaward data into a single download built from an awards search query. Supports `file_format`, `spending_level`, `columns`, and `limit`.
499
+ - `QueryBuilder.to_filters_payload()`: public accessor returning the assembled API `filters` object for a query without executing a search.
500
+
501
+ ### Changed
502
+
503
+ - HTTP 422 (Unprocessable Entity) validation responses are now surfaced as `APIError` with the response detail instead of a generic `HTTPError`. This aligns with the USASpending API's move to Pydantic request validation on `/search/spending_by_award/`.
504
+
505
+ ### Deprecated
506
+
507
+ - `program_activity()` is deprecated and now forwards to `program_activities()`, emitting a `DeprecationWarning`. The USASpending API removed the `program_activity` filter from `/search/spending_by_award/` in favor of `program_activities`. Existing calls continue to work; integer codes are forwarded as string `code` values.
508
+
509
+ ## [0.7.1] - 2026-04-16
510
+
511
+ ### Added
512
+
513
+ - Public export of `DetachedInstanceError` so callers can `from usaspending import DetachedInstanceError` as documented in the README.
514
+ - `config.allowed_download_hosts`: an allow-list of hostnames the library will fetch binary downloads from. Defaults to `{"api.usaspending.gov", "files.usaspending.gov"}`.
515
+ - `config.validate()` now rejects malformed `base_url`, CR/LF in `user_agent`, and empty `cache_dir`.
516
+
517
+ ### Changed
518
+
519
+ - `ValidationError` now inherits from both `USASpendingError` and the built-in `ValueError`. Callers that previously caught `ValueError` for invalid parameters continue to work unchanged; new code can catch the library-specific `ValidationError`.
520
+ - `RateLimiter` now raises `ValidationError` (rather than a bare `ValueError`) for invalid `max_calls`/`period` arguments. Non-breaking due to the dual inheritance above.
521
+ - `RecipientsResource.find_by_recipient_id` is now annotated as non-Optional. This matches its long-standing behavior of raising on miss via the direct-ID lookup path; runtime behavior is unchanged. `find_by_duns` and `find_by_uei` remain Optional because they use the search-delegation path.
522
+ - `Agency.get_obligations` return annotation corrected from `float | None` to `Decimal | None` to match actual runtime type.
523
+ - `Agency.total_obligations` now applies `Decimal` coercion to stored values to match its declared return type.
524
+ - `TransactionsSearch._award_id` and `FundingSearch._award_id` annotated as `str | None` rather than `str` (they are always `None` until set).
525
+ - `time_period()` now rejects date ranges where `end_date` is earlier than `start_date`.
526
+
527
+ ### Fixed
528
+
529
+ - `to_date()` now extracts the `date` portion from `datetime` inputs instead of passing them through unchanged.
530
+ - `IDVChildAwardsSearch._clone()` no longer silently drops `_filter_objects`, `_max_pages`, `_order_by`, and `_order_direction` when chaining.
531
+ - `SubAwardsSearch._clone()` now delegates to `super()._clone()` so base-class state changes propagate.
532
+ - `AwardAccountsQuery._clone()` no longer copies a stale `_cached_count` across filter changes.
533
+ - `Agency.latest_action_date` no longer crashes when the award-summary endpoint returns no data.
534
+ - `round_to_millions()` cleaned up redundant disjunct in threshold check.
535
+ - Removed unused `DEFAULT_KEEP_UPPERCASE` set, which also contained a latent missing-comma bug that concatenated two entries.
536
+ - Fixed malformed `Raises:` block in `AwardResource.find_by_generated_id` docstring.
537
+ - `Agency.id` was missing its return-type annotation.
538
+
539
+ ### Security
540
+
541
+ - Binary downloads (`_download_binary_file`) now require `https` and a hostname on `config.allowed_download_hosts`, mitigating SSRF if API responses are tampered with.
542
+ - File-backed cache directory is now created with `0700` and tightened to `0700` if it already exists with looser bits. Protects the pickle-backed cache on shared hosts.
543
+ - ZIP extraction creates the extract directory with `0700`, detects absolute paths with stdlib `posixpath.isabs` / `ntpath.isabs` (covers Windows drive letters and UNC prefixes), and uses `normpath`-based prefix comparison for traversal detection.
544
+
545
+ ### Removed
546
+
547
+ - Dead code: `get_past_fiscal_years`, `custom_titlecase_callback`, `parse_agency_type`, `parse_agency_tier`, `QueryBuilder._fetch_page`, empty `AwardsSearch.__init__`, redundant `AwardsSearch._clone` override, unreachable `try/except` in `_derived_award_identifier`.
548
+
549
+ ## [0.7.0] - 2026-04-12
550
+
551
+ Previous release. See git history for details.