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,105 @@
|
|
|
1
|
+
"""Agency resource implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import TYPE_CHECKING, Optional
|
|
5
|
+
|
|
6
|
+
from .base_resource import BaseResource
|
|
7
|
+
from ..logging_config import USASpendingLogger
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ..models.agency import Agency
|
|
11
|
+
from ..queries.funding_agencies_search import FundingAgenciesSearch
|
|
12
|
+
from ..queries.awarding_agencies_search import AwardingAgenciesSearch
|
|
13
|
+
|
|
14
|
+
logger = USASpendingLogger.get_logger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AgencyResource(BaseResource):
|
|
18
|
+
"""Resource for agency-related operations.
|
|
19
|
+
|
|
20
|
+
Provides access to agency overview and detail endpoints.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def find_by_toptier_code(
|
|
24
|
+
self, toptier_code: str, fiscal_year: Optional[int] = None
|
|
25
|
+
) -> "Agency":
|
|
26
|
+
"""Retrieve agency overview for a specific toptier code and fiscal year.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
toptier_code: The toptier code of an agency (3-4 digit string)
|
|
30
|
+
fiscal_year: Optional fiscal year for the data (defaults to current)
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Agency model instance with full details
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
ValidationError: If toptier_code is invalid
|
|
37
|
+
APIError: If agency not found
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
>>> agency = client.agencies.find_by_toptier_code("080") # Get NASA for current fiscal year
|
|
41
|
+
>>> print(agency.name, agency.mission)
|
|
42
|
+
>>>
|
|
43
|
+
>>> agency_2023 = client.agencies.find_by_toptier_code("080", fiscal_year=2023) # Get NASA for FY 2023
|
|
44
|
+
>>> print(agency_2023.fiscal_year, agency_2023.def_codes)
|
|
45
|
+
"""
|
|
46
|
+
logger.debug(
|
|
47
|
+
f"Retrieving agency overview for toptier_code: {toptier_code}, "
|
|
48
|
+
f"fiscal_year: {fiscal_year}"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
from ..queries.agency_query import AgencyQuery
|
|
52
|
+
|
|
53
|
+
return AgencyQuery(self._client).find_by_id(toptier_code, fiscal_year)
|
|
54
|
+
|
|
55
|
+
def find_all_funding_agencies_by_name(self, name: str) -> "FundingAgenciesSearch":
|
|
56
|
+
"""Search for funding agencies and offices by name.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
name: Search text to match against agency/office names
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
FundingAgenciesSearch query builder for iteration and filtering
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
>>> # Get all matches (agencies, subtiers, offices)
|
|
66
|
+
>>> all_results = list(client.agencies.find_all_funding_agencies_by_name("NASA"))
|
|
67
|
+
>>>
|
|
68
|
+
>>> # Get only toptier agencies
|
|
69
|
+
>>> agencies = list(client.agencies.find_all_funding_agencies_by_name("NASA").toptier())
|
|
70
|
+
>>>
|
|
71
|
+
>>> # Get only subtier agencies
|
|
72
|
+
>>> subtiers = list(client.agencies.find_all_funding_agencies_by_name("NASA").subtier())
|
|
73
|
+
>>>
|
|
74
|
+
>>> # Get only offices
|
|
75
|
+
>>> offices = list(client.agencies.find_all_funding_agencies_by_name("NASA").office())
|
|
76
|
+
"""
|
|
77
|
+
from ..queries.funding_agencies_search import FundingAgenciesSearch
|
|
78
|
+
|
|
79
|
+
return FundingAgenciesSearch(self._client).search_text(name)
|
|
80
|
+
|
|
81
|
+
def find_all_awarding_agencies_by_name(self, name: str) -> "AwardingAgenciesSearch":
|
|
82
|
+
"""Search for funding agencies and offices by name.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
name: Search text to match against agency/office names
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
AwardingAgenciesSearch query builder for iteration and filtering
|
|
89
|
+
|
|
90
|
+
Example:
|
|
91
|
+
>>> # Get all matches (agencies, subtiers, offices)
|
|
92
|
+
>>> all_results = list(client.agencies.find_all_awarding_agencies_by_name("NASA"))
|
|
93
|
+
>>>
|
|
94
|
+
>>> # Get only toptier agencies
|
|
95
|
+
>>> agencies = list(client.agencies.find_all_awarding_agencies_by_name("NASA").toptier())
|
|
96
|
+
>>>
|
|
97
|
+
>>> # Get only subtier agencies
|
|
98
|
+
>>> subtiers = list(client.agencies.find_all_awarding_agencies_by_name("NASA").subtier())
|
|
99
|
+
>>>
|
|
100
|
+
>>> # Get only offices
|
|
101
|
+
>>> offices = list(client.agencies.find_all_awarding_agencies_by_name("NASA").office())
|
|
102
|
+
"""
|
|
103
|
+
from ..queries.awarding_agencies_search import AwardingAgenciesSearch
|
|
104
|
+
|
|
105
|
+
return AwardingAgenciesSearch(self._client).search_text(name)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Award resource implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import TYPE_CHECKING, Optional
|
|
5
|
+
|
|
6
|
+
from .base_resource import BaseResource
|
|
7
|
+
from ..logging_config import USASpendingLogger
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ..queries.awards_search import AwardsSearch
|
|
11
|
+
from ..models.award import Award
|
|
12
|
+
|
|
13
|
+
logger = USASpendingLogger.get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AwardResource(BaseResource):
|
|
17
|
+
"""Resource for award-related operations.
|
|
18
|
+
|
|
19
|
+
Provides access to award search and retrieval endpoints.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def find_by_generated_id(self, generated_award_id: str) -> "Award":
|
|
23
|
+
"""Retrieve a single award by the system's internally generated
|
|
24
|
+
award entry ID (e.g. "CONT_AWD_80GSFC18C0008_8000_-NONE-_-NONE-")
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
generated_award_id: Unique award identifier
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Award model instance
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
: If generated_award_id is invalid
|
|
34
|
+
APIError: If award not found
|
|
35
|
+
"""
|
|
36
|
+
logger.debug(f"Retrieving award by ID: {generated_award_id}")
|
|
37
|
+
from ..queries.award_query import AwardQuery
|
|
38
|
+
|
|
39
|
+
return AwardQuery(self._client).find_by_generated_id(generated_award_id)
|
|
40
|
+
|
|
41
|
+
def find_by_award_id(self, award_id: str) -> Optional["Award"]:
|
|
42
|
+
"""Find an award by its PIID or FAIN unique identifier.
|
|
43
|
+
Args:
|
|
44
|
+
award_id: Unique identifier for the award (PIID or FAIN)
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Award model instance if found, otherwise None
|
|
48
|
+
"""
|
|
49
|
+
logger.debug(f"Finding award by ID: {award_id}")
|
|
50
|
+
from ..queries.awards_search import AwardsSearch
|
|
51
|
+
|
|
52
|
+
# Get counts by award type
|
|
53
|
+
search_result = (
|
|
54
|
+
AwardsSearch(self._client).award_ids(award_id).count_awards_by_type()
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Find which type has exactly one result
|
|
58
|
+
matching_types = [
|
|
59
|
+
(award_type, count)
|
|
60
|
+
for award_type, count in search_result.items()
|
|
61
|
+
if count == 1
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
if len(matching_types) != 1:
|
|
65
|
+
total_awards = sum(count for count in search_result.values() if count > 0)
|
|
66
|
+
if total_awards == 0:
|
|
67
|
+
logger.info(f"No awards found for ID {award_id}")
|
|
68
|
+
else:
|
|
69
|
+
logger.warning(
|
|
70
|
+
f"Expected exactly one award for ID {award_id}, found {total_awards} awards across {len(matching_types)} types"
|
|
71
|
+
)
|
|
72
|
+
logger.debug(f"Search result: {search_result}")
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
award_type, _ = matching_types[0]
|
|
76
|
+
logger.info(f"Found 1 award of type {award_type} for ID {award_id}")
|
|
77
|
+
|
|
78
|
+
# Map API response keys to method names
|
|
79
|
+
method_mapping = {
|
|
80
|
+
"contracts": "contracts",
|
|
81
|
+
"grants": "grants",
|
|
82
|
+
"idvs": "idvs",
|
|
83
|
+
"loans": "loans",
|
|
84
|
+
"direct_payments": "direct_payments",
|
|
85
|
+
"other": "other_assistance",
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
method_name = method_mapping.get(award_type)
|
|
89
|
+
if not method_name:
|
|
90
|
+
logger.error(f"Unknown award type from API: {award_type}")
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
# Create search and apply the appropriate filter
|
|
94
|
+
awards_search = AwardsSearch(self._client)
|
|
95
|
+
if hasattr(awards_search, method_name):
|
|
96
|
+
logger.debug(f"Calling .{method_name}() method on AwardsSearch")
|
|
97
|
+
awards_search = getattr(awards_search, method_name)()
|
|
98
|
+
return awards_search.award_ids(award_id).first()
|
|
99
|
+
else:
|
|
100
|
+
logger.error(f"Method {method_name} not found on AwardsSearch")
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
def search(self) -> AwardsSearch:
|
|
104
|
+
"""Create a new award search query builder.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
AwardSearch query builder for chaining filters
|
|
108
|
+
|
|
109
|
+
Example:
|
|
110
|
+
>>> awards = client.awards.search()
|
|
111
|
+
... .agency("NASA")
|
|
112
|
+
... .in_state("TX")
|
|
113
|
+
... .fiscal_years(2023, 2024)
|
|
114
|
+
... .limit(10)
|
|
115
|
+
"""
|
|
116
|
+
logger.debug("Creating new AwardsSearch query builder")
|
|
117
|
+
from ..queries.awards_search import AwardsSearch
|
|
118
|
+
|
|
119
|
+
return AwardsSearch(self._client)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from ..logging_config import USASpendingLogger
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..client import USASpendingClient
|
|
8
|
+
|
|
9
|
+
logger = USASpendingLogger.get_logger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseResource:
|
|
13
|
+
"""Base class for API resources.
|
|
14
|
+
|
|
15
|
+
Resources provide structured gateways to USASpending API endpoints
|
|
16
|
+
and return appropriate query builders or model instances.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, client: USASpendingClient):
|
|
20
|
+
"""Initialize resource with client reference.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
client: USASpendingClient client instance
|
|
24
|
+
"""
|
|
25
|
+
self._client: USASpendingClient = client
|
|
26
|
+
logger.debug(f"Initialized {self.__class__.__name__} resource")
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def client(self) -> USASpendingClient:
|
|
30
|
+
"""Get client instance."""
|
|
31
|
+
return self._client
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# src/usaspending/resources/download_resource.py
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import TYPE_CHECKING, Optional
|
|
5
|
+
|
|
6
|
+
from .base_resource import BaseResource
|
|
7
|
+
from ..logging_config import USASpendingLogger
|
|
8
|
+
|
|
9
|
+
# Import the manager and type aliases
|
|
10
|
+
from ..download.manager import DownloadManager, FileFormat
|
|
11
|
+
from ..download.job import DownloadJob
|
|
12
|
+
from ..models.download import DownloadStatus
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from ..client import USASpendingClient
|
|
16
|
+
|
|
17
|
+
logger = USASpendingLogger.get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DownloadResource(BaseResource):
|
|
21
|
+
"""Resource for award data download operations."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, client: USASpendingClient):
|
|
24
|
+
super().__init__(client)
|
|
25
|
+
self._manager = DownloadManager(client)
|
|
26
|
+
|
|
27
|
+
def contract(
|
|
28
|
+
self,
|
|
29
|
+
award_id: str,
|
|
30
|
+
file_format: FileFormat = "csv",
|
|
31
|
+
destination_dir: Optional[str] = None,
|
|
32
|
+
) -> DownloadJob:
|
|
33
|
+
"""
|
|
34
|
+
Queue a download job for contract award data.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
award_id: The unique award identifier (e.g., CONT_AWD_...).
|
|
38
|
+
file_format: Format of the file (csv, tsv, pstxt).
|
|
39
|
+
destination_dir: Directory where the file will be saved (defaults to CWD).
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
A DownloadJob object. Use job.wait_for_completion() to block until finished.
|
|
43
|
+
"""
|
|
44
|
+
return self._manager.queue_download(
|
|
45
|
+
"contract", award_id, file_format, destination_dir
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def assistance(
|
|
49
|
+
self,
|
|
50
|
+
award_id: str,
|
|
51
|
+
file_format: FileFormat = "csv",
|
|
52
|
+
destination_dir: Optional[str] = None,
|
|
53
|
+
) -> DownloadJob:
|
|
54
|
+
"""
|
|
55
|
+
Queue a download job for assistance award data.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
award_id: The unique award identifier (e.g., ASST_NON_...).
|
|
59
|
+
file_format: Format of the file (csv, tsv, pstxt).
|
|
60
|
+
destination_dir: Directory where the file will be saved (defaults to CWD).
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
A DownloadJob object. Use job.wait_for_completion() to block until finished.
|
|
64
|
+
"""
|
|
65
|
+
return self._manager.queue_download(
|
|
66
|
+
"assistance", award_id, file_format, destination_dir
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def idv(
|
|
70
|
+
self,
|
|
71
|
+
award_id: str,
|
|
72
|
+
file_format: FileFormat = "csv",
|
|
73
|
+
destination_dir: Optional[str] = None,
|
|
74
|
+
) -> DownloadJob:
|
|
75
|
+
"""
|
|
76
|
+
Queue a download job for IDV (Indefinite Delivery Vehicle) award data.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
award_id: The unique award identifier (e.g., IDV_...).
|
|
80
|
+
file_format: Format of the file (csv, tsv, pstxt).
|
|
81
|
+
destination_dir: Directory where the file will be saved (defaults to CWD).
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
A DownloadJob object. Use job.wait_for_completion() to block until finished.
|
|
85
|
+
"""
|
|
86
|
+
return self._manager.queue_download(
|
|
87
|
+
"idv", award_id, file_format, destination_dir
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def status(self, file_name: str) -> DownloadStatus:
|
|
91
|
+
"""
|
|
92
|
+
Check the status of a specific download job directly via the API.
|
|
93
|
+
|
|
94
|
+
Note: Using DownloadJob.refresh_status() is generally preferred.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
file_name: The name of the file returned by the download request.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
The DownloadStatus model representation.
|
|
101
|
+
"""
|
|
102
|
+
return self._manager.check_status(file_name)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Funding resource implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from .base_resource import BaseResource
|
|
7
|
+
from ..logging_config import USASpendingLogger
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ..queries.funding_search import FundingSearch
|
|
11
|
+
|
|
12
|
+
logger = USASpendingLogger.get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FundingResource(BaseResource):
|
|
16
|
+
"""Resource for funding-related operations.
|
|
17
|
+
|
|
18
|
+
Provides access to federal account funding data for awards.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def award_id(self, award_id: str) -> "FundingSearch":
|
|
22
|
+
"""Create a funding search query for a specific award.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
award_id: Unique award identifier
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
FundingSearch query builder for chaining filters
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
>>> funding = client.funding.award_id("CONT_AWD_123")
|
|
32
|
+
... .order_by("fiscal_date", "asc")
|
|
33
|
+
... .limit(50)
|
|
34
|
+
>>> for record in funding:
|
|
35
|
+
... print(f"{record.reporting_fiscal_year}-{record.reporting_fiscal_month}: "
|
|
36
|
+
... f"${record.transaction_obligated_amount:,.2f}")
|
|
37
|
+
"""
|
|
38
|
+
logger.debug(f"Creating funding search for award: {award_id}")
|
|
39
|
+
from ..queries.funding_search import FundingSearch
|
|
40
|
+
|
|
41
|
+
return FundingSearch(self._client).award_id(award_id)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Recipient resource implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import Optional, TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from .base_resource import BaseResource
|
|
7
|
+
from ..logging_config import USASpendingLogger
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ..queries.recipients_search import RecipientsSearch
|
|
11
|
+
from ..models.recipient import Recipient
|
|
12
|
+
|
|
13
|
+
logger = USASpendingLogger.get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RecipientsResource(BaseResource):
|
|
17
|
+
"""Resource for recipient-related operations.
|
|
18
|
+
|
|
19
|
+
Provides access to recipient search and retrieval endpoints.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def find_by_recipient_id(self, recipient_id: str) -> Optional["Recipient"]:
|
|
23
|
+
"""Retrieve a single recipient by ID.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
recipient_id: Unique recipient identifier
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Recipient model instance
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
ValidationError: If recipient_id is invalid
|
|
33
|
+
APIError: If recipient not found
|
|
34
|
+
"""
|
|
35
|
+
logger.debug(f"Retrieving recipient by ID: {recipient_id}")
|
|
36
|
+
from ..queries.recipient_query import RecipientQuery
|
|
37
|
+
|
|
38
|
+
return RecipientQuery(self._client).find_by_id(recipient_id)
|
|
39
|
+
|
|
40
|
+
def search(self) -> "RecipientsSearch":
|
|
41
|
+
"""Create a new recipient search query builder.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
RecipientsSearch query builder for recipient searches
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
>>> recipients = client.recipients.search()
|
|
48
|
+
... .keyword("california")
|
|
49
|
+
... .award_type("contracts")
|
|
50
|
+
... .order_by("amount", "desc")
|
|
51
|
+
... .limit(10)
|
|
52
|
+
"""
|
|
53
|
+
logger.debug(
|
|
54
|
+
"Creating new RecipientsSearch query builder for recipient searches"
|
|
55
|
+
)
|
|
56
|
+
from ..queries.recipients_search import RecipientsSearch
|
|
57
|
+
|
|
58
|
+
return RecipientsSearch(self._client)
|
|
59
|
+
|
|
60
|
+
def find_by_duns(self, duns: str) -> Optional["Recipient"]:
|
|
61
|
+
"""Retrieve a single recipient by DUNS number.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
duns: Unique DUNS identifier
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Recipient model instance or None if not found
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
ValidationError: If duns is invalid
|
|
71
|
+
"""
|
|
72
|
+
logger.debug(f"Searching recipient by DUNS: {duns}")
|
|
73
|
+
from ..queries.recipients_search import RecipientsSearch
|
|
74
|
+
|
|
75
|
+
recipients = RecipientsSearch(self._client).keyword(duns).limit(4)
|
|
76
|
+
# Return the parent recipient if available, otherwise first result
|
|
77
|
+
for r in recipients:
|
|
78
|
+
if "-P" in r.recipient_id:
|
|
79
|
+
return r
|
|
80
|
+
# Return first result if no parent found (avoids hanging len() call)
|
|
81
|
+
return recipients.first()
|
|
82
|
+
|
|
83
|
+
def find_by_uei(self, uei: str) -> Optional["Recipient"]:
|
|
84
|
+
"""Retrieve a single recipient by UEI number.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
uei: Unique Entity Identifier
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Recipient model instance or None if not found
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
ValidationError: If uei is invalid
|
|
94
|
+
"""
|
|
95
|
+
logger.debug(f"Searching recipient by UEI: {uei}")
|
|
96
|
+
from ..queries.recipients_search import RecipientsSearch
|
|
97
|
+
|
|
98
|
+
recipients = RecipientsSearch(self._client).keyword(uei).limit(4)
|
|
99
|
+
# Return the parent recipient if available, otherwise first result
|
|
100
|
+
for r in recipients:
|
|
101
|
+
if "-P" in r.recipient_id:
|
|
102
|
+
return r
|
|
103
|
+
# Return first result if no parent found (avoids hanging len() call)
|
|
104
|
+
return recipients.first()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Spending resource implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from .base_resource import BaseResource
|
|
7
|
+
from ..logging_config import USASpendingLogger
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ..queries.spending_search import SpendingSearch
|
|
11
|
+
|
|
12
|
+
logger = USASpendingLogger.get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SpendingResource(BaseResource):
|
|
16
|
+
"""Resource for spending-related operations.
|
|
17
|
+
|
|
18
|
+
Provides access to spending by category endpoints (recipient and district).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def search(self) -> "SpendingSearch":
|
|
22
|
+
"""Create a new spending search query builder.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
SpendingSearch query builder for chaining filters
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
>>> # Search spending by recipient
|
|
29
|
+
>>> recipient_spending = client.spending.search()
|
|
30
|
+
... .by_recipient()
|
|
31
|
+
... .agency("National Aeronautics and Space Administration")
|
|
32
|
+
... .fiscal_year(2024)
|
|
33
|
+
... .limit(10)
|
|
34
|
+
|
|
35
|
+
>>> # Search spending by district
|
|
36
|
+
>>> district_spending = client.spending.search()
|
|
37
|
+
... .by_district()
|
|
38
|
+
... .spending_level("awards")
|
|
39
|
+
... .place_of_performance_locations({"country_code": "USA", "state_code": "TX"))
|
|
40
|
+
... .all()
|
|
41
|
+
"""
|
|
42
|
+
logger.debug("Creating new SpendingSearch query builder")
|
|
43
|
+
from ..queries.spending_search import SpendingSearch
|
|
44
|
+
|
|
45
|
+
return SpendingSearch(self._client)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Subawards resource implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from .base_resource import BaseResource
|
|
7
|
+
from ..logging_config import USASpendingLogger
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ..queries.subawards_search import SubAwardsSearch
|
|
11
|
+
|
|
12
|
+
logger = USASpendingLogger.get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SubAwardsResource(BaseResource):
|
|
16
|
+
"""Resource for subaward-related operations.
|
|
17
|
+
|
|
18
|
+
Provides access to subaward search and retrieval endpoints. Subawards
|
|
19
|
+
represent the secondary distribution of federal funds from prime recipients
|
|
20
|
+
to subrecipients who carry out portions of the federal program.
|
|
21
|
+
|
|
22
|
+
The resource supports searching for both contract subawards (subcontracts)
|
|
23
|
+
and grant subawards, with filtering by time periods, award types, agencies,
|
|
24
|
+
and recipients. Results include detailed information about the prime award,
|
|
25
|
+
subrecipient, and the subaward transaction itself.
|
|
26
|
+
|
|
27
|
+
Note: Subaward reporting is required for subawards of $30,000 or more under
|
|
28
|
+
the Federal Funding Accountability and Transparency Act (FFATA).
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def search(self) -> "SubAwardsSearch":
|
|
32
|
+
"""Create a subawards search query builder.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
SubAwardsSearch query builder for chaining filters
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
>>> subawards = client.subawards.search()
|
|
39
|
+
... .award_type_codes("A", "B", "C")
|
|
40
|
+
... .time_period("2024-01-01", "2024-12-31")
|
|
41
|
+
... .limit(50)
|
|
42
|
+
>>> for sub in subawards:
|
|
43
|
+
... print(f"{sub.sub_awardee_name}: ${sub.sub_award_amount:,.2f}")
|
|
44
|
+
"""
|
|
45
|
+
logger.debug("Creating subawards search query builder")
|
|
46
|
+
from ..queries.subawards_search import SubAwardsSearch
|
|
47
|
+
|
|
48
|
+
return SubAwardsSearch(self._client)
|
|
49
|
+
|
|
50
|
+
def award_id(self, award_id: str) -> "SubAwardsSearch":
|
|
51
|
+
"""Create a subawards search query for a specific award.
|
|
52
|
+
|
|
53
|
+
This is a convenience method that chains search().award_id(award_id).
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
award_id: Unique award identifier
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
SubAwardsSearch query builder for chaining filters
|
|
60
|
+
|
|
61
|
+
Example:
|
|
62
|
+
>>> subawards = client.subawards.award_id("CONT_AWD_123...")
|
|
63
|
+
... .limit(50)
|
|
64
|
+
>>> for sub in subawards:
|
|
65
|
+
... print(f"{sub.sub_award_date}: {sub.sub_awardee_name}")
|
|
66
|
+
"""
|
|
67
|
+
logger.debug(f"Creating subawards search for award: {award_id}")
|
|
68
|
+
return self.search().award_id(award_id)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Transactions resource implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from .base_resource import BaseResource
|
|
7
|
+
from ..logging_config import USASpendingLogger
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from ..queries.transactions_search import TransactionsSearch
|
|
11
|
+
|
|
12
|
+
logger = USASpendingLogger.get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TransactionsResource(BaseResource):
|
|
16
|
+
"""Resource for transaction-related operations.
|
|
17
|
+
|
|
18
|
+
Provides access to transaction search and retrieval endpoints.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def award_id(self, award_id: str) -> "TransactionsSearch":
|
|
22
|
+
"""Create a transactions search query for a specific award.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
award_id: Unique award identifier
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
TransactionsSearch query builder for chaining filters
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
>>> transactions = client.transactions.award_id("CONT_AWD_123")
|
|
32
|
+
... .limit(50)
|
|
33
|
+
>>> for txn in transactions:
|
|
34
|
+
... print(f"{txn.action_date}: ${txn.federal_action_obligation:,.2f}")
|
|
35
|
+
"""
|
|
36
|
+
logger.debug(f"Creating transactions search for award: {award_id}")
|
|
37
|
+
from ..queries.transactions_search import TransactionsSearch
|
|
38
|
+
|
|
39
|
+
return TransactionsSearch(self._client).award_id(award_id)
|