usaspending-orm 0.6.0__py3-none-any.whl
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/__init__.py +144 -0
- usaspending/cli/__init__.py +2 -0
- usaspending/cli/download_award.py +113 -0
- usaspending/client.py +550 -0
- usaspending/config.py +119 -0
- usaspending/download/__init__.py +8 -0
- usaspending/download/job.py +219 -0
- usaspending/download/manager.py +140 -0
- usaspending/exceptions.py +71 -0
- usaspending/logging_config.py +310 -0
- usaspending/models/__init__.py +86 -0
- usaspending/models/agency.py +631 -0
- usaspending/models/award.py +933 -0
- usaspending/models/award_factory.py +72 -0
- usaspending/models/award_types.py +114 -0
- usaspending/models/base_model.py +214 -0
- usaspending/models/contract.py +147 -0
- usaspending/models/district_spending.py +70 -0
- usaspending/models/download.py +74 -0
- usaspending/models/funding.py +182 -0
- usaspending/models/grant.py +147 -0
- usaspending/models/idv.py +165 -0
- usaspending/models/lazy_record.py +82 -0
- usaspending/models/loan.py +85 -0
- usaspending/models/location.py +125 -0
- usaspending/models/period_of_performance.py +43 -0
- usaspending/models/recipient.py +204 -0
- usaspending/models/recipient_business_categories.py +364 -0
- usaspending/models/recipient_spending.py +58 -0
- usaspending/models/spending.py +70 -0
- usaspending/models/state_spending.py +43 -0
- usaspending/models/subaward.py +270 -0
- usaspending/models/subtier_agency.py +98 -0
- usaspending/models/transaction.py +89 -0
- usaspending/queries/__init__.py +87 -0
- usaspending/queries/agencies_search.py +196 -0
- usaspending/queries/agency_award_summary.py +134 -0
- usaspending/queries/agency_query.py +102 -0
- usaspending/queries/award_query.py +40 -0
- usaspending/queries/awarding_agencies_search.py +16 -0
- usaspending/queries/awards_search.py +602 -0
- usaspending/queries/filters.py +437 -0
- usaspending/queries/funding_agencies_search.py +20 -0
- usaspending/queries/funding_search.py +180 -0
- usaspending/queries/query_builder.py +1430 -0
- usaspending/queries/recipient_query.py +66 -0
- usaspending/queries/recipients_search.py +167 -0
- usaspending/queries/single_resource_base.py +83 -0
- usaspending/queries/spending_search.py +261 -0
- usaspending/queries/sub_agency_query.py +177 -0
- usaspending/queries/subawards_search.py +196 -0
- usaspending/queries/transactions_search.py +213 -0
- usaspending/resources/__init__.py +17 -0
- usaspending/resources/agency_resource.py +105 -0
- usaspending/resources/award_resource.py +119 -0
- usaspending/resources/base_resource.py +31 -0
- usaspending/resources/download_resource.py +102 -0
- usaspending/resources/funding_resource.py +41 -0
- usaspending/resources/recipients_resource.py +104 -0
- usaspending/resources/spending_resource.py +45 -0
- usaspending/resources/subawards_resource.py +68 -0
- usaspending/resources/transactions_resource.py +39 -0
- usaspending/utils/__init__.py +6 -0
- usaspending/utils/formatter.py +550 -0
- usaspending/utils/rate_limit.py +135 -0
- usaspending/utils/retry.py +257 -0
- usaspending/utils/special_cases.yaml +155 -0
- usaspending_orm-0.6.0.dist-info/LICENSE +18 -0
- usaspending_orm-0.6.0.dist-info/METADATA +324 -0
- usaspending_orm-0.6.0.dist-info/RECORD +71 -0
- usaspending_orm-0.6.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""Agencies search query implementation for funding agency/office autocomplete."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import Dict, Any, TYPE_CHECKING
|
|
5
|
+
from ..exceptions import ValidationError
|
|
6
|
+
from ..models.agency import Agency
|
|
7
|
+
from ..models.subtier_agency import SubTierAgency
|
|
8
|
+
from .query_builder import QueryBuilder
|
|
9
|
+
from ..logging_config import USASpendingLogger
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from ..client import USASpendingClient
|
|
13
|
+
|
|
14
|
+
logger = USASpendingLogger.get_logger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AgenciesSearch(QueryBuilder[Agency]):
|
|
18
|
+
"""Search for funding agencies and offices by name using autocomplete.
|
|
19
|
+
|
|
20
|
+
This query builder uses the /v2/autocomplete/funding_agency_office/ endpoint
|
|
21
|
+
to search for agencies by name. Results can be filtered by type (toptier,
|
|
22
|
+
subtier, or office).
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, client: USASpendingClient):
|
|
26
|
+
"""Initialize AgenciesSearch with client."""
|
|
27
|
+
super().__init__(client)
|
|
28
|
+
self._search_text = ""
|
|
29
|
+
self._limit = 100 # Default limit
|
|
30
|
+
self._result_type = None # Filter: None, 'toptier', 'subtier', 'office'
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def _endpoint(self) -> str:
|
|
34
|
+
"""API endpoint for agency autocomplete."""
|
|
35
|
+
raise NotImplementedError("Subclasses must implement _endpoint")
|
|
36
|
+
|
|
37
|
+
def _build_payload(self, page: int) -> Dict[str, Any]:
|
|
38
|
+
"""Build request payload."""
|
|
39
|
+
if not self._search_text:
|
|
40
|
+
raise ValidationError(
|
|
41
|
+
"search_text is required. Use search_text() method."
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
return {"search_text": self._search_text, "limit": self._limit}
|
|
45
|
+
|
|
46
|
+
def _execute_query(self, page: int) -> Dict[str, Any]:
|
|
47
|
+
"""Execute the autocomplete query.
|
|
48
|
+
|
|
49
|
+
This endpoint doesn't support pagination, so only return results on page 1.
|
|
50
|
+
"""
|
|
51
|
+
# No pagination - return empty after first page
|
|
52
|
+
if page > 1:
|
|
53
|
+
return {"results": [], "page_metadata": {"hasNext": False}}
|
|
54
|
+
|
|
55
|
+
payload = self._build_payload(1)
|
|
56
|
+
response = self._client._make_request("POST", self._endpoint, json=payload)
|
|
57
|
+
|
|
58
|
+
# The response has results as an object with three arrays
|
|
59
|
+
# We need to flatten this into a single results array for QueryBuilder
|
|
60
|
+
results_obj = response.get("results", {})
|
|
61
|
+
flat_results = []
|
|
62
|
+
|
|
63
|
+
# Add results based on filter type
|
|
64
|
+
if self._result_type is None:
|
|
65
|
+
# No filter - return all types
|
|
66
|
+
# Add toptier agencies
|
|
67
|
+
for agency in results_obj.get("toptier_agency", []):
|
|
68
|
+
flat_results.append({"type": "toptier", "data": agency})
|
|
69
|
+
|
|
70
|
+
# Add subtier agencies
|
|
71
|
+
for subtier in results_obj.get("subtier_agency", []):
|
|
72
|
+
flat_results.append({"type": "subtier", "data": subtier})
|
|
73
|
+
|
|
74
|
+
# Add offices
|
|
75
|
+
for office in results_obj.get("office", []):
|
|
76
|
+
flat_results.append({"type": "office", "data": office})
|
|
77
|
+
|
|
78
|
+
elif self._result_type == "toptier":
|
|
79
|
+
for agency in results_obj.get("toptier_agency", []):
|
|
80
|
+
flat_results.append({"type": "toptier", "data": agency})
|
|
81
|
+
|
|
82
|
+
elif self._result_type == "subtier":
|
|
83
|
+
for subtier in results_obj.get("subtier_agency", []):
|
|
84
|
+
flat_results.append({"type": "subtier", "data": subtier})
|
|
85
|
+
|
|
86
|
+
elif self._result_type == "office":
|
|
87
|
+
for office in results_obj.get("office", []):
|
|
88
|
+
flat_results.append({"type": "office", "data": office})
|
|
89
|
+
|
|
90
|
+
# Return flattened structure for QueryBuilder
|
|
91
|
+
return {
|
|
92
|
+
"results": flat_results,
|
|
93
|
+
"page_metadata": {"hasNext": False},
|
|
94
|
+
"messages": response.get("messages", []),
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
def _transform_result(self, result: Dict[str, Any]) -> Agency:
|
|
98
|
+
"""Transform result into Agency object based on type."""
|
|
99
|
+
if not result:
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
result_type = result.get("type")
|
|
103
|
+
data = result.get("data", {})
|
|
104
|
+
|
|
105
|
+
if result_type == "toptier":
|
|
106
|
+
# Direct toptier agency result
|
|
107
|
+
agency_data = {
|
|
108
|
+
"code": data.get("code"),
|
|
109
|
+
"toptier_code": data.get("code"),
|
|
110
|
+
"name": data.get("name"),
|
|
111
|
+
"abbreviation": data.get("abbreviation"),
|
|
112
|
+
}
|
|
113
|
+
return Agency(agency_data, self._client)
|
|
114
|
+
|
|
115
|
+
elif result_type == "subtier":
|
|
116
|
+
# Include subtier data
|
|
117
|
+
return SubTierAgency(data, self._client)
|
|
118
|
+
|
|
119
|
+
elif result_type == "office":
|
|
120
|
+
return SubTierAgency(data, self._client)
|
|
121
|
+
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
def count(self) -> int:
|
|
125
|
+
"""Get total count of matching agencies/offices.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Total number of matching results
|
|
129
|
+
"""
|
|
130
|
+
logger.debug(f"{self.__class__.__name__}.count() called")
|
|
131
|
+
|
|
132
|
+
if not self._search_text:
|
|
133
|
+
raise ValidationError(
|
|
134
|
+
"search_text is required. Use search_text() method."
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Execute query to get all results
|
|
138
|
+
response = self._execute_query(1)
|
|
139
|
+
results = response.get("results", [])
|
|
140
|
+
|
|
141
|
+
count = len(results)
|
|
142
|
+
|
|
143
|
+
logger.info(
|
|
144
|
+
f"{self.__class__.__name__}.count() = {count} results "
|
|
145
|
+
f"for search text '{self._search_text}'"
|
|
146
|
+
)
|
|
147
|
+
return count
|
|
148
|
+
|
|
149
|
+
def search_text(self, search_text: str) -> AgenciesSearch:
|
|
150
|
+
"""Set the search text for the query.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
search_text: Text to search for in agency names
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
New AgenciesSearch instance with search text set
|
|
157
|
+
"""
|
|
158
|
+
clone = self._clone()
|
|
159
|
+
clone._search_text = search_text
|
|
160
|
+
return clone
|
|
161
|
+
|
|
162
|
+
def toptier(self) -> AgenciesSearch:
|
|
163
|
+
"""Filter to only return toptier agency matches.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
New AgenciesSearch instance filtered to toptier agencies
|
|
167
|
+
"""
|
|
168
|
+
clone = self._clone()
|
|
169
|
+
clone._result_type = "toptier"
|
|
170
|
+
return clone
|
|
171
|
+
|
|
172
|
+
def subtier(self) -> AgenciesSearch:
|
|
173
|
+
"""Filter to only return subtier agency matches.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
New AgenciesSearch instance filtered to subtier agencies
|
|
177
|
+
"""
|
|
178
|
+
clone = self._clone()
|
|
179
|
+
clone._result_type = "subtier"
|
|
180
|
+
return clone
|
|
181
|
+
|
|
182
|
+
def office(self) -> AgenciesSearch:
|
|
183
|
+
"""Filter to only return office matches.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
New AgenciesSearch instance filtered to offices
|
|
187
|
+
"""
|
|
188
|
+
clone = self._clone()
|
|
189
|
+
clone._result_type = "office"
|
|
190
|
+
return clone
|
|
191
|
+
|
|
192
|
+
def _clone(self) -> AgenciesSearch:
|
|
193
|
+
"""Create immutable copy for chaining."""
|
|
194
|
+
clone = super()._clone()
|
|
195
|
+
clone._search_text = self._search_text
|
|
196
|
+
return clone
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Agency award summary query implementation for retrieving award aggregations."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Optional, Dict, Any, List
|
|
4
|
+
from .single_resource_base import SingleResourceBase
|
|
5
|
+
from ..exceptions import ValidationError
|
|
6
|
+
from ..client import USASpendingClient
|
|
7
|
+
from ..logging_config import USASpendingLogger
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
logger = USASpendingLogger.get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AgencyAwardSummary(SingleResourceBase):
|
|
16
|
+
"""Retrieve agency award summary data from the USAspending API.
|
|
17
|
+
|
|
18
|
+
This query class handles fetching aggregated award information including
|
|
19
|
+
transaction counts and obligations filtered by fiscal year, agency type,
|
|
20
|
+
and award type codes.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, client: USASpendingClient):
|
|
24
|
+
"""Initialize AgencyAwardSummary with client.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
client: USASpendingClient client instance
|
|
28
|
+
"""
|
|
29
|
+
super().__init__(client)
|
|
30
|
+
logger.debug("AgencyAwardSummary initialized with client: %s", client)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def _endpoint(self) -> str:
|
|
34
|
+
"""Base endpoint for agency award summary retrieval."""
|
|
35
|
+
return "/agency/"
|
|
36
|
+
|
|
37
|
+
def _construct_endpoint(self, resource_id: str) -> str:
|
|
38
|
+
"""Construct the full endpoint URL for agency award summary.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
resource_id: The toptier_code for the agency
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Full endpoint path including /awards/
|
|
45
|
+
"""
|
|
46
|
+
return f"{self._endpoint}{resource_id}/awards/"
|
|
47
|
+
|
|
48
|
+
def find_by_id(self, toptier_code: str) -> Dict[str, Any]:
|
|
49
|
+
"""Not used for award summary - use get_awards_summary instead.
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
NotImplementedError: This method should not be used directly
|
|
53
|
+
"""
|
|
54
|
+
raise NotImplementedError(
|
|
55
|
+
"Use get_awards_summary() method instead for award summary data"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def get_awards_summary(
|
|
59
|
+
self,
|
|
60
|
+
toptier_code: str,
|
|
61
|
+
fiscal_year: Optional[int] = None,
|
|
62
|
+
agency_type: str = "awarding",
|
|
63
|
+
award_type_codes: Optional[List[str]] = None,
|
|
64
|
+
) -> Dict[str, Any]:
|
|
65
|
+
"""Retrieve agency award summary with optional filters.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
toptier_code: The toptier code of an agency (3-4 digit string)
|
|
69
|
+
fiscal_year: Optional fiscal year for the data (defaults to current)
|
|
70
|
+
agency_type: "awarding" or "funding" (defaults to "awarding")
|
|
71
|
+
award_type_codes: Optional list of award type codes to filter by
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Dictionary containing:
|
|
75
|
+
- toptier_code: Agency toptier code
|
|
76
|
+
- fiscal_year: Fiscal year of data
|
|
77
|
+
- latest_action_date: Latest transaction date
|
|
78
|
+
- transaction_count: Number of transactions
|
|
79
|
+
- obligations: Total obligations amount
|
|
80
|
+
- messages: Any API messages
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
ValidationError: If toptier_code is invalid or agency_type is invalid
|
|
84
|
+
APIError: If API request fails
|
|
85
|
+
"""
|
|
86
|
+
# Validate toptier_code
|
|
87
|
+
if not toptier_code:
|
|
88
|
+
raise ValidationError("toptier_code is required")
|
|
89
|
+
|
|
90
|
+
toptier_code = str(toptier_code).strip()
|
|
91
|
+
if not toptier_code.isdigit() or len(toptier_code) not in [3, 4]:
|
|
92
|
+
raise ValidationError(
|
|
93
|
+
f"Invalid toptier_code: {toptier_code}. "
|
|
94
|
+
"Must be a 3-4 digit numeric string"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Validate agency_type
|
|
98
|
+
if agency_type not in ["awarding", "funding"]:
|
|
99
|
+
raise ValidationError(
|
|
100
|
+
f"Invalid agency_type: {agency_type}. Must be 'awarding' or 'funding'"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
logger.debug(
|
|
104
|
+
"Fetching award summary for toptier_code: %s, fiscal_year: %s, "
|
|
105
|
+
"agency_type: %s, award_type_codes: %s",
|
|
106
|
+
toptier_code,
|
|
107
|
+
fiscal_year,
|
|
108
|
+
agency_type,
|
|
109
|
+
award_type_codes,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Build params
|
|
113
|
+
params = {"agency_type": agency_type}
|
|
114
|
+
|
|
115
|
+
if fiscal_year is not None:
|
|
116
|
+
params["fiscal_year"] = fiscal_year
|
|
117
|
+
|
|
118
|
+
if award_type_codes:
|
|
119
|
+
# Convert to list if needed and filter out None/empty values
|
|
120
|
+
if isinstance(award_type_codes, (set, frozenset)):
|
|
121
|
+
award_type_codes = list(award_type_codes)
|
|
122
|
+
award_type_codes = [code for code in award_type_codes if code]
|
|
123
|
+
|
|
124
|
+
if award_type_codes:
|
|
125
|
+
# API expects award_type_codes as array parameter
|
|
126
|
+
params["award_type_codes"] = award_type_codes
|
|
127
|
+
|
|
128
|
+
# Construct endpoint
|
|
129
|
+
endpoint = self._construct_endpoint(toptier_code)
|
|
130
|
+
|
|
131
|
+
# Make API request with params
|
|
132
|
+
response = self._client._make_request("GET", endpoint, params=params)
|
|
133
|
+
|
|
134
|
+
return response
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Agency query implementation for retrieving single agency details."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Optional, Dict, Any
|
|
4
|
+
from .single_resource_base import SingleResourceBase
|
|
5
|
+
from ..exceptions import ValidationError
|
|
6
|
+
from ..client import USASpendingClient
|
|
7
|
+
from ..logging_config import USASpendingLogger
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ..models.agency import Agency
|
|
11
|
+
|
|
12
|
+
logger = USASpendingLogger.get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AgencyQuery(SingleResourceBase):
|
|
16
|
+
"""Retrieve a single agency from the USAspending API.
|
|
17
|
+
|
|
18
|
+
This query class handles fetching agency overview information for a
|
|
19
|
+
specific fiscal year using the agency's toptier code.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, client: USASpendingClient):
|
|
23
|
+
"""Initialize AgencyQuery with client.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
client: USASpendingClient client instance
|
|
27
|
+
"""
|
|
28
|
+
super().__init__(client)
|
|
29
|
+
logger.debug("AgencyQuery initialized with client: %s", client)
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def _endpoint(self) -> str:
|
|
33
|
+
"""Base endpoint for single agency retrieval."""
|
|
34
|
+
return "/agency/"
|
|
35
|
+
|
|
36
|
+
def find_by_id(
|
|
37
|
+
self, toptier_code: str, fiscal_year: Optional[int] = None
|
|
38
|
+
) -> "Agency":
|
|
39
|
+
"""Retrieve agency by toptier code and optional fiscal year.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
toptier_code: The toptier code of an agency (3-4 digit string)
|
|
43
|
+
fiscal_year: Optional fiscal year for the data (defaults to current)
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Agency model instance with full details
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
ValidationError: If toptier_code is invalid
|
|
50
|
+
APIError: If agency not found
|
|
51
|
+
"""
|
|
52
|
+
if not toptier_code:
|
|
53
|
+
raise ValidationError("toptier_code is required")
|
|
54
|
+
|
|
55
|
+
# Validate toptier_code format (3-4 digit numeric string)
|
|
56
|
+
toptier_code = str(toptier_code).strip()
|
|
57
|
+
if not toptier_code.isdigit() or len(toptier_code) not in [3, 4]:
|
|
58
|
+
raise ValidationError(
|
|
59
|
+
f"Invalid toptier_code: {toptier_code}. "
|
|
60
|
+
"Must be a 3-4 digit numeric string"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
logger.debug(
|
|
64
|
+
"Fetching agency with toptier_code: %s, fiscal_year: %s",
|
|
65
|
+
toptier_code,
|
|
66
|
+
fiscal_year,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Make API request with optional fiscal_year parameter
|
|
70
|
+
response = self._get_resource_with_params(toptier_code, fiscal_year)
|
|
71
|
+
|
|
72
|
+
# Create model instance
|
|
73
|
+
from ..models.agency import Agency
|
|
74
|
+
|
|
75
|
+
return Agency(response, client=self._client)
|
|
76
|
+
|
|
77
|
+
def _get_resource_with_params(
|
|
78
|
+
self, toptier_code: str, fiscal_year: Optional[int] = None
|
|
79
|
+
) -> Dict[str, Any]:
|
|
80
|
+
"""Retrieve agency with optional query parameters.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
toptier_code: The agency's toptier code
|
|
84
|
+
fiscal_year: Optional fiscal year parameter
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
API response dictionary
|
|
88
|
+
"""
|
|
89
|
+
# Construct endpoint
|
|
90
|
+
endpoint = self._construct_endpoint(toptier_code)
|
|
91
|
+
|
|
92
|
+
# Build params dict if fiscal_year provided
|
|
93
|
+
params = {}
|
|
94
|
+
if fiscal_year is not None:
|
|
95
|
+
params["fiscal_year"] = fiscal_year
|
|
96
|
+
|
|
97
|
+
# Make API request with params
|
|
98
|
+
response = self._client._make_request(
|
|
99
|
+
"GET", endpoint, params=params if params else None
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return response
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
from .single_resource_base import SingleResourceBase
|
|
3
|
+
from ..exceptions import ValidationError
|
|
4
|
+
from ..client import USASpendingClient
|
|
5
|
+
from ..logging_config import USASpendingLogger
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from ..models.award import Award
|
|
9
|
+
|
|
10
|
+
logger = USASpendingLogger.get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AwardQuery(SingleResourceBase):
|
|
14
|
+
"""Retrieve a single-award from the USAspending API."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, client: USASpendingClient):
|
|
17
|
+
super().__init__(client)
|
|
18
|
+
logger.debug("AwardQuery initialized with client: %s", client)
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def _endpoint(self) -> str:
|
|
22
|
+
"""Base endpoint for single award retrieval."""
|
|
23
|
+
return "/awards/"
|
|
24
|
+
|
|
25
|
+
def find_by_id(self, award_id: str) -> "Award":
|
|
26
|
+
"""Filter by unique award identifier."""
|
|
27
|
+
return self.find_by_generated_id(award_id)
|
|
28
|
+
|
|
29
|
+
def find_by_generated_id(self, award_id: str) -> "Award":
|
|
30
|
+
"""Filter by USASpending's internally generated unique award identifier."""
|
|
31
|
+
if not award_id:
|
|
32
|
+
raise ValidationError("award_id is required")
|
|
33
|
+
|
|
34
|
+
# Make API request
|
|
35
|
+
response = self._get_resource(award_id)
|
|
36
|
+
|
|
37
|
+
# Create model instance using factory
|
|
38
|
+
from ..models.award_factory import create_award
|
|
39
|
+
|
|
40
|
+
return create_award(response, client=self._client)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Agencies search query implementation for awarding agency/office autocomplete."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from .agencies_search import AgenciesSearch
|
|
5
|
+
from ..logging_config import USASpendingLogger
|
|
6
|
+
|
|
7
|
+
logger = USASpendingLogger.get_logger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AwardingAgenciesSearch(AgenciesSearch):
|
|
11
|
+
"""Search for awarding agencies and offices by name."""
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def _endpoint(self) -> str:
|
|
15
|
+
"""API endpoint for agency autocomplete."""
|
|
16
|
+
return "/autocomplete/awarding_agency_office/"
|