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,310 @@
1
+ """Logging configuration for USASpending API client.
2
+
3
+ For applications using this library:
4
+
5
+ #### Configure Only USASpending Logs
6
+
7
+ To control only the USASpending library logs without affecting other libraries:
8
+
9
+ ```python
10
+ import logging
11
+ from usaspending import USASpendingClient
12
+
13
+ # Configure only the usaspending logger
14
+ usaspending_logger = logging.getLogger('usaspending')
15
+ usaspending_logger.setLevel(logging.DEBUG)
16
+
17
+ # Add a handler for usaspending logs
18
+ handler = logging.StreamHandler()
19
+ handler.setFormatter(logging.Formatter('%(name)s - %(levelname)s - %(message)s'))
20
+ usaspending_logger.addHandler(handler)
21
+
22
+ client = USASpendingClient()
23
+ ```
24
+
25
+ #### Silence USASpending Logs
26
+
27
+ To suppress USASpending log messages even if your application has logging configured:
28
+
29
+ ```python
30
+ import logging
31
+ from usaspending import USASpendingClient
32
+
33
+ # Silence usaspending logs
34
+ logging.getLogger('usaspending').setLevel(logging.WARNING)
35
+
36
+ client = USASpendingClient()
37
+ ```
38
+
39
+ #### Log to File
40
+
41
+ To write USASpending logs to a file:
42
+
43
+ ```python
44
+ import logging
45
+ from usaspending import USASpendingClient
46
+
47
+ # Configure file logging for usaspending
48
+ usaspending_logger = logging.getLogger('usaspending')
49
+ usaspending_logger.setLevel(logging.INFO)
50
+
51
+ file_handler = logging.FileHandler('usaspending.log')
52
+ file_handler.setFormatter(
53
+ logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
54
+ )
55
+ usaspending_logger.addHandler(file_handler)
56
+
57
+ client = USASpendingClient()
58
+ ```
59
+
60
+ """
61
+
62
+ from __future__ import annotations
63
+
64
+ import logging
65
+ from collections import Counter
66
+ from typing import Dict, Any, Optional, TYPE_CHECKING
67
+
68
+ if TYPE_CHECKING:
69
+ from .queries.filters import BaseFilter
70
+
71
+
72
+ class USASpendingLogger:
73
+ """Library logging manager.
74
+
75
+ Example usage within the library:
76
+ from usaspending.logging_config import USASpendingLogger
77
+
78
+ logger = USASpendingLogger.get_logger(__name__)
79
+ logger.info("Library message")
80
+
81
+ Example usage by applications:
82
+ import logging
83
+ from usaspending import USASpendingClient
84
+
85
+ # Configure logging
86
+ logging.basicConfig(
87
+ level=logging.INFO,
88
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
89
+ )
90
+
91
+ client = USASpendingClient()
92
+ """
93
+
94
+ _loggers: Dict[str, logging.Logger] = {}
95
+
96
+ @classmethod
97
+ def get_logger(cls, name: str) -> logging.Logger:
98
+ """Get a logger instance with proper library configuration.
99
+
100
+ This method ensures that library loggers:
101
+ - Have a NullHandler to prevent "No handlers found" warnings
102
+ - Have propagation enabled so applications can control them
103
+ - Are cached to avoid repeated configuration
104
+
105
+ Args:
106
+ name: Logger name (typically __name__ from the calling module)
107
+
108
+ Returns:
109
+ Configured logger instance suitable for library use
110
+
111
+ Example:
112
+ logger = USASpendingLogger.get_logger(__name__)
113
+ logger.info("Library message")
114
+ """
115
+ if name not in cls._loggers:
116
+ logger = logging.getLogger(name)
117
+
118
+ # Only add NullHandler if this is a library logger and has no handlers
119
+ if name.startswith("usaspending") and not logger.handlers:
120
+ # NullHandler prevents "No handler found" warnings while allowing
121
+ # the application to control actual logging behavior
122
+ logger.addHandler(logging.NullHandler())
123
+
124
+ # Ensure propagation is enabled so parent loggers (configured by
125
+ # the application) can handle our log records
126
+ logger.propagate = True
127
+
128
+ cls._loggers[name] = logger
129
+
130
+ return cls._loggers[name]
131
+
132
+ @classmethod
133
+ def is_debug_enabled(cls) -> bool:
134
+ """Check if debug logging is enabled for the library.
135
+
136
+ This checks the effective level of the 'usaspending' logger to determine
137
+ if debug messages would be processed. Useful for expensive debug operations.
138
+
139
+ Returns:
140
+ True if debug logging is enabled, False otherwise
141
+
142
+ Example:
143
+ if USASpendingLogger.is_debug_enabled():
144
+ logger.debug(f"Expensive debug info: {expensive_operation()}")
145
+ """
146
+ usaspending_logger = logging.getLogger("usaspending")
147
+ return usaspending_logger.isEnabledFor(logging.DEBUG)
148
+
149
+
150
+ def get_logger(name: str) -> logging.Logger:
151
+ """Convenience function to get a library logger.
152
+
153
+ This is a shortcut for USASpendingLogger.get_logger() that's commonly used
154
+ throughout the library codebase.
155
+
156
+ Args:
157
+ name: Logger name (typically __name__)
158
+
159
+ Returns:
160
+ Configured logger instance
161
+
162
+ Example:
163
+ from usaspending.logging_config import get_logger
164
+
165
+ logger = get_logger(__name__)
166
+ logger.info("Library message")
167
+ """
168
+ return USASpendingLogger.get_logger(name)
169
+
170
+
171
+ def log_api_request(
172
+ logger: logging.Logger,
173
+ method: str,
174
+ url: str,
175
+ params: Optional[Dict[str, Any]] = None,
176
+ json_data: Optional[Dict[str, Any]] = None,
177
+ ) -> None:
178
+ """Log an API request with appropriate detail level.
179
+
180
+ This helper function provides consistent formatting for API request logging
181
+ throughout the library. The level of detail depends on the logger's effective level.
182
+
183
+ Args:
184
+ logger: Logger instance to use for output
185
+ method: HTTP method (GET, POST, etc.)
186
+ url: Request URL
187
+ params: Optional query parameters dict
188
+ json_data: Optional JSON payload dict
189
+
190
+ Example:
191
+ log_api_request(logger, "POST", "https://api.example.com/search",
192
+ json_data={"filters": {...}})
193
+ """
194
+ if logger.isEnabledFor(logging.DEBUG):
195
+ logger.debug(f"API Request: {method} {url}")
196
+ if params:
197
+ logger.debug(f"Query params: {params}")
198
+ if json_data:
199
+ logger.debug(f"JSON payload: {json_data}")
200
+ else:
201
+ logger.info(f"API Request: {method} {url}")
202
+
203
+
204
+ def log_api_response(
205
+ logger: logging.Logger,
206
+ status_code: int,
207
+ response_size: Optional[int] = None,
208
+ duration: Optional[float] = None,
209
+ error: Optional[str] = None,
210
+ ) -> None:
211
+ """Log an API response with appropriate detail level.
212
+
213
+ This helper function provides consistent formatting for API response logging
214
+ throughout the library. The message format and level depend on the response status.
215
+
216
+ Args:
217
+ logger: Logger instance to use for output
218
+ status_code: HTTP status code
219
+ response_size: Optional response size in bytes
220
+ duration: Optional request duration in seconds
221
+ error: Optional error message for failed requests
222
+
223
+ Example:
224
+ log_api_response(logger, 200, response_size=1024, duration=0.543)
225
+ log_api_response(logger, 500, error="Internal server error")
226
+ """
227
+ if error:
228
+ logger.error(f"API Response: {status_code} - Error: {error}")
229
+ elif status_code >= 400:
230
+ logger.warning(f"API Response: {status_code}")
231
+ else:
232
+ msg_parts = [f"API Response: {status_code}"]
233
+ if duration is not None:
234
+ msg_parts.append(f"({duration:.3f}s)")
235
+ if response_size is not None and logger.isEnabledFor(logging.DEBUG):
236
+ msg_parts.append(f"- {response_size} bytes")
237
+
238
+ if status_code >= 300:
239
+ logger.warning(" ".join(msg_parts))
240
+ else:
241
+ logger.debug(" ".join(msg_parts))
242
+
243
+
244
+ def log_query_execution(
245
+ logger: logging.Logger,
246
+ query_type: str,
247
+ filter_objects: list["BaseFilter"],
248
+ endpoint: str,
249
+ page: int = 1,
250
+ ) -> None:
251
+ """Log query execution details.
252
+
253
+ This helper function provides consistent formatting for query execution logging
254
+ throughout the library. Debug level includes additional endpoint, pagination info,
255
+ and detailed filter breakdown.
256
+
257
+ Args:
258
+ logger: Logger instance to use for output
259
+ query_type: Type of query being executed (e.g., "AwardsSearch")
260
+ filter_objects: List of filter objects applied to the query
261
+ endpoint: API endpoint being called
262
+ page: Page number for paginated requests (default: 1)
263
+
264
+ Example:
265
+ log_query_execution(logger, "AwardsSearch", [KeywordsFilter(...), TimePeriodFilter(...)], "/api/v2/search/spending_by_award/", 2)
266
+ """
267
+ filters_count = len(filter_objects)
268
+
269
+ if logger.isEnabledFor(logging.DEBUG):
270
+ # Create filter breakdown by type
271
+ filter_types = [type(f).__name__ for f in filter_objects]
272
+ filter_counts = Counter(filter_types)
273
+
274
+ if filter_counts:
275
+ filter_breakdown = ", ".join(
276
+ [
277
+ f"{count} {filter_type}"
278
+ for filter_type, count in filter_counts.items()
279
+ ]
280
+ )
281
+ filter_summary = f"{filters_count} filters ({filter_breakdown})"
282
+ else:
283
+ filter_summary = "0 filters"
284
+
285
+ logger.debug(
286
+ f"Executing {query_type} query - {filter_summary}, "
287
+ f"endpoint: {endpoint}, page: {page}"
288
+ )
289
+
290
+ # Log individual filter details in debug mode
291
+ for i, filter_obj in enumerate(filter_objects):
292
+ filter_dict = filter_obj.to_dict()
293
+ logger.debug(f"Filter {i + 1}: {type(filter_obj).__name__} = {filter_dict}")
294
+
295
+ else:
296
+ # Extract filter keys for INFO level logging
297
+ if filters_count == 0:
298
+ logger.info(f"Executing {query_type} query - no filters")
299
+ else:
300
+ # Get the keys from each filter's dictionary representation
301
+ filter_keys = []
302
+ for filter_obj in filter_objects:
303
+ filter_dict = filter_obj.to_dict()
304
+ # Each filter's to_dict() returns a dict with one key
305
+ filter_keys.extend(filter_dict.keys())
306
+
307
+ # Join unique keys (in case of duplicates)
308
+ unique_keys = list(dict.fromkeys(filter_keys)) # Preserves order
309
+ keys_str = ", ".join(unique_keys)
310
+ logger.info(f"Executing {query_type} query - filters: {keys_str}")
@@ -0,0 +1,86 @@
1
+ """Model classes for USASpending data structures."""
2
+
3
+ from __future__ import annotations
4
+
5
+ # Base classes
6
+ from .base_model import BaseModel, ClientAwareModel
7
+ from .lazy_record import LazyRecord
8
+
9
+ # Core models
10
+ from .award import Award
11
+ from .contract import Contract
12
+ from .grant import Grant
13
+ from .idv import IDV
14
+ from .loan import Loan
15
+ from .recipient import Recipient
16
+ from .location import Location
17
+ from .transaction import Transaction
18
+ from .funding import Funding
19
+ from .period_of_performance import PeriodOfPerformance
20
+ from .award_factory import create_award
21
+ from .agency import Agency
22
+ from .subtier_agency import SubTierAgency
23
+ from .subaward import SubAward
24
+
25
+ # Spending models
26
+ from .spending import Spending
27
+ from .recipient_spending import RecipientSpending
28
+ from .district_spending import DistrictSpending
29
+ from .state_spending import StateSpending
30
+
31
+ # Award type constants
32
+ from .award_types import (
33
+ AWARD_TYPE_GROUPS,
34
+ AWARD_TYPE_DESCRIPTIONS,
35
+ CONTRACT_CODES,
36
+ IDV_CODES,
37
+ LOAN_CODES,
38
+ GRANT_CODES,
39
+ DIRECT_PAYMENT_CODES,
40
+ OTHER_CODES,
41
+ ALL_AWARD_CODES,
42
+ get_category_for_code,
43
+ is_valid_award_type,
44
+ get_description,
45
+ )
46
+
47
+ __all__ = [
48
+ # Base classes
49
+ "BaseModel",
50
+ "ClientAwareModel",
51
+ "LazyRecord",
52
+ # Core models
53
+ "Award",
54
+ "Contract",
55
+ "Grant",
56
+ "IDV",
57
+ "Loan",
58
+ "Recipient",
59
+ "Location",
60
+ "Transaction",
61
+ "Funding",
62
+ "PeriodOfPerformance",
63
+ "Agency",
64
+ "SubTierAgency",
65
+ "SubAward",
66
+ # Spending models
67
+ "Spending",
68
+ "RecipientSpending",
69
+ "DistrictSpending",
70
+ "StateSpending",
71
+ # Factory
72
+ "create_award",
73
+ # Award type constants
74
+ "AWARD_TYPE_GROUPS",
75
+ "AWARD_TYPE_DESCRIPTIONS",
76
+ "CONTRACT_CODES",
77
+ "IDV_CODES",
78
+ "LOAN_CODES",
79
+ "GRANT_CODES",
80
+ "DIRECT_PAYMENT_CODES",
81
+ "OTHER_CODES",
82
+ "ALL_AWARD_CODES",
83
+ "get_category_for_code",
84
+ "is_valid_award_type",
85
+ "get_description",
86
+ ]