simpleapps-com-augur-api 0.8.10__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 (96) hide show
  1. augur_api/__init__.py +43 -0
  2. augur_api/client.py +453 -0
  3. augur_api/core/__init__.py +40 -0
  4. augur_api/core/config.py +75 -0
  5. augur_api/core/errors.py +173 -0
  6. augur_api/core/http_client.py +426 -0
  7. augur_api/core/schemas.py +105 -0
  8. augur_api/py.typed +0 -0
  9. augur_api/services/__init__.py +13 -0
  10. augur_api/services/agr_info/__init__.py +47 -0
  11. augur_api/services/agr_info/client.py +326 -0
  12. augur_api/services/agr_info/schemas.py +123 -0
  13. augur_api/services/agr_site/__init__.py +79 -0
  14. augur_api/services/agr_site/client.py +384 -0
  15. augur_api/services/agr_site/schemas.py +268 -0
  16. augur_api/services/agr_work/__init__.py +7 -0
  17. augur_api/services/agr_work/client.py +32 -0
  18. augur_api/services/agr_work/schemas.py +11 -0
  19. augur_api/services/avalara/__init__.py +17 -0
  20. augur_api/services/avalara/client.py +64 -0
  21. augur_api/services/avalara/schemas.py +34 -0
  22. augur_api/services/base.py +54 -0
  23. augur_api/services/basecamp2/__init__.py +65 -0
  24. augur_api/services/basecamp2/client.py +568 -0
  25. augur_api/services/basecamp2/schemas.py +227 -0
  26. augur_api/services/brand_folder/__init__.py +31 -0
  27. augur_api/services/brand_folder/client.py +206 -0
  28. augur_api/services/brand_folder/schemas.py +133 -0
  29. augur_api/services/commerce/__init__.py +56 -0
  30. augur_api/services/commerce/client.py +298 -0
  31. augur_api/services/commerce/schemas.py +167 -0
  32. augur_api/services/customers/__init__.py +69 -0
  33. augur_api/services/customers/client.py +437 -0
  34. augur_api/services/customers/schemas.py +273 -0
  35. augur_api/services/gregorovich/__init__.py +31 -0
  36. augur_api/services/gregorovich/client.py +151 -0
  37. augur_api/services/gregorovich/schemas.py +42 -0
  38. augur_api/services/items/__init__.py +302 -0
  39. augur_api/services/items/client.py +1223 -0
  40. augur_api/services/items/schemas.py +722 -0
  41. augur_api/services/joomla/__init__.py +59 -0
  42. augur_api/services/joomla/client.py +333 -0
  43. augur_api/services/joomla/schemas.py +286 -0
  44. augur_api/services/legacy/__init__.py +66 -0
  45. augur_api/services/legacy/client.py +391 -0
  46. augur_api/services/legacy/schemas.py +115 -0
  47. augur_api/services/logistics/__init__.py +34 -0
  48. augur_api/services/logistics/client.py +116 -0
  49. augur_api/services/logistics/schemas.py +65 -0
  50. augur_api/services/nexus/__init__.py +89 -0
  51. augur_api/services/nexus/client.py +589 -0
  52. augur_api/services/nexus/schemas.py +171 -0
  53. augur_api/services/open_search/__init__.py +58 -0
  54. augur_api/services/open_search/client.py +285 -0
  55. augur_api/services/open_search/schemas.py +146 -0
  56. augur_api/services/orders/__init__.py +51 -0
  57. augur_api/services/orders/client.py +299 -0
  58. augur_api/services/orders/schemas.py +195 -0
  59. augur_api/services/p21_apis/__init__.py +83 -0
  60. augur_api/services/p21_apis/client.py +420 -0
  61. augur_api/services/p21_apis/schemas.py +130 -0
  62. augur_api/services/p21_core/__init__.py +29 -0
  63. augur_api/services/p21_core/client.py +395 -0
  64. augur_api/services/p21_core/schemas.py +221 -0
  65. augur_api/services/p21_pim/__init__.py +51 -0
  66. augur_api/services/p21_pim/client.py +319 -0
  67. augur_api/services/p21_pim/schemas.py +128 -0
  68. augur_api/services/p21_sism/__init__.py +60 -0
  69. augur_api/services/p21_sism/client.py +334 -0
  70. augur_api/services/p21_sism/schemas.py +92 -0
  71. augur_api/services/payments/__init__.py +97 -0
  72. augur_api/services/payments/client.py +508 -0
  73. augur_api/services/payments/schemas.py +166 -0
  74. augur_api/services/pricing/__init__.py +43 -0
  75. augur_api/services/pricing/client.py +175 -0
  76. augur_api/services/pricing/schemas.py +146 -0
  77. augur_api/services/resource.py +141 -0
  78. augur_api/services/shipping/__init__.py +17 -0
  79. augur_api/services/shipping/client.py +68 -0
  80. augur_api/services/shipping/schemas.py +38 -0
  81. augur_api/services/slack/__init__.py +23 -0
  82. augur_api/services/slack/client.py +74 -0
  83. augur_api/services/slack/schemas.py +35 -0
  84. augur_api/services/smarty_streets/__init__.py +19 -0
  85. augur_api/services/smarty_streets/client.py +82 -0
  86. augur_api/services/smarty_streets/schemas.py +32 -0
  87. augur_api/services/ups/__init__.py +17 -0
  88. augur_api/services/ups/client.py +72 -0
  89. augur_api/services/ups/schemas.py +41 -0
  90. augur_api/services/vmi/__init__.py +157 -0
  91. augur_api/services/vmi/client.py +586 -0
  92. augur_api/services/vmi/schemas.py +285 -0
  93. simpleapps_com_augur_api-0.8.10.dist-info/METADATA +177 -0
  94. simpleapps_com_augur_api-0.8.10.dist-info/RECORD +96 -0
  95. simpleapps_com_augur_api-0.8.10.dist-info/WHEEL +4 -0
  96. simpleapps_com_augur_api-0.8.10.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,173 @@
