From c17b66eba91c6b74b0766476e22ff3ec141b0e34 Mon Sep 17 00:00:00 2001 From: David Mikola Date: Wed, 15 Jul 2026 08:48:38 -0500 Subject: [PATCH 01/12] [DEV-15427] Fixed Incorrect Category Field Modification --- .../spending_by_category_views/spending_by_category.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py index a8dafdfcce..7e9ccc7d44 100644 --- a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py +++ b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py @@ -1,7 +1,7 @@ import copy import logging from abc import ABCMeta, abstractmethod -from dataclasses import dataclass +from dataclasses import dataclass, replace from typing import List, Optional, Union from django.conf import settings @@ -126,8 +126,10 @@ def validate_payload(self, request: Request) -> dict: def perform_search(self, original_filters: dict) -> dict: if self.spending_level == SpendingLevel.SUBAWARD: # Swap the agg_key fields for the equivalent Subaward fields, if applicable - self.category.agg_key = self.subaward_agg_key_mapper.get(self.category.agg_key, self.category.agg_key) - + self.category = replace( + self.category, + agg_key=self.subaward_agg_key_mapper.get(self.category.agg_key, self.category.agg_key) + ) query_with_filters = QueryWithFilters(QueryType.SUBAWARDS) filter_query = query_with_filters.generate_elasticsearch_query(self.filters) results = self.query_elasticsearch(filter_query) From 80865e7445d4f3bd9534084c1b6fbf82203d8128 Mon Sep 17 00:00:00 2001 From: David Mikola Date: Thu, 16 Jul 2026 10:20:57 -0500 Subject: [PATCH 02/12] [DEV-15427] Added New Category Instance per Request --- .../spending_by_category_views/spending_by_category.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py index 7e9ccc7d44..4a2f012099 100644 --- a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py +++ b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py @@ -32,7 +32,7 @@ logger = logging.getLogger(__name__) -@dataclass +@dataclass(frozen=True) class Category: name: str agg_key: str @@ -68,6 +68,10 @@ class AbstractSpendingByCategoryViewSet(APIView, metaclass=ABCMeta): def post(self, request: Request, *args, **kwargs) -> Response: original_filters = request.data.get("filters") + # Creates an instance copy for the category for each request + # Prevents modifications from previous requests affecting current requests + self.category = copy.deepcopy(self.category) + # Handles case where the request has already been validated by an implementation of the abstract class validated_payload = kwargs.get("validated_payload", self.validate_payload(request)) From 4fdf44faac1c921b244ec264c4c80f084a0f15d4 Mon Sep 17 00:00:00 2001 From: David Mikola Date: Thu, 23 Jul 2026 12:18:54 -0500 Subject: [PATCH 03/12] [DEV-15427] - Added test case to ensure posts are cached correctly --- .../test_spending_by_county.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/usaspending_api/search/tests/integration/spending_by_category/test_spending_by_county.py b/usaspending_api/search/tests/integration/spending_by_category/test_spending_by_county.py index 69e5e46d40..b45a3ddc60 100644 --- a/usaspending_api/search/tests/integration/spending_by_category/test_spending_by_county.py +++ b/usaspending_api/search/tests/integration/spending_by_category/test_spending_by_county.py @@ -75,3 +75,47 @@ def test_correct_response_of_empty_list(client, monkeypatch, elasticsearch_trans } assert resp.status_code == status.HTTP_200_OK, "Failed to return 200 Response" assert resp.json() == expected_response + +def test_category_dataclass_subaward_caching(client, monkeypatch, awards_and_transactions, + elasticsearch_subaward_index, elasticsearch_transaction_index): + # Tests that the Category dataclass is not being cached incorrectly + # Originally would happen when updated by subaward spending levels + # tested by making a spending_level subawards request and then requesting on transaction level + setup_elasticsearch_test(monkeypatch, elasticsearch_subaward_index) + + sub_resp = client.post( + "/api/v2/search/spending_by_category/county", + content_type="application/json", + data=json.dumps({ + "filters": {"time_period": [{"start_date": "2018-10-01", "end_date": "2020-09-30"}]}, + "spending_level": "subawards" + }), + ) + + assert sub_resp.status_code == status.HTTP_200_OK, "Failed to return 200 Response" + assert sub_resp.json().get("spending_level") == "subawards" + setup_elasticsearch_test(monkeypatch, elasticsearch_transaction_index) + + trn_resp = client.post( + "/api/v2/search/spending_by_category/county", + content_type="application/json", + data=json.dumps({ + "filters": {"time_period": [{"start_date": "2018-10-01", "end_date": "2020-09-30"}]}, + "spending_level": "transactions" + }), + ) + + expected_response = { + "category": "county", + "limit": 10, + "page_metadata": {"page": 1, "next": None, "previous": None, "hasNext": False, "hasPrevious": False}, + "results": [ + {"amount": 550005.0, "code": "001", "id": None, "name": "CHARLESTON", "total_outlays": None}, + {"amount": 5500.0, "code": "005", "id": None, "name": "TEST NAME", "total_outlays": None}, + {"amount": 50.0, "code": "005", "id": None, "name": "TEST NAME", "total_outlays": None}, + ], + "messages": _expected_messages(), + "spending_level": "transactions", + } + assert trn_resp.status_code == status.HTTP_200_OK, "Failed to return 200 Response" + assert trn_resp.json() == expected_response From a6e0b69132517d6b099a23ec7218cb27511a5f6e Mon Sep 17 00:00:00 2001 From: David Mikola Date: Thu, 23 Jul 2026 12:23:43 -0500 Subject: [PATCH 04/12] [Dev-15427] testcase lint fixes --- .../spending_by_category/test_spending_by_county.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/usaspending_api/search/tests/integration/spending_by_category/test_spending_by_county.py b/usaspending_api/search/tests/integration/spending_by_category/test_spending_by_county.py index b45a3ddc60..32af8bc15d 100644 --- a/usaspending_api/search/tests/integration/spending_by_category/test_spending_by_county.py +++ b/usaspending_api/search/tests/integration/spending_by_category/test_spending_by_county.py @@ -76,6 +76,7 @@ def test_correct_response_of_empty_list(client, monkeypatch, elasticsearch_trans assert resp.status_code == status.HTTP_200_OK, "Failed to return 200 Response" assert resp.json() == expected_response + def test_category_dataclass_subaward_caching(client, monkeypatch, awards_and_transactions, elasticsearch_subaward_index, elasticsearch_transaction_index): # Tests that the Category dataclass is not being cached incorrectly @@ -87,11 +88,11 @@ def test_category_dataclass_subaward_caching(client, monkeypatch, awards_and_tra "/api/v2/search/spending_by_category/county", content_type="application/json", data=json.dumps({ - "filters": {"time_period": [{"start_date": "2018-10-01", "end_date": "2020-09-30"}]}, - "spending_level": "subawards" + "filters": {"time_period": [{"start_date": "2018-10-01", "end_date": "2020-09-30"}]}, + "spending_level": "subawards" }), ) - + assert sub_resp.status_code == status.HTTP_200_OK, "Failed to return 200 Response" assert sub_resp.json().get("spending_level") == "subawards" setup_elasticsearch_test(monkeypatch, elasticsearch_transaction_index) @@ -100,8 +101,8 @@ def test_category_dataclass_subaward_caching(client, monkeypatch, awards_and_tra "/api/v2/search/spending_by_category/county", content_type="application/json", data=json.dumps({ - "filters": {"time_period": [{"start_date": "2018-10-01", "end_date": "2020-09-30"}]}, - "spending_level": "transactions" + "filters": {"time_period": [{"start_date": "2018-10-01", "end_date": "2020-09-30"}]}, + "spending_level": "transactions" }), ) From e14a7ae8087332d567ceed31455fae08e582d294 Mon Sep 17 00:00:00 2001 From: David Mikola Date: Fri, 24 Jul 2026 14:13:55 -0500 Subject: [PATCH 05/12] [Dev-15427] Added _category proprty --- .../spending_by_category_views/spending_by_category.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py index 4a2f012099..148106e834 100644 --- a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py +++ b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py @@ -48,8 +48,8 @@ class AbstractSpendingByCategoryViewSet(APIView, metaclass=ABCMeta): """ Abstract class inherited by the different spending by category endpoints. """ - - category: Category + + _category: Category filters: dict pagination: Pagination high_cardinality_categories: List[str] = ["recipient", "recipient_duns"] @@ -70,7 +70,7 @@ def post(self, request: Request, *args, **kwargs) -> Response: # Creates an instance copy for the category for each request # Prevents modifications from previous requests affecting current requests - self.category = copy.deepcopy(self.category) + self.category = copy.deepcopy(self._category) # Handles case where the request has already been validated by an implementation of the abstract class validated_payload = kwargs.get("validated_payload", self.validate_payload(request)) From 18451b52a10b44cb322f39913321b311f990e964 Mon Sep 17 00:00:00 2001 From: David Mikola Date: Fri, 24 Jul 2026 16:47:10 -0500 Subject: [PATCH 06/12] Ruff lint fix --- .../v2/views/spending_by_category_views/spending_by_category.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py index 148106e834..16045e2df6 100644 --- a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py +++ b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py @@ -48,7 +48,7 @@ class AbstractSpendingByCategoryViewSet(APIView, metaclass=ABCMeta): """ Abstract class inherited by the different spending by category endpoints. """ - + _category: Category filters: dict pagination: Pagination From d3223b8b7e42970d3ecb88e6dfb09c8c56329065 Mon Sep 17 00:00:00 2001 From: David Mikola Date: Mon, 27 Jul 2026 09:40:28 -0500 Subject: [PATCH 07/12] [Dev-15427] Updated Category Declaration for Child Classes --- .../spending_by_category_views/spending_by_locations.py | 8 ++++---- .../spending_by_category_views/spending_by_recipient.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_locations.py b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_locations.py index 85c52d9c07..9a98d505eb 100644 --- a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_locations.py +++ b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_locations.py @@ -186,7 +186,7 @@ class CountyViewSet(AbstractLocationViewSet): endpoint_doc = "usaspending_api/api_contracts/contracts/v2/search/spending_by_category/county.md" location_type = LocationType.COUNTY - category = Category(name="county", agg_key="pop_county_agg_key") + _category = Category(name="county", agg_key="pop_county_agg_key") class DistrictViewSet(AbstractLocationViewSet): @@ -197,7 +197,7 @@ class DistrictViewSet(AbstractLocationViewSet): endpoint_doc = "usaspending_api/api_contracts/contracts/v2/search/spending_by_category/district.md" location_type = LocationType.CONGRESSIONAL_DISTRICT - category = Category(name="district", agg_key="pop_congressional_cur_agg_key") + _category = Category(name="district", agg_key="pop_congressional_cur_agg_key") class StateTerritoryViewSet(AbstractLocationViewSet): @@ -208,7 +208,7 @@ class StateTerritoryViewSet(AbstractLocationViewSet): endpoint_doc = "usaspending_api/api_contracts/contracts/v2/search/spending_by_category/state_territory.md" location_type = LocationType.STATE_TERRITORY - category = Category(name="state_territory", agg_key="pop_state_agg_key") + _category = Category(name="state_territory", agg_key="pop_state_agg_key") class CountryViewSet(AbstractLocationViewSet): @@ -219,4 +219,4 @@ class CountryViewSet(AbstractLocationViewSet): endpoint_doc = "usaspending_api/api_contracts/contracts/v2/search/spending_by_category/country.md" location_type = LocationType.COUNTRY - category = Category(name="country", agg_key="pop_country_agg_key") + _category = Category(name="country", agg_key="pop_country_agg_key") diff --git a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_recipient.py b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_recipient.py index d100992825..b312e6fd9e 100644 --- a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_recipient.py +++ b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_recipient.py @@ -18,7 +18,7 @@ class RecipientViewSet(AbstractSpendingByCategoryViewSet): """ endpoint_doc = "usaspending_api/api_contracts/contracts/v2/search/spending_by_category/recipient.md" - category = Category(name="recipient", agg_key="recipient_agg_key") + _category = Category(name="recipient", agg_key="recipient_agg_key") def build_elasticsearch_result(self, response: dict) -> List[dict]: # Get the codes @@ -81,4 +81,4 @@ class RecipientDunsViewSet(RecipientViewSet): """ endpoint_doc = "usaspending_api/api_contracts/contracts/v2/search/spending_by_category/recipient_duns.md" - category = Category(name="recipient_duns", agg_key="recipient_agg_key") + _category = Category(name="recipient_duns", agg_key="recipient_agg_key") From 9ee057f9d5007e8fa0e3f9052339d18002665fc5 Mon Sep 17 00:00:00 2001 From: David Mikola Date: Mon, 27 Jul 2026 11:52:51 -0500 Subject: [PATCH 08/12] ruff lint fix --- .../views/spending_by_category_views/spending_by_locations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_locations.py b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_locations.py index 9a98d505eb..cbf7b49d44 100644 --- a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_locations.py +++ b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_locations.py @@ -24,7 +24,7 @@ class LocationType(Enum): CONGRESSIONAL_DISTRICT = "congressional" -def _combine_dicts_by_keys(dicts, keys, sum_key) -> List[Dict]: +def _combine_dicts_by_keys(dicts: list[dict], keys: list[str], sum_key: str) -> List[Dict]: """Combine all dictionaries in a list that have the same values for the given field(s) Args: From fb2c4a9e47ebb9328e8f100136d03df308ee8a3f Mon Sep 17 00:00:00 2001 From: David Mikola Date: Mon, 27 Jul 2026 12:29:26 -0500 Subject: [PATCH 09/12] [Dev-15427] Updated Category Declaration for other Child Classes --- .../spending_by_agency_types.py | 8 ++++---- .../spending_by_federal_account.py | 2 +- .../spending_by_industry_codes.py | 12 +++++++----- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_agency_types.py b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_agency_types.py index 0977e1c21b..e618b72796 100644 --- a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_agency_types.py +++ b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_agency_types.py @@ -95,7 +95,7 @@ class AwardingAgencyViewSet(AbstractAgencyViewSet): endpoint_doc = "usaspending_api/api_contracts/contracts/v2/search/spending_by_category/awarding_agency.md" agency_type = AgencyType.AWARDING_TOPTIER - category = Category(name="awarding_agency", agg_key="awarding_toptier_agency_agg_key") + _category = Category(name="awarding_agency", agg_key="awarding_toptier_agency_agg_key") class AwardingSubagencyViewSet(AbstractAgencyViewSet): @@ -106,7 +106,7 @@ class AwardingSubagencyViewSet(AbstractAgencyViewSet): endpoint_doc = "usaspending_api/api_contracts/contracts/v2/search/spending_by_category/awarding_subagency.md" agency_type = AgencyType.AWARDING_SUBTIER - category = Category(name="awarding_subagency", agg_key="awarding_subtier_agency_agg_key") + _category = Category(name="awarding_subagency", agg_key="awarding_subtier_agency_agg_key") class FundingAgencyViewSet(AbstractAgencyViewSet): @@ -117,7 +117,7 @@ class FundingAgencyViewSet(AbstractAgencyViewSet): endpoint_doc = "usaspending_api/api_contracts/contracts/v2/search/spending_by_category/funding_agency.md" agency_type = AgencyType.FUNDING_TOPTIER - category = Category(name="funding_agency", agg_key="funding_toptier_agency_agg_key") + _category = Category(name="funding_agency", agg_key="funding_toptier_agency_agg_key") class FundingSubagencyViewSet(AbstractAgencyViewSet): @@ -128,4 +128,4 @@ class FundingSubagencyViewSet(AbstractAgencyViewSet): endpoint_doc = "usaspending_api/api_contracts/contracts/v2/search/spending_by_category/funding_subagency.md" agency_type = AgencyType.FUNDING_SUBTIER - category = Category(name="funding_subagency", agg_key="funding_subtier_agency_agg_key") + _category = Category(name="funding_subagency", agg_key="funding_subtier_agency_agg_key") diff --git a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_federal_account.py b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_federal_account.py index 05ab6da064..e06f594ac4 100644 --- a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_federal_account.py +++ b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_federal_account.py @@ -51,4 +51,4 @@ class FederalAccountViewSet(AbstractAccountViewSet): endpoint_doc = "usaspending_api/api_contracts/contracts/v2/search/spending_by_category/federal_account.md" - category = Category(name="federal_account", agg_key="federal_accounts") + _category = Category(name="federal_account", agg_key="federal_accounts") diff --git a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_industry_codes.py b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_industry_codes.py index e2a81a75cc..6f340545f6 100644 --- a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_industry_codes.py +++ b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_industry_codes.py @@ -5,6 +5,8 @@ from typing import List from django.db.models import F +from rest_framework.request import Request +from rest_framework.response import Response from usaspending_api.references.models import NAICS, PSC, Cfda, DisasterEmergencyFundCode from usaspending_api.search.v2.views.enums import SpendingLevel @@ -101,7 +103,7 @@ class CfdaViewSet(AbstractIndustryCodeViewSet): endpoint_doc = "usaspending_api/api_contracts/contracts/v2/search/spending_by_category/cfda.md" industry_code_type = IndustryCodeType.CFDA - category = Category(name="cfda", agg_key="cfda_agg_key") + _category = Category(name="cfda", agg_key="cfda_agg_key") class NAICSViewSet(AbstractIndustryCodeViewSet): @@ -112,7 +114,7 @@ class NAICSViewSet(AbstractIndustryCodeViewSet): endpoint_doc = "usaspending_api/api_contracts/contracts/v2/search/spending_by_category/naics.md" industry_code_type = IndustryCodeType.NAICS - category = Category(name="naics", agg_key="naics_agg_key") + _category = Category(name="naics", agg_key="naics_agg_key") class PSCViewSet(AbstractIndustryCodeViewSet): @@ -123,7 +125,7 @@ class PSCViewSet(AbstractIndustryCodeViewSet): endpoint_doc = "usaspending_api/api_contracts/contracts/v2/search/spending_by_category/psc.md" industry_code_type = IndustryCodeType.PSC - category = Category(name="psc", agg_key="psc_agg_key") + _category = Category(name="psc", agg_key="psc_agg_key") class DEFCViewSet(AbstractIndustryCodeViewSet): @@ -134,9 +136,9 @@ class DEFCViewSet(AbstractIndustryCodeViewSet): endpoint_doc = "usaspending_api/api_contracts/contracts/v2/search/spending_by_category/defc.md" industry_code_type = IndustryCodeType.DEFC - category = Category(name="defc", agg_key="defc_agg_key") + _category = Category(name="defc", agg_key="defc_agg_key") - def post(self, request, *args, **kwargs): + def post(self, request: Request, *args, **kwargs) -> Response: validated_payload = self.validate_payload(request) nested_path = "spending_by_defc" if self.spending_level == SpendingLevel.FILE_C: From 8731e7a327966b812d75b0e74f3ef5b33490fed7 Mon Sep 17 00:00:00 2001 From: David Mikola Date: Mon, 27 Jul 2026 13:34:48 -0500 Subject: [PATCH 10/12] Relocated Category Deepcopy --- .../spending_by_category_views/spending_by_category.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py index 16045e2df6..222b80755c 100644 --- a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py +++ b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py @@ -68,10 +68,6 @@ class AbstractSpendingByCategoryViewSet(APIView, metaclass=ABCMeta): def post(self, request: Request, *args, **kwargs) -> Response: original_filters = request.data.get("filters") - # Creates an instance copy for the category for each request - # Prevents modifications from previous requests affecting current requests - self.category = copy.deepcopy(self._category) - # Handles case where the request has already been validated by an implementation of the abstract class validated_payload = kwargs.get("validated_payload", self.validate_payload(request)) @@ -83,6 +79,10 @@ def post(self, request: Request, *args, **kwargs) -> Response: return Response(raw_response) def validate_payload(self, request: Request) -> dict: + # Creates an instance copy for the category for each request + # Prevents modifications from previous requests affecting current requests + self.category = copy.deepcopy(self._category) + models = [ {"name": "subawards", "key": "subawards", "type": "boolean", "default": False, "optional": True}, { From 0ad35a6f8975bbfd2866caf5ab20b0f0707d53ee Mon Sep 17 00:00:00 2001 From: David Mikola Date: Wed, 29 Jul 2026 10:17:12 -0500 Subject: [PATCH 11/12] Fixed recloning for self.category --- .../views/spending_by_category_views/spending_by_category.py | 3 ++- .../spending_by_category_views/spending_by_industry_codes.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py index 222b80755c..fdcf37deec 100644 --- a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py +++ b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py @@ -81,7 +81,8 @@ def post(self, request: Request, *args, **kwargs) -> Response: def validate_payload(self, request: Request) -> dict: # Creates an instance copy for the category for each request # Prevents modifications from previous requests affecting current requests - self.category = copy.deepcopy(self._category) + if not hasattr(self, "category"): + self.category = copy.deepcopy(self._category) models = [ {"name": "subawards", "key": "subawards", "type": "boolean", "default": False, "optional": True}, diff --git a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_industry_codes.py b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_industry_codes.py index 6f340545f6..95564f74ff 100644 --- a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_industry_codes.py +++ b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_industry_codes.py @@ -139,6 +139,9 @@ class DEFCViewSet(AbstractIndustryCodeViewSet): _category = Category(name="defc", agg_key="defc_agg_key") def post(self, request: Request, *args, **kwargs) -> Response: + if hasattr(self, "category"): + delattr(self, "category") + validated_payload = self.validate_payload(request) nested_path = "spending_by_defc" if self.spending_level == SpendingLevel.FILE_C: @@ -149,7 +152,7 @@ def post(self, request: Request, *args, **kwargs) -> Response: agg_key_suffix="", obligation_field=f"{nested_path}.obligation", outlay_field=f"{nested_path}.outlay", - filter_key_to_limit="def_codes", + filter_key_to_limit="def_codes" ) return super().post(request, validated_payload=validated_payload) From c62a47e91796b2aaf3e2e4c3b0368fce1591094e Mon Sep 17 00:00:00 2001 From: David Mikola Date: Thu, 30 Jul 2026 13:27:37 -0500 Subject: [PATCH 12/12] [Dev-15427] Updated check for validated payload --- .../spending_by_category_views/spending_by_category.py | 8 +++++--- .../spending_by_industry_codes.py | 3 --- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py index fdcf37deec..4205781de7 100644 --- a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py +++ b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_category.py @@ -69,7 +69,10 @@ def post(self, request: Request, *args, **kwargs) -> Response: original_filters = request.data.get("filters") # Handles case where the request has already been validated by an implementation of the abstract class - validated_payload = kwargs.get("validated_payload", self.validate_payload(request)) + if kwargs.get("validated_payload"): + validated_payload = kwargs.get("validated_payload") + else: + validated_payload = self.validate_payload(request) self.filters = validated_payload.get("filters", {}) self.pagination = self._get_pagination(validated_payload) @@ -81,8 +84,7 @@ def post(self, request: Request, *args, **kwargs) -> Response: def validate_payload(self, request: Request) -> dict: # Creates an instance copy for the category for each request # Prevents modifications from previous requests affecting current requests - if not hasattr(self, "category"): - self.category = copy.deepcopy(self._category) + self.category = copy.deepcopy(self._category) models = [ {"name": "subawards", "key": "subawards", "type": "boolean", "default": False, "optional": True}, diff --git a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_industry_codes.py b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_industry_codes.py index 95564f74ff..e77200adc5 100644 --- a/usaspending_api/search/v2/views/spending_by_category_views/spending_by_industry_codes.py +++ b/usaspending_api/search/v2/views/spending_by_category_views/spending_by_industry_codes.py @@ -139,9 +139,6 @@ class DEFCViewSet(AbstractIndustryCodeViewSet): _category = Category(name="defc", agg_key="defc_agg_key") def post(self, request: Request, *args, **kwargs) -> Response: - if hasattr(self, "category"): - delattr(self, "category") - validated_payload = self.validate_payload(request) nested_path = "spending_by_defc" if self.spending_level == SpendingLevel.FILE_C: