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.
Files changed (71) hide show
  1. usaspending/__init__.py +144 -0
  2. usaspending/cli/__init__.py +2 -0
  3. usaspending/cli/download_award.py +113 -0
  4. usaspending/client.py +550 -0
  5. usaspending/config.py +119 -0
  6. usaspending/download/__init__.py +8 -0
  7. usaspending/download/job.py +219 -0
  8. usaspending/download/manager.py +140 -0
  9. usaspending/exceptions.py +71 -0
  10. usaspending/logging_config.py +310 -0
  11. usaspending/models/__init__.py +86 -0
  12. usaspending/models/agency.py +631 -0
  13. usaspending/models/award.py +933 -0
  14. usaspending/models/award_factory.py +72 -0
  15. usaspending/models/award_types.py +114 -0
  16. usaspending/models/base_model.py +214 -0
  17. usaspending/models/contract.py +147 -0
  18. usaspending/models/district_spending.py +70 -0
  19. usaspending/models/download.py +74 -0
  20. usaspending/models/funding.py +182 -0
  21. usaspending/models/grant.py +147 -0
  22. usaspending/models/idv.py +165 -0
  23. usaspending/models/lazy_record.py +82 -0
  24. usaspending/models/loan.py +85 -0
  25. usaspending/models/location.py +125 -0
  26. usaspending/models/period_of_performance.py +43 -0
  27. usaspending/models/recipient.py +204 -0
  28. usaspending/models/recipient_business_categories.py +364 -0
  29. usaspending/models/recipient_spending.py +58 -0
  30. usaspending/models/spending.py +70 -0
  31. usaspending/models/state_spending.py +43 -0
  32. usaspending/models/subaward.py +270 -0
  33. usaspending/models/subtier_agency.py +98 -0
  34. usaspending/models/transaction.py +89 -0
  35. usaspending/queries/__init__.py +87 -0
  36. usaspending/queries/agencies_search.py +196 -0
  37. usaspending/queries/agency_award_summary.py +134 -0
  38. usaspending/queries/agency_query.py +102 -0
  39. usaspending/queries/award_query.py +40 -0
  40. usaspending/queries/awarding_agencies_search.py +16 -0
  41. usaspending/queries/awards_search.py +602 -0
  42. usaspending/queries/filters.py +437 -0
  43. usaspending/queries/funding_agencies_search.py +20 -0
  44. usaspending/queries/funding_search.py +180 -0
  45. usaspending/queries/query_builder.py +1430 -0
  46. usaspending/queries/recipient_query.py +66 -0
  47. usaspending/queries/recipients_search.py +167 -0
  48. usaspending/queries/single_resource_base.py +83 -0
  49. usaspending/queries/spending_search.py +261 -0
  50. usaspending/queries/sub_agency_query.py +177 -0
  51. usaspending/queries/subawards_search.py +196 -0
  52. usaspending/queries/transactions_search.py +213 -0
  53. usaspending/resources/__init__.py +17 -0
  54. usaspending/resources/agency_resource.py +105 -0
  55. usaspending/resources/award_resource.py +119 -0
  56. usaspending/resources/base_resource.py +31 -0
  57. usaspending/resources/download_resource.py +102 -0
  58. usaspending/resources/funding_resource.py +41 -0
  59. usaspending/resources/recipients_resource.py +104 -0
  60. usaspending/resources/spending_resource.py +45 -0
  61. usaspending/resources/subawards_resource.py +68 -0
  62. usaspending/resources/transactions_resource.py +39 -0
  63. usaspending/utils/__init__.py +6 -0
  64. usaspending/utils/formatter.py +550 -0
  65. usaspending/utils/rate_limit.py +135 -0
  66. usaspending/utils/retry.py +257 -0
  67. usaspending/utils/special_cases.yaml +155 -0
  68. usaspending_orm-0.6.0.dist-info/LICENSE +18 -0
  69. usaspending_orm-0.6.0.dist-info/METADATA +324 -0
  70. usaspending_orm-0.6.0.dist-info/RECORD +71 -0
  71. 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)
@@ -0,0 +1,6 @@
1
+ """Utility modules for USASpending API client."""
2
+
3
+ from .rate_limit import RateLimiter
4
+ from .retry import RetryHandler
5
+
6
+ __all__ = ["RateLimiter", "RetryHandler"]