fauxshield 0.1.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.
fauxshield/__init__.py ADDED
@@ -0,0 +1,62 @@
1
+ """FauxShield Python SDK.
2
+
3
+ Official Python client for the FauxShield anti-counterfeiting platform API.
4
+
5
+ Quick start:
6
+ ```python
7
+ from fauxshield import FauxShieldClient
8
+
9
+ client = FauxShieldClient(api_key="fs_live_...")
10
+ products = client.products.list()
11
+ ```
12
+
13
+ For webhook verification:
14
+ ```python
15
+ from fauxshield import verify_webhook_signature
16
+
17
+ verify_webhook_signature(
18
+ payload=request.body,
19
+ signature=request.headers["X-FauxShield-Signature"],
20
+ secret="whsec_...",
21
+ )
22
+ ```
23
+ """
24
+
25
+ from .client import FauxShieldClient
26
+ from .errors import (
27
+ AuthenticationError,
28
+ AuthorizationError,
29
+ ConflictError,
30
+ FauxShieldError,
31
+ NotFoundError,
32
+ RateLimitError,
33
+ ServiceUnavailableError,
34
+ ValidationError,
35
+ )
36
+ from .pagination import PaginatedIterator
37
+ from .types import PaginatedResponse, PaginationMeta, RateLimitInfo
38
+ from .webhook import verify_webhook_signature, WebhookVerificationError
39
+
40
+ __version__ = "0.1.0"
41
+
42
+ __all__ = [
43
+ # Client
44
+ "FauxShieldClient",
45
+ # Errors
46
+ "FauxShieldError",
47
+ "AuthenticationError",
48
+ "AuthorizationError",
49
+ "ValidationError",
50
+ "NotFoundError",
51
+ "RateLimitError",
52
+ "ConflictError",
53
+ "ServiceUnavailableError",
54
+ # Webhook
55
+ "verify_webhook_signature",
56
+ "WebhookVerificationError",
57
+ # Types
58
+ "PaginatedIterator",
59
+ "PaginatedResponse",
60
+ "PaginationMeta",
61
+ "RateLimitInfo",
62
+ ]
fauxshield/client.py ADDED
@@ -0,0 +1,173 @@
1
+ """Main client for the FauxShield Python SDK.
2
+
3
+ Provides the top-level FauxShieldClient class that serves as the
4
+ primary entry point for all API interactions.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Optional
10
+
11
+ from .http_client import DEFAULT_BASE_URL, DEFAULT_TIMEOUT, HttpClient
12
+ from .resources import (
13
+ ApiKeysResource,
14
+ BatchesResource,
15
+ EventsResource,
16
+ FraudAlertsResource,
17
+ IdentitiesResource,
18
+ ProductsResource,
19
+ WebhooksResource,
20
+ )
21
+ from .types import RateLimitInfo
22
+
23
+
24
+ class FauxShieldClient:
25
+ """FauxShield API client.
26
+
27
+ The main entry point for interacting with the FauxShield API.
28
+ Provides access to all resource namespaces through typed properties.
29
+
30
+ Example:
31
+ ```python
32
+ from fauxshield import FauxShieldClient
33
+
34
+ client = FauxShieldClient(api_key="fs_live_...")
35
+
36
+ # Access resources
37
+ products = client.products.list()
38
+ batch = client.batches.create(product_id="prod_123", quantity=500)
39
+ alerts = client.fraud_alerts.list(status="open")
40
+
41
+ # Clean up when done
42
+ client.close()
43
+ ```
44
+
45
+ Context manager usage:
46
+ ```python
47
+ with FauxShieldClient(api_key="fs_live_...") as client:
48
+ for product in client.products.list():
49
+ print(product["name"])
50
+ ```
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ api_key: str,
56
+ *,
57
+ base_url: str = DEFAULT_BASE_URL,
58
+ timeout: float = DEFAULT_TIMEOUT,
59
+ max_retries: int = 3,
60
+ ) -> None:
61
+ """Initialize the FauxShield client.
62
+
63
+ Args:
64
+ api_key: Your FauxShield API key (required).
65
+ Keys start with "fs_live_" for production or "fs_test_" for sandbox.
66
+ base_url: Override the base API URL. Defaults to production.
67
+ timeout: Request timeout in seconds. Defaults to 30.
68
+ max_retries: Maximum retry attempts for transient failures. Defaults to 3.
69
+
70
+ Raises:
71
+ ValueError: If api_key is empty.
72
+ """
73
+ if not api_key:
74
+ raise ValueError("api_key is required")
75
+
76
+ self._http_client = HttpClient(
77
+ api_key=api_key,
78
+ base_url=base_url,
79
+ timeout=timeout,
80
+ max_retries=max_retries,
81
+ )
82
+
83
+ # Initialize resource namespaces
84
+ self._products = ProductsResource(self._http_client)
85
+ self._batches = BatchesResource(self._http_client)
86
+ self._identities = IdentitiesResource(self._http_client)
87
+ self._fraud_alerts = FraudAlertsResource(self._http_client)
88
+ self._events = EventsResource(self._http_client)
89
+ self._webhooks = WebhooksResource(self._http_client)
90
+ self._api_keys = ApiKeysResource(self._http_client)
91
+
92
+ @property
93
+ def products(self) -> ProductsResource:
94
+ """Access the Products API.
95
+
96
+ Manage products protected by the anti-counterfeiting system.
97
+ """
98
+ return self._products
99
+
100
+ @property
101
+ def batches(self) -> BatchesResource:
102
+ """Access the Batches API.
103
+
104
+ Manage product batches and initiate recalls.
105
+ """
106
+ return self._batches
107
+
108
+ @property
109
+ def identities(self) -> IdentitiesResource:
110
+ """Access the Identities API.
111
+
112
+ Generate and manage unique product identity tokens.
113
+ """
114
+ return self._identities
115
+
116
+ @property
117
+ def fraud_alerts(self) -> FraudAlertsResource:
118
+ """Access the Fraud Alerts API.
119
+
120
+ View and resolve fraud alerts from the detection engine.
121
+ """
122
+ return self._fraud_alerts
123
+
124
+ @property
125
+ def events(self) -> EventsResource:
126
+ """Access the Events API.
127
+
128
+ Query the immutable audit event trail.
129
+ """
130
+ return self._events
131
+
132
+ @property
133
+ def webhooks(self) -> WebhooksResource:
134
+ """Access the Webhooks API.
135
+
136
+ Manage webhook endpoints for real-time notifications.
137
+ """
138
+ return self._webhooks
139
+
140
+ @property
141
+ def api_keys(self) -> ApiKeysResource:
142
+ """Access the API Keys API.
143
+
144
+ Manage API keys for programmatic access.
145
+ """
146
+ return self._api_keys
147
+
148
+ @property
149
+ def last_rate_limit(self) -> Optional[RateLimitInfo]:
150
+ """Most recent rate limit info from the last API response.
151
+
152
+ Returns None if no requests have been made yet.
153
+ """
154
+ return self._http_client.last_rate_limit
155
+
156
+ def close(self) -> None:
157
+ """Close the client and release all resources.
158
+
159
+ Always call this method when you're done using the client,
160
+ or use the client as a context manager instead.
161
+ """
162
+ self._http_client.close()
163
+
164
+ def __enter__(self) -> "FauxShieldClient":
165
+ """Enter the context manager."""
166
+ return self
167
+
168
+ def __exit__(self, *args: object) -> None:
169
+ """Exit the context manager and close resources."""
170
+ self.close()
171
+
172
+ def __repr__(self) -> str:
173
+ return f"FauxShieldClient(base_url={self._http_client._base_url!r})"
fauxshield/errors.py ADDED
@@ -0,0 +1,182 @@
1
+ """Error classes for the FauxShield Python SDK.
2
+
3
+ Provides a structured error hierarchy that maps API error responses
4
+ to specific exception types for precise error handling.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, Dict, List, Optional
10
+
11
+
12
+ class FauxShieldError(Exception):
13
+ """Base exception for all FauxShield SDK errors.
14
+
15
+ Attributes:
16
+ message: Human-readable error message.
17
+ status_code: HTTP status code from the API response, if applicable.
18
+ code: Machine-readable error code from the API.
19
+ request_id: Unique request identifier for support inquiries.
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ message: str,
25
+ status_code: Optional[int] = None,
26
+ code: Optional[str] = None,
27
+ request_id: Optional[str] = None,
28
+ ) -> None:
29
+ super().__init__(message)
30
+ self.message = message
31
+ self.status_code = status_code
32
+ self.code = code
33
+ self.request_id = request_id
34
+
35
+ def __repr__(self) -> str:
36
+ attrs = [f"message={self.message!r}"]
37
+ if self.status_code is not None:
38
+ attrs.append(f"status_code={self.status_code}")
39
+ if self.code is not None:
40
+ attrs.append(f"code={self.code!r}")
41
+ if self.request_id is not None:
42
+ attrs.append(f"request_id={self.request_id!r}")
43
+ return f"{self.__class__.__name__}({', '.join(attrs)})"
44
+
45
+
46
+ class AuthenticationError(FauxShieldError):
47
+ """Raised when the API key is invalid or missing (HTTP 401)."""
48
+
49
+ def __init__(
50
+ self,
51
+ message: str = "Invalid or missing API key",
52
+ code: Optional[str] = None,
53
+ request_id: Optional[str] = None,
54
+ ) -> None:
55
+ super().__init__(
56
+ message=message,
57
+ status_code=401,
58
+ code=code or "authentication_error",
59
+ request_id=request_id,
60
+ )
61
+
62
+
63
+ class AuthorizationError(FauxShieldError):
64
+ """Raised when the API key lacks permission for the operation (HTTP 403)."""
65
+
66
+ def __init__(
67
+ self,
68
+ message: str = "Insufficient permissions for this operation",
69
+ code: Optional[str] = None,
70
+ request_id: Optional[str] = None,
71
+ ) -> None:
72
+ super().__init__(
73
+ message=message,
74
+ status_code=403,
75
+ code=code or "authorization_error",
76
+ request_id=request_id,
77
+ )
78
+
79
+
80
+ class ValidationError(FauxShieldError):
81
+ """Raised when request parameters fail validation (HTTP 400/422).
82
+
83
+ Attributes:
84
+ fields: List of field-level validation errors from the API.
85
+ """
86
+
87
+ def __init__(
88
+ self,
89
+ message: str = "Request validation failed",
90
+ fields: Optional[List[Dict[str, Any]]] = None,
91
+ code: Optional[str] = None,
92
+ request_id: Optional[str] = None,
93
+ ) -> None:
94
+ super().__init__(
95
+ message=message,
96
+ status_code=422,
97
+ code=code or "validation_error",
98
+ request_id=request_id,
99
+ )
100
+ self.fields: List[Dict[str, Any]] = fields or []
101
+
102
+ def __repr__(self) -> str:
103
+ base = super().__repr__()
104
+ return f"{base[:-1]}, fields={self.fields!r})"
105
+
106
+
107
+ class NotFoundError(FauxShieldError):
108
+ """Raised when the requested resource does not exist (HTTP 404)."""
109
+
110
+ def __init__(
111
+ self,
112
+ message: str = "Resource not found",
113
+ code: Optional[str] = None,
114
+ request_id: Optional[str] = None,
115
+ ) -> None:
116
+ super().__init__(
117
+ message=message,
118
+ status_code=404,
119
+ code=code or "not_found",
120
+ request_id=request_id,
121
+ )
122
+
123
+
124
+ class RateLimitError(FauxShieldError):
125
+ """Raised when the rate limit has been exceeded (HTTP 429).
126
+
127
+ Attributes:
128
+ retry_after: Number of seconds to wait before retrying.
129
+ """
130
+
131
+ def __init__(
132
+ self,
133
+ message: str = "Rate limit exceeded",
134
+ retry_after: Optional[float] = None,
135
+ code: Optional[str] = None,
136
+ request_id: Optional[str] = None,
137
+ ) -> None:
138
+ super().__init__(
139
+ message=message,
140
+ status_code=429,
141
+ code=code or "rate_limit_exceeded",
142
+ request_id=request_id,
143
+ )
144
+ self.retry_after: Optional[float] = retry_after
145
+
146
+ def __repr__(self) -> str:
147
+ base = super().__repr__()
148
+ return f"{base[:-1]}, retry_after={self.retry_after})"
149
+
150
+
151
+ class ConflictError(FauxShieldError):
152
+ """Raised when the request conflicts with existing state (HTTP 409)."""
153
+
154
+ def __init__(
155
+ self,
156
+ message: str = "Request conflicts with current state",
157
+ code: Optional[str] = None,
158
+ request_id: Optional[str] = None,
159
+ ) -> None:
160
+ super().__init__(
161
+ message=message,
162
+ status_code=409,
163
+ code=code or "conflict",
164
+ request_id=request_id,
165
+ )
166
+
167
+
168
+ class ServiceUnavailableError(FauxShieldError):
169
+ """Raised when the API is temporarily unavailable (HTTP 503)."""
170
+
171
+ def __init__(
172
+ self,
173
+ message: str = "Service temporarily unavailable",
174
+ code: Optional[str] = None,
175
+ request_id: Optional[str] = None,
176
+ ) -> None:
177
+ super().__init__(
178
+ message=message,
179
+ status_code=503,
180
+ code=code or "service_unavailable",
181
+ request_id=request_id,
182
+ )