1
+ """Error classes for the Augur API client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ class AugurError(Exception):
9
+ """Base error class for all Augur API errors.
10
+
11
+ Attributes:
12
+ message: Human-readable error message.
13
+ code: Error code string (e.g., 'API_ERROR', 'NETWORK_ERROR').
14
+ status_code: HTTP status code if applicable.
15
+ service: The service that raised the error.
16
+ endpoint: The endpoint that raised the error.
17
+ request_id: Optional request ID for debugging.
18
+ validation_errors: Optional list of validation errors.
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ message: str,
24
+ code: str,
25
+ status_code: int,
26
+ service: str,
27
+ endpoint: str,
28
+ request_id: str | None = None,
29
+ validation_errors: list[dict[str, Any]] | None = None,
30
+ ) -> None:
31
+ """Initialize the error.
32
+
33
+ Args:
34
+ message: Human-readable error message.
35
+ code: Error code string.
36
+ status_code: HTTP status code.
37
+ service: The service that raised the error.
38
+ endpoint: The endpoint that raised the error.
39
+ request_id: Optional request ID for debugging.
40
+ validation_errors: Optional list of validation errors.
41
+ """
42
+ super().__init__(message)
43
+ self.message = message
44
+ self.code = code
45
+ self.status_code = status_code
46
+ self.service = service
47
+ self.endpoint = endpoint
48
+ self.request_id = request_id
49
+ self.validation_errors = validation_errors
50
+
51
+ def __str__(self) -> str:
52
+ """Return string representation of the error."""
53
+ parts = [f"{self.code}: {self.message}"]
54
+ parts.append(f"(service={self.service}, endpoint={self.endpoint})")
55
+ if self.request_id:
56
+ parts.append(f"[request_id={self.request_id}]")
57
+ return " ".join(parts)
58
+
59
+
60
+ class ValidationError(AugurError):
61
+ """Error raised when request or response validation fails.
62
+
63
+ Provides detailed information about which fields failed validation.
64
+ """
65
+
66
+ def __init__(
67
+ self,
68
+ message: str,
69
+ service: str,
70
+ endpoint: str,
71
+ validation_errors: list[dict[str, Any]],
72
+ ) -> None:
73
+ """Initialize the validation error.
74
+
75
+ Args:
76
+ message: Human-readable error message.
77
+ service: The service that raised the error.
78
+ endpoint: The endpoint that raised the error.
79
+ validation_errors: List of validation error details.
80
+ """
81
+ super().__init__(
82
+ message=message,
83
+ code="VALIDATION_ERROR",
84
+ status_code=400,
85
+ service=service,
86
+ endpoint=endpoint,
87
+ validation_errors=validation_errors,
88
+ )
89
+
90
+ def get_formatted_errors(self) -> list[str]:
91
+ """Format validation errors into human-readable messages.
92
+
93
+ Returns:
94
+ List of formatted error messages.
95
+ """
96
+ if not self.validation_errors:
97
+ return []
98
+
99
+ formatted = []
100
+ for error in self.validation_errors:
101
+ path = ".".join(str(p) for p in error.get("loc", [])) or "root"
102
+ msg = error.get("msg", "Unknown error")
103
+ formatted.append(f"{path}: {msg}")
104
+ return formatted
105
+
106
+ def __str__(self) -> str:
107
+ """Return string representation with validation details."""
108
+ base = super().__str__()
109
+ formatted = self.get_formatted_errors()
110
+ if formatted:
111
+ errors_str = "\n - ".join(formatted)
112
+ return f"{base}\nValidation errors:\n - {errors_str}"
113
+ return base
114
+
115
+
116
+ class AuthenticationError(AugurError):
117
+ """Error raised when authentication fails (HTTP 401)."""
118
+
119
+ def __init__(self, message: str, service: str, endpoint: str) -> None:
120
+ """Initialize the authentication error.
121
+
122
+ Args:
123
+ message: Human-readable error message.
124
+ service: The service that raised the error.
125
+ endpoint: The endpoint that raised the error.
126
+ """
127
+ super().__init__(
128
+ message=message,
129
+ code="AUTHENTICATION_ERROR",
130
+ status_code=401,
131
+ service=service,
132
+ endpoint=endpoint,
133
+ )
134
+
135
+
136
+ class NotFoundError(AugurError):
137
+ """Error raised when a resource is not found (HTTP 404)."""
138
+
139
+ def __init__(self, message: str, service: str, endpoint: str) -> None:
140
+ """Initialize the not found error.
141
+
142
+ Args:
143
+ message: Human-readable error message.
144
+ service: The service that raised the error.
145
+ endpoint: The endpoint that raised the error.
146
+ """
147
+ super().__init__(
148
+ message=message,
149
+ code="NOT_FOUND",
150
+ status_code=404,
151
+ service=service,
152
+ endpoint=endpoint,
153
+ )
154
+
155
+
156
+ class RateLimitError(AugurError):
157
+ """Error raised when rate limit is exceeded (HTTP 429)."""
158
+
159
+ def __init__(self, message: str, service: str, endpoint: str) -> None:
160
+ """Initialize the rate limit error.
161
+
162
+ Args:
163
+ message: Human-readable error message.
164
+ service: The service that raised the error.
165
+ endpoint: The endpoint that raised the error.
166
+ """
167
+ super().__init__(
168
+ message=message,
169
+ code="RATE_LIMIT_EXCEEDED",
170
+ status_code=429,
171
+ service=service,
172
+ endpoint=endpoint,
173
+ )
@@ -0,0 +1,426 @@
1
+ """HTTP client for making requests to Augur API services."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import random
8
+ import time
9
+ from typing import TYPE_CHECKING, Any, TypeVar
10
+
11
+ import httpx
12
+
13
+ from augur_api.core.errors import (
14
+ AugurError,
15
+ AuthenticationError,
16
+ NotFoundError,
17
+ RateLimitError,
18
+ )
19
+
20
+ if TYPE_CHECKING:
21
+ from augur_api.core.config import AugurAPIConfig
22
+
23
+ T = TypeVar("T")
24
+
25
+ # Service base URLs
26
+ SERVICE_BASE_URLS: dict[str, str] = {
27
+ "agr-info": "https://agr-info.augur-api.com",
28
+ "agr-site": "https://agr-site.augur-api.com",
29
+ "agr-work": "https://agr-work.augur-api.com",
30
+ "avalara": "https://avalara.augur-api.com",
31
+ "basecamp2": "https://basecamp2.augur-api.com",
32
+ "brand-folder": "https://brand-folder.augur-api.com",
33
+ "commerce": "https://commerce.augur-api.com",
34
+ "customers": "https://customers.augur-api.com",
35
+ "gregorovich": "https://gregorovich.augur-api.com",
36
+ "items": "https://items.augur-api.com",
37
+ "joomla": "https://joomla.augur-api.com",
38
+ "legacy": "https://legacy.augur-api.com",
39
+ "logistics": "https://logistics.augur-api.com",
40
+ "nexus": "https://nexus.augur-api.com",
41
+ "open-search": "https://open-search.augur-api.com",
42
+ "orders": "https://orders.augur-api.com",
43
+ "p21-apis": "https://p21-apis.augur-api.com",
44
+ "p21-core": "https://p21-core.augur-api.com",
45
+ "p21-pim": "https://p21-pim.augur-api.com",
46
+ "p21-sism": "https://p21-sism.augur-api.com",
47
+ "payments": "https://payments.augur-api.com",
48
+ "pricing": "https://pricing.augur-api.com",
49
+ "shipping": "https://shipping.augur-api.com",
50
+ "slack": "https://slack.augur-api.com",
51
+ "smarty-streets": "https://smarty-streets.augur-api.com",
52
+ "ups": "https://ups.augur-api.com",
53
+ "vmi": "https://vmi.augur-api.com",
54
+ }
55
+
56
+
57
+ def _generate_request_key(method: str, url: str, params: Any | None = None) -> str:
58
+ """Generate a unique key for request deduplication.
59
+
60
+ Args:
61
+ method: HTTP method.
62
+ url: Request URL.
63
+ params: Query parameters or request body.
64
+
65
+ Returns:
66
+ Unique string key for this request.
67
+ """
68
+ params_str = ""
69
+ if params:
70
+ # Sort keys for consistent hashing
71
+ params_str = json.dumps(params, sort_keys=True)
72
+ combined = f"{method}:{url}:{params_str}"
73
+ return hashlib.md5(combined.encode()).hexdigest() # noqa: S324
74
+
75
+
76
+ def _calculate_backoff_delay(attempt: int, base_delay: float) -> float:
77
+ """Calculate delay for exponential backoff with jitter.
78
+
79
+ Args:
80
+ attempt: Current attempt number (0-indexed).
81
+ base_delay: Base delay in seconds.
82
+
83
+ Returns:
84
+ Delay in seconds with jitter.
85
+ """
86
+ exponential_delay = base_delay * (2**attempt)
87
+ jitter = random.random() * 0.3 * exponential_delay # noqa: S311
88
+ delay = exponential_delay + jitter
89
+ return delay if delay < 30.0 else 30.0 # Cap at 30 seconds
90
+
91
+
92
+ def _is_retryable_error(status_code: int | None) -> bool:
93
+ """Determine if an error is retryable.
94
+
95
+ Args:
96
+ status_code: HTTP status code, or None for network errors.
97
+
98
+ Returns:
99
+ True if the request should be retried.
100
+ """
101
+ # Network errors (no status) are retryable
102
+ if status_code is None:
103
+ return True
104
+
105
+ # Rate limit (429) and server errors (5xx) are retryable
106
+ return status_code == 429 or (status_code >= 500 and status_code < 600)
107
+
108
+
109
+ class HTTPClient:
110
+ """HTTP client for making requests to a specific Augur service.
111
+
112
+ Handles authentication, retries, request deduplication, and error mapping.
113
+
114
+ Attributes:
115
+ service_name: Name of the service (e.g., 'items', 'customers').
116
+ config: Client configuration.
117
+ """
118
+
119
+ def __init__(self, service_name: str, config: AugurAPIConfig) -> None:
120
+ """Initialize the HTTP client.
121
+
122
+ Args:
123
+ service_name: Name of the service.
124
+ config: Client configuration.
125
+ """
126
+ self.service_name = service_name
127
+ self.config = config
128
+ self._base_url = SERVICE_BASE_URLS.get(
129
+ service_name, f"https://{service_name}.augur-api.com"
130
+ )
131
+ self._inflight_requests: dict[str, dict[str, Any]] = {}
132
+ self._client = httpx.Client(timeout=config.timeout)
133
+
134
+ @property
135
+ def base_url(self) -> str:
136
+ """Get the base URL for this service."""
137
+ return self._base_url
138
+
139
+ def _get_headers(self, endpoint: str) -> dict[str, str]:
140
+ """Build request headers.
141
+
142
+ Args:
143
+ endpoint: The endpoint path being called.
144
+
145
+ Returns:
146
+ Dictionary of headers.
147
+ """
148
+ headers: dict[str, str] = {
149
+ "x-site-id": self.config.site_id,
150
+ "Content-Type": "application/json",
151
+ "Accept": "application/json",
152
+ }
153
+
154
+ # Add bearer token for non-public endpoints
155
+ is_public = endpoint.endswith(("/health-check", "/ping"))
156
+ if not is_public and self.config.token:
157
+ headers["Authorization"] = f"Bearer {self.config.token}"
158
+
159
+ return headers
160
+
161
+ def _handle_http_error(self, response: httpx.Response, endpoint: str) -> None:
162
+ """Handle HTTP error responses by raising appropriate exceptions.
163
+
164
+ Args:
165
+ response: The HTTP response.
166
+ endpoint: The endpoint that was called.
167
+
168
+ Raises:
169
+ AuthenticationError: For 401 responses.
170
+ NotFoundError: For 404 responses.
171
+ RateLimitError: For 429 responses.
172
+ AugurError: For other error responses.
173
+ """
174
+ status = response.status_code
175
+
176
+ # Try to extract error details from response
177
+ try:
178
+ data = response.json()
179
+ message = data.get("message", f"Request failed with status {status}")
180
+ code = data.get("code", "API_ERROR")
181
+ request_id = data.get("requestId")
182
+ except Exception:
183
+ message = f"Request failed with status {status}"
184
+ code = "API_ERROR"
185
+ request_id = None
186
+
187
+ if status == 401:
188
+ raise AuthenticationError(
189
+ message=message or "Authentication failed",
190
+ service=self.service_name,
191
+ endpoint=endpoint,
192
+ )
193
+ if status == 404:
194
+ raise NotFoundError(
195
+ message=message or "Resource not found",
196
+ service=self.service_name,
197
+ endpoint=endpoint,
198
+ )
199
+ if status == 429:
200
+ raise RateLimitError(
201
+ message=message or "Rate limit exceeded",
202
+ service=self.service_name,
203
+ endpoint=endpoint,
204
+ )
205
+
206
+ raise AugurError(
207
+ message=message,
208
+ code=code,
209
+ status_code=status,
210
+ service=self.service_name,
211
+ endpoint=endpoint,
212
+ request_id=request_id,
213
+ )
214
+
215
+ def _execute_with_retry(
216
+ self,
217
+ method: str,
218
+ url: str,
219
+ params: dict[str, Any] | None = None,
220
+ json_data: Any | None = None,
221
+ ) -> httpx.Response:
222
+ """Execute a request with retry logic.
223
+
224
+ Args:
225
+ method: HTTP method.
226
+ url: Full URL to request.
227
+ params: Query parameters.
228
+ json_data: JSON body data.
229
+
230
+ Returns:
231
+ HTTP response.
232
+
233
+ Raises:
234
+ AugurError: For network or API errors.
235
+ """
236
+ max_retries = self.config.retries
237
+ base_delay = self.config.retry_delay
238
+ endpoint = url.replace(self._base_url, "")
239
+
240
+ for attempt in range(max_retries + 1):
241
+ try:
242
+ headers = self._get_headers(endpoint)
243
+ response = self._client.request(
244
+ method=method,
245
+ url=url,
246
+ params=params,
247
+ json=json_data,
248
+ headers=headers,
249
+ )
250
+
251
+ # Check for HTTP errors
252
+ if response.status_code >= 400:
253
+ # Only retry on retryable errors
254
+ if attempt < max_retries and _is_retryable_error(response.status_code):
255
+ delay = _calculate_backoff_delay(attempt, base_delay)
256
+ time.sleep(delay)
257
+ continue
258
+ self._handle_http_error(response, endpoint)
259
+
260
+ return response
261
+
262
+ except httpx.RequestError as e:
263
+ # Network errors are retryable
264
+ if attempt < max_retries:
265
+ delay = _calculate_backoff_delay(attempt, base_delay)
266
+ time.sleep(delay)
267
+ continue
268
+
269
+ raise AugurError(
270
+ message=str(e),
271
+ code="NETWORK_ERROR",
272
+ status_code=0,
273
+ service=self.service_name,
274
+ endpoint=endpoint,
275
+ ) from e
276
+
277
+ # Should not reach here, but satisfy type checker
278
+ raise AugurError( # pragma: no cover
279
+ message="Max retries exceeded",
280
+ code="MAX_RETRIES",
281
+ status_code=0,
282
+ service=self.service_name,
283
+ endpoint=endpoint,
284
+ )
285
+
286
+ def _transform_edge_cache_params(self, params: dict[str, Any] | None) -> dict[str, Any] | None:
287
+ """Transform edge_cache parameter to Cloudflare's cacheSiteId format.
288
+
289
+ Cloudflare expects cacheSiteId{suffix}=<site_id> where suffix indicates duration:
290
+ - '30s', '1m', '5m' for sub-hour caches
291
+ - 1, 2, 3, 4, 5, 8 for hourly caches
292
+
293
+ Examples:
294
+ - edge_cache: '30s' → cacheSiteId30s: <site_id>
295
+ - edge_cache: '1m' → cacheSiteId1m: <site_id>
296
+ - edge_cache: '5m' → cacheSiteId5m: <site_id>
297
+ - edge_cache: 1 → cacheSiteId1: <site_id>
298
+ - edge_cache: 8 → cacheSiteId8: <site_id>
299
+
300
+ Args:
301
+ params: Original request parameters.
302
+
303
+ Returns:
304
+ Parameters with edge_cache transformed to cacheSiteId{suffix}.
305
+ """
306
+ if not params:
307
+ return params
308
+
309
+ edge_cache = params.get("edge_cache")
310
+
311
+ # If no edge_cache param, return original params
312
+ if edge_cache is None:
313
+ return params
314
+
315
+ # Valid sub-hour values (strings only)
316
+ valid_sub_hour = {"30s", "1m", "5m"}
317
+ # Valid hourly values
318
+ valid_hourly = {1, 2, 3, 4, 5, 8}
319
+
320
+ edge_cache_str = str(edge_cache)
321
+
322
+ if edge_cache_str in valid_sub_hour:
323
+ # Sub-hour cache: '30s', '1m', '5m'
324
+ cache_suffix = edge_cache_str
325
+ else:
326
+ # Try to parse as hourly value
327
+ try:
328
+ cache_hours = int(edge_cache_str)
329
+ except ValueError:
330
+ # Invalid value - return params without edge_cache
331
+ return {k: v for k, v in params.items() if k != "edge_cache"}
332
+
333
+ if cache_hours not in valid_hourly:
334
+ # Invalid value - return params without edge_cache
335
+ return {k: v for k, v in params.items() if k != "edge_cache"}
336
+
337
+ cache_suffix = str(cache_hours)
338
+
339
+ # Transform to Cloudflare format: cacheSiteId{suffix}=<site_id>
340
+ cache_key = f"cacheSiteId{cache_suffix}"
341
+ result = {k: v for k, v in params.items() if k != "edge_cache"}
342
+ result[cache_key] = self.config.site_id
343
+
344
+ return result
345
+
346
+ def get(self, path: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
347
+ """Make a GET request.
348
+
349
+ Args:
350
+ path: Endpoint path (e.g., '/health-check').
351
+ params: Query parameters.
352
+
353
+ Returns:
354
+ Response JSON as a dictionary.
355
+ """
356
+ # Transform edge_cache to cacheSiteId{N} format for Cloudflare
357
+ transformed_params = self._transform_edge_cache_params(params)
358
+ url = f"{self._base_url}{path}"
359
+
360
+ # Request deduplication for GET requests
361
+ request_key = _generate_request_key("GET", url, transformed_params)
362
+ if request_key in self._inflight_requests:
363
+ return self._inflight_requests[request_key]
364
+
365
+ try:
366
+ response = self._execute_with_retry("GET", url, params=transformed_params)
367
+ result: dict[str, Any] = response.json()
368
+ return result
369
+ finally:
370
+ # Clean up inflight tracking
371
+ self._inflight_requests.pop(request_key, None)
372
+
373
+ def post(
374
+ self, path: str, data: Any | None = None, params: dict[str, Any] | None = None
375
+ ) -> dict[str, Any]:
376
+ """Make a POST request.
377
+
378
+ Args:
379
+ path: Endpoint path.
380
+ data: Request body data.
381
+ params: Query parameters.
382
+
383
+ Returns:
384
+ Response JSON as a dictionary.
385
+ """
386
+ url = f"{self._base_url}{path}"
387
+ response = self._execute_with_retry("POST", url, params=params, json_data=data)
388
+ result: dict[str, Any] = response.json()
389
+ return result
390
+
391
+ def put(
392
+ self, path: str, data: Any | None = None, params: dict[str, Any] | None = None
393
+ ) -> dict[str, Any]:
394
+ """Make a PUT request.
395
+
396
+ Args:
397
+ path: Endpoint path.
398
+ data: Request body data.
399
+ params: Query parameters.
400
+
401
+ Returns:
402
+ Response JSON as a dictionary.
403
+ """
404
+ url = f"{self._base_url}{path}"
405
+ response = self._execute_with_retry("PUT", url, params=params, json_data=data)
406
+ result: dict[str, Any] = response.json()
407
+ return result
408
+
409
+ def delete(self, path: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
410
+ """Make a DELETE request.
411
+
412
+ Args:
413
+ path: Endpoint path.
414
+ params: Query parameters.
415
+
416
+ Returns:
417
+ Response JSON as a dictionary.
418
+ """
419
+ url = f"{self._base_url}{path}"
420
+ response = self._execute_with_retry("DELETE", url, params=params)
421
+ result: dict[str, Any] = response.json()
422
+ return result
423
+
424
+ def close(self) -> None:
425
+ """Close the HTTP client and release resources."""
426
+ self._client.close()
@@ -0,0 +1,105 @@
1
+ """Base schemas for Augur API responses."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Generic, TypeVar
6
+
7
+ from pydantic import BaseModel, ConfigDict
8
+ from pydantic.alias_generators import to_camel
9
+
10
+ T = TypeVar("T")
11
+
12
+
13
+ class CamelCaseModel(BaseModel):
14
+ """Base model that handles camelCase to snake_case conversion.
15
+
16
+ The Augur API returns camelCase field names, but Python convention
17
+ uses snake_case. This model automatically handles the conversion.
18
+ """
19
+
20
+ model_config = ConfigDict(
21
+ extra="allow",
22
+ populate_by_name=True,
23
+ alias_generator=to_camel,
24
+ )
25
+
26
+
27
+ class BaseResponse(CamelCaseModel, Generic[T]):
28
+ """Base response schema for all Augur API responses.
29
+
30
+ All API responses follow this structure, wrapping the actual data
31
+ with metadata about the request and response.
32
+
33
+ Attributes:
34
+ count: Number of items in the current response.
35
+ data: The actual response data (type varies by endpoint).
36
+ message: Response message from the API.
37
+ options: Additional options (supports both array and object formats).
38
+ params: Parameters used in the request.
39
+ status: HTTP status code.
40
+ total: Total number of results available.
41
+ total_results: Total results (alias handled by CamelCaseModel).
42
+ """
43
+
44
+ count: int
45
+ data: T
46
+ message: str
47
+ options: list[Any] | dict[str, Any]
48
+ params: list[Any] | dict[str, Any]
49
+ status: int
50
+ total: int
51
+ total_results: int
52
+
53
+
54
+ class HealthCheckData(CamelCaseModel):
55
+ """Health check response data.
56
+
57
+ Returned by all service /health-check endpoints.
58
+
59
+ Attributes:
60
+ site_hash: Hash of the site configuration.
61
+ site_id: Site identifier.
62
+ """
63
+
64
+ site_hash: str
65
+ site_id: str
66
+
67
+
68
+ class PingData(BaseModel):
69
+ """Ping response data.
70
+
71
+ Simple connectivity check - returns 'pong'.
72
+ """
73
+
74
+ model_config = ConfigDict(extra="allow")
75
+
76
+
77
+ class PaginationParams(BaseModel):
78
+ """Common pagination parameters for list endpoints.
79
+
80
+ Attributes:
81
+ limit: Maximum number of items to return.
82
+ offset: Number of items to skip.
83
+ """
84
+
85
+ limit: int | None = None
86
+ offset: int | None = None
87
+
88
+
89
+ class EdgeCacheParams(BaseModel):
90
+ """Edge cache duration parameters.
91
+
92
+ Supported values for Cloudflare cache rules.
93
+
94
+ Attributes:
95
+ edge_cache: Cache duration - sub-hour ('30s', '1m', '5m') or hourly (1-5, 8).
96
+ """
97
+
98
+ edge_cache: str | int | None = None
99
+
100
+
101
+ class BaseGetParams(PaginationParams, EdgeCacheParams):
102
+ """Combined base parameters for GET requests.
103
+
104
+ Includes both pagination and edge caching options.
105
+ """
augur_api/py.typed ADDED
File without changes