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.
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/.gitignore +3 -0
- usaspending_orm-0.9.0/CHANGELOG.md +551 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/PKG-INFO +83 -9
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/README.md +80 -8
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/api-docs-links.md +19 -1
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/pyproject.toml +18 -1
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/__init__.py +2 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/client.py +2 -2
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/logging_config.py +0 -21
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/agency.py +41 -29
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/award.py +186 -243
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/award_account.py +10 -9
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/award_factory.py +46 -13
- usaspending_orm-0.9.0/src/usaspending/models/award_identifier.py +73 -0
- usaspending_orm-0.9.0/src/usaspending/models/award_types.py +379 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/base_model.py +79 -46
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/contract.py +13 -80
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/district_spending.py +2 -22
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/download.py +1 -1
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/federal_account.py +37 -9
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/funding.py +12 -15
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/grant.py +2 -2
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/idv.py +14 -105
- usaspending_orm-0.9.0/src/usaspending/models/lazy_record.py +151 -0
- usaspending_orm-0.9.0/src/usaspending/models/loan.py +83 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/location.py +7 -6
- usaspending_orm-0.9.0/src/usaspending/models/period_of_performance.py +121 -0
- usaspending_orm-0.9.0/src/usaspending/models/procurement_award.py +117 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/recipient.py +79 -49
- usaspending_orm-0.9.0/src/usaspending/models/recipient_spending.py +72 -0
- usaspending_orm-0.9.0/src/usaspending/models/spending.py +143 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/state_spending.py +2 -22
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/subaward.py +11 -17
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/subtier_agency.py +10 -3
- usaspending_orm-0.9.0/src/usaspending/models/transaction.py +582 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/treasury_account_symbol.py +3 -4
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/__init__.py +5 -1
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/agencies_search.py +12 -35
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/agency_award_summary.py +17 -51
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/agency_query.py +3 -11
- usaspending_orm-0.9.0/src/usaspending/queries/award_accounts_query.py +85 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/award_query.py +1 -1
- 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
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/awarding_agencies_search.py +7 -14
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/awards_search.py +46 -139
- usaspending_orm-0.9.0/src/usaspending/queries/base_query.py +239 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/client_side_query_builder.py +51 -28
- usaspending_orm-0.9.0/src/usaspending/queries/federal_accounts_query.py +112 -0
- usaspending_orm-0.9.0/src/usaspending/queries/filter_tree_query.py +263 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/filters.py +80 -6
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/funding_agencies_search.py +7 -14
- usaspending_orm-0.9.0/src/usaspending/queries/funding_search.py +66 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/idv_child_awards.py +14 -61
- usaspending_orm-0.9.0/src/usaspending/queries/mixins.py +154 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/query_builder.py +415 -171
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/recipient_query.py +3 -31
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/recipients_search.py +7 -6
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/single_resource_base.py +4 -2
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/spending_search.py +15 -62
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/sub_agency_query.py +19 -57
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/queries/subawards_search.py +7 -27
- usaspending_orm-0.9.0/src/usaspending/queries/tas_agencies_query.py +72 -0
- usaspending_orm-0.9.0/src/usaspending/queries/tas_codes_query.py +122 -0
- usaspending_orm-0.9.0/src/usaspending/queries/transactions_search.py +239 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/resources/agency_resource.py +30 -22
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/resources/award_accounts_resource.py +1 -1
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/resources/award_resource.py +12 -22
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/resources/funding_resource.py +4 -4
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/resources/recipients_resource.py +48 -21
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/resources/spending_resource.py +7 -3
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/resources/subawards_resource.py +5 -4
- usaspending_orm-0.9.0/src/usaspending/resources/tas_resource.py +65 -0
- usaspending_orm-0.9.0/src/usaspending/resources/transactions_resource.py +63 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/utils/__init__.py +2 -0
- usaspending_orm-0.9.0/src/usaspending/utils/dates.py +139 -0
- usaspending_orm-0.9.0/src/usaspending/utils/numbers.py +99 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/utils/retry.py +4 -9
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/utils/special_cases.yaml +1 -2
- usaspending_orm-0.9.0/src/usaspending/utils/textcase.py +379 -0
- usaspending_orm-0.9.0/src/usaspending/utils/validations.py +388 -0
- usaspending_orm-0.9.0/tests/fixtures/golden/agency.json +74 -0
- usaspending_orm-0.9.0/tests/fixtures/golden/award_account_first_result.json +29 -0
- usaspending_orm-0.9.0/tests/fixtures/golden/award_contract.json +190 -0
- usaspending_orm-0.9.0/tests/fixtures/golden/award_contract_awarding_agency.json +74 -0
- usaspending_orm-0.9.0/tests/fixtures/golden/award_contract_period_of_performance.json +15 -0
- usaspending_orm-0.9.0/tests/fixtures/golden/award_contract_place_of_performance.json +23 -0
- usaspending_orm-0.9.0/tests/fixtures/golden/award_contract_recipient.json +51 -0
- usaspending_orm-0.9.0/tests/fixtures/golden/award_grant.json +198 -0
- usaspending_orm-0.9.0/tests/fixtures/golden/award_idv.json +190 -0
- usaspending_orm-0.9.0/tests/fixtures/golden/award_loan.json +191 -0
- usaspending_orm-0.9.0/tests/fixtures/golden/award_parent_of_contract.json +136 -0
- usaspending_orm-0.9.0/tests/fixtures/golden/funding_first_result.json +35 -0
- usaspending_orm-0.9.0/tests/fixtures/golden/idv_child_award_first_result.json +192 -0
- usaspending_orm-0.9.0/tests/fixtures/golden/recipient.json +60 -0
- usaspending_orm-0.9.0/tests/fixtures/golden/search_contract_first_result.json +190 -0
- usaspending_orm-0.9.0/tests/fixtures/golden/search_subaward_first_result.json +91 -0
- usaspending_orm-0.9.0/tests/fixtures/golden/spending_by_recipient_first_result.json +68 -0
- usaspending_orm-0.9.0/tests/fixtures/golden/spending_by_state_first_result.json +17 -0
- usaspending_orm-0.9.0/tests/fixtures/golden/transaction_first_result.json +18 -0
- usaspending_orm-0.9.0/tests/fixtures/public_api_surface.json +1489 -0
- usaspending_orm-0.9.0/tests/fixtures/spending_by_transaction.json +214 -0
- usaspending_orm-0.9.0/tests/fixtures/spending_by_transaction_count.json +10 -0
- usaspending_orm-0.9.0/tests/fixtures/spending_by_transaction_mixed.json +210 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/mocks/README.md +29 -3
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/mocks/mock_client.py +70 -9
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/mocks/response_builder.py +5 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_agency.py +11 -38
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_agency_subagencies.py +5 -5
- usaspending_orm-0.9.0/tests/models/test_agency_tas_integration.py +364 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_award_account.py +16 -2
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_award_agency_integration.py +63 -0
- usaspending_orm-0.9.0/tests/models/test_award_category_registry.py +159 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_award_common.py +14 -62
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_award_decimal_precision.py +39 -12
- usaspending_orm-0.9.0/tests/models/test_award_identifier.py +81 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_award_types.py +10 -4
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_base_model.py +60 -2
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_federal_account.py +130 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_funding.py +22 -8
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_lazy_load.py +161 -1
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_location.py +9 -11
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_period_of_performance.py +113 -1
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_recipient.py +188 -37
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_recipient_spending.py +6 -4
- usaspending_orm-0.9.0/tests/models/test_search_projection.py +175 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_subaward.py +16 -23
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_subtier_agency.py +4 -4
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_transaction.py +72 -1
- usaspending_orm-0.9.0/tests/models/test_transaction_search_projection.py +456 -0
- usaspending_orm-0.9.0/tests/models/test_volatile_field_mapping.py +177 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_agency_award_summary.py +17 -17
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_award_accounts_query.py +28 -4
- usaspending_orm-0.9.0/tests/queries/test_award_transactions_search.py +492 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_client_side_query_builder.py +94 -2
- usaspending_orm-0.9.0/tests/queries/test_count_respects_limits.py +217 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_federal_accounts_query.py +8 -1
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_query_builder.py +52 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_query_builder_list_behavior.py +98 -49
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_query_builder_pagination.py +3 -2
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_recipient_query.py +6 -2
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_search_query_builder.py +38 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_spending_search.py +12 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_sub_agency_query.py +2 -2
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_subawards_search.py +2 -2
- usaspending_orm-0.9.0/tests/queries/test_tas_agencies_query.py +84 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_tas_codes_query.py +61 -1
- usaspending_orm-0.9.0/tests/queries/test_transactions_search.py +504 -0
- usaspending_orm-0.9.0/tests/resources/test_recipients_resource.py +71 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/resources/test_tas_resource.py +56 -8
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/resources/test_transactions_resource.py +22 -3
- usaspending_orm-0.9.0/tests/snapshot_support.py +70 -0
- usaspending_orm-0.9.0/tests/test_characterization.py +282 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_client_transactions.py +1 -1
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_download_resource.py +97 -0
- usaspending_orm-0.9.0/tests/test_golden_master_integration.py +707 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_integration.py +52 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_logging_config.py +0 -20
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_mock_client_example.py +11 -8
- usaspending_orm-0.9.0/tests/test_public_api_surface.py +388 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_readme_examples.py +1 -1
- usaspending_orm-0.9.0/tests/utils/test_dates.py +288 -0
- usaspending_orm-0.9.0/tests/utils/test_doctests.py +66 -0
- usaspending_orm-0.9.0/tests/utils/test_numbers.py +73 -0
- usaspending_orm-0.9.0/tests/utils/test_special_cases_data.py +118 -0
- usaspending_orm-0.7.3/tests/utils/test_text_formatter.py → usaspending_orm-0.9.0/tests/utils/test_textcase.py +267 -40
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/utils/test_validations.py +153 -0
- usaspending_orm-0.9.0/tests/volatile_fields.py +293 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/uv.lock +244 -2
- usaspending_orm-0.7.3/CHANGELOG.md +0 -81
- usaspending_orm-0.7.3/CLAUDE.md +0 -243
- usaspending_orm-0.7.3/poetry.lock +0 -585
- usaspending_orm-0.7.3/src/usaspending/models/award_types.py +0 -145
- usaspending_orm-0.7.3/src/usaspending/models/lazy_record.py +0 -90
- usaspending_orm-0.7.3/src/usaspending/models/loan.py +0 -115
- usaspending_orm-0.7.3/src/usaspending/models/period_of_performance.py +0 -75
- usaspending_orm-0.7.3/src/usaspending/models/recipient_spending.py +0 -84
- usaspending_orm-0.7.3/src/usaspending/models/spending.py +0 -103
- usaspending_orm-0.7.3/src/usaspending/models/transaction.py +0 -154
- usaspending_orm-0.7.3/src/usaspending/queries/award_accounts_query.py +0 -194
- usaspending_orm-0.7.3/src/usaspending/queries/base_query.py +0 -141
- usaspending_orm-0.7.3/src/usaspending/queries/federal_accounts_query.py +0 -205
- usaspending_orm-0.7.3/src/usaspending/queries/funding_search.py +0 -174
- usaspending_orm-0.7.3/src/usaspending/queries/tas_codes_query.py +0 -213
- usaspending_orm-0.7.3/src/usaspending/resources/tas_resource.py +0 -77
- usaspending_orm-0.7.3/src/usaspending/resources/transactions_resource.py +0 -40
- usaspending_orm-0.7.3/src/usaspending/utils/formatter.py +0 -449
- usaspending_orm-0.7.3/src/usaspending/utils/validations.py +0 -148
- usaspending_orm-0.7.3/tests/models/test_agency_tas_integration.py +0 -172
- usaspending_orm-0.7.3/tests/queries/test_transactions_search.py +0 -126
- usaspending_orm-0.7.3/tests/resources/test_recipients_resource.py +0 -29
- usaspending_orm-0.7.3/tests/utils/test_formatter.py +0 -301
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/LICENSE +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/cli/__init__.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/cli/download_award.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/config.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/download/__init__.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/download/job.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/download/manager.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/exceptions.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/__init__.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/models/recipient_business_categories.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/resources/__init__.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/resources/base_resource.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/resources/download_resource.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/src/usaspending/utils/rate_limit.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/__init__.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/conftest.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/agency.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/agency_autocomplete.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/agency_award_summary.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/agency_subagencies.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/accounts.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/award_funding_grant.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/contract.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/grant.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/idv.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/idv_awards.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/loan.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/search_results_contracts.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/search_results_grants.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/search_results_idvs.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/search_results_subawards.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/awards/transactions.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/download_assistance.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/download_search.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/download_status.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/recipient_university.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/recipients_search.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/spending_by_award_contracts.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/spending_by_district.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/spending_by_recipient.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/spending_by_state.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/tas_agencies.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/tas_codes.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/tas_federal_accounts.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/top_recipients_params.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/fixtures/top_recipients_response.json +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/mocks/__init__.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/mocks/test_mock_client.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/__init__.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_award_factory.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_award_lazy_loading.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_award_office_integration.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_award_type_constants.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_district_spending.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_recipient_business_categories.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_spending.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_state_spending.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/models/test_treasury_account_symbol.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_agencies_search.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_agency_query.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_awards_search.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_filters.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_funding_search.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_idv_child_awards.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_recipients_search.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_single_resource_base.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/queries/test_spending_search_count.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/resources/test_agency_resource.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/resources/test_award_resource.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/resources/test_funding_resource.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/resources/test_spending_resource.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/resources/test_subawards_resource.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_award_accounts_integration.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_cache_fallback.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_client.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_client_caching.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_client_funding.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_client_rate_limit.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_client_security.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_config.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_exceptions.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/test_spending_integration.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/utils/__init__.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/utils/decimal_helpers.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/utils/test_rate_limit.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/utils/test_retry.py +0 -0
- {usaspending_orm-0.7.3 → usaspending_orm-0.9.0}/tests/utils/test_retry_session_reset.py +0 -0
|
@@ -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.
|