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