authpi-admin 0.3.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 (40) hide show
  1. authpi_admin/__init__.py +55 -0
  2. authpi_admin/_version.py +5 -0
  3. authpi_admin/client.py +155 -0
  4. authpi_admin/errors.py +218 -0
  5. authpi_admin/generated/__init__.py +28 -0
  6. authpi_admin/generated/models.py +380 -0
  7. authpi_admin/generated/resources/__init__.py +0 -0
  8. authpi_admin/generated/resources/accounts.py +157 -0
  9. authpi_admin/generated/resources/agents.py +177 -0
  10. authpi_admin/generated/resources/api_keys.py +213 -0
  11. authpi_admin/generated/resources/approvals.py +109 -0
  12. authpi_admin/generated/resources/auth_methods.py +148 -0
  13. authpi_admin/generated/resources/clients.py +197 -0
  14. authpi_admin/generated/resources/deliveries.py +83 -0
  15. authpi_admin/generated/resources/domains.py +194 -0
  16. authpi_admin/generated/resources/events.py +111 -0
  17. authpi_admin/generated/resources/groups.py +160 -0
  18. authpi_admin/generated/resources/invitations.py +202 -0
  19. authpi_admin/generated/resources/issuers.py +157 -0
  20. authpi_admin/generated/resources/members.py +154 -0
  21. authpi_admin/generated/resources/notes.py +192 -0
  22. authpi_admin/generated/resources/organizations.py +244 -0
  23. authpi_admin/generated/resources/sessions.py +125 -0
  24. authpi_admin/generated/resources/tokens.py +94 -0
  25. authpi_admin/generated/resources/trusted_devices.py +102 -0
  26. authpi_admin/generated/resources/users.py +224 -0
  27. authpi_admin/generated/resources/verifiers.py +102 -0
  28. authpi_admin/generated/resources/webhooks.py +167 -0
  29. authpi_admin/generated/scopes/__init__.py +0 -0
  30. authpi_admin/generated/scopes/agent_scope.py +84 -0
  31. authpi_admin/generated/scopes/issuer_scope.py +160 -0
  32. authpi_admin/generated/scopes/user_scope.py +102 -0
  33. authpi_admin/generated/scopes/webhook_scope.py +84 -0
  34. authpi_admin/http_client.py +305 -0
  35. authpi_admin/pagination.py +41 -0
  36. authpi_admin/py.typed +0 -0
  37. authpi_admin/user_agent.py +21 -0
  38. authpi_admin-0.3.0.dist-info/METADATA +285 -0
  39. authpi_admin-0.3.0.dist-info/RECORD +40 -0
  40. authpi_admin-0.3.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,55 @@
1
+ """AuthPI Admin SDK — Official Python client for the AuthPI Core API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __version__ = "0.3.0"
6
+
7
+ from authpi_admin.client import AuthPIAdmin
8
+ from authpi_admin.errors import (
9
+ ApiError,
10
+ AuthenticationError,
11
+ BadGatewayError,
12
+ ClosedClientError,
13
+ ConfigurationError,
14
+ ConflictError,
15
+ ForbiddenError,
16
+ GatewayTimeoutError,
17
+ InternalServerError,
18
+ NotFoundError,
19
+ PreconditionFailedError,
20
+ RateLimitError,
21
+ ServerError,
22
+ ServiceUnavailableError,
23
+ UnexpectedError,
24
+ ValidationError,
25
+ )
26
+ from authpi_admin.generated.scopes.issuer_scope import IssuerScope
27
+ from authpi_admin.generated.scopes.user_scope import UserScope
28
+ from authpi_admin.generated.scopes.agent_scope import AgentScope
29
+ from authpi_admin.generated.scopes.webhook_scope import WebhookScope
30
+ from authpi_admin.pagination import Page
31
+
32
+ __all__ = [
33
+ "AgentScope",
34
+ "ApiError",
35
+ "AuthPIAdmin",
36
+ "AuthenticationError",
37
+ "BadGatewayError",
38
+ "ClosedClientError",
39
+ "ConfigurationError",
40
+ "ConflictError",
41
+ "ForbiddenError",
42
+ "GatewayTimeoutError",
43
+ "InternalServerError",
44
+ "IssuerScope",
45
+ "NotFoundError",
46
+ "Page",
47
+ "PreconditionFailedError",
48
+ "RateLimitError",
49
+ "ServerError",
50
+ "ServiceUnavailableError",
51
+ "UnexpectedError",
52
+ "UserScope",
53
+ "ValidationError",
54
+ "WebhookScope",
55
+ ]
@@ -0,0 +1,5 @@
1
+ """SDK version — kept in sync with pyproject.toml via release-please."""
2
+
3
+ # x-release-please-start-version
4
+ SDK_VERSION = "0.3.0"
5
+ # x-release-please-end
authpi_admin/client.py ADDED
@@ -0,0 +1,155 @@
1
+ """AuthPIAdmin — entry-point client for the AuthPI Admin SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from functools import cached_property
6
+ from typing import TYPE_CHECKING, Any, Awaitable, Callable
7
+
8
+ import httpx
9
+
10
+ from authpi_admin.errors import ConfigurationError
11
+ from authpi_admin.http_client import HttpClient
12
+ from authpi_admin.generated.resources.accounts import AccountsResource
13
+ from authpi_admin.generated.resources.api_keys import ApiKeysResource
14
+ from authpi_admin.generated.resources.domains import DomainsResource
15
+ from authpi_admin.generated.resources.events import EventsResource
16
+ from authpi_admin.generated.resources.issuers import IssuersResource
17
+ from authpi_admin.generated.resources.notes import NotesResource
18
+ from authpi_admin.generated.resources.tokens import TokensResource
19
+ from authpi_admin.generated.resources.webhooks import WebhooksResource
20
+ from authpi_admin.generated.scopes.issuer_scope import IssuerScope
21
+ from authpi_admin.generated.scopes.webhook_scope import WebhookScope
22
+
23
+ if TYPE_CHECKING:
24
+ from types import TracebackType
25
+
26
+
27
+ class AuthPIAdmin:
28
+ """Entry-point client for the AuthPI Core API.
29
+
30
+ Usage with API key:
31
+ async with AuthPIAdmin(api_key="key_xxx") as admin:
32
+ users = await admin.issuer("iss_xxx").users.list()
33
+
34
+ Usage with bearer token:
35
+ async with AuthPIAdmin(
36
+ access_token="tok_xxx",
37
+ account_id="acc_xxx",
38
+ ) as admin:
39
+ users = await admin.issuer("iss_xxx").users.list()
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ *,
45
+ api_key: str | None = None,
46
+ access_token: str | None = None,
47
+ on_token_expired: Callable[[], Awaitable[dict[str, str]]] | None = None,
48
+ account_id: str | None = None,
49
+ base_url: str = "https://api.authpi.dev",
50
+ http_client: httpx.AsyncClient | None = None,
51
+ timeout: float = 30.0,
52
+ default_headers: dict[str, str] | None = None,
53
+ retries: bool | dict[str, Any] | None = None,
54
+ ) -> None:
55
+ if not api_key and not access_token:
56
+ raise ConfigurationError("Either api_key or access_token is required")
57
+ if api_key and access_token:
58
+ raise ConfigurationError("Provide either api_key or access_token, not both")
59
+ if access_token and not account_id:
60
+ raise ConfigurationError("account_id is required when using access_token")
61
+
62
+ resolved_account_id = account_id or "me"
63
+ self._http = HttpClient(
64
+ base_url=base_url,
65
+ api_key=api_key,
66
+ access_token=access_token,
67
+ on_token_expired=on_token_expired,
68
+ http_client=http_client,
69
+ timeout=timeout,
70
+ default_headers=default_headers,
71
+ retries=retries,
72
+ )
73
+ self._base_path = f"/v1/accounts/{resolved_account_id}"
74
+
75
+ @property
76
+ def http(self) -> HttpClient:
77
+ """The underlying HTTP client (for advanced use)."""
78
+ return self._http
79
+
80
+ # --- Account-level resource accessors ---
81
+
82
+ @cached_property
83
+ def accounts(self) -> AccountsResource:
84
+ """Operations on the account itself."""
85
+ return AccountsResource(self._http, "/v1/accounts")
86
+
87
+ @cached_property
88
+ def api_keys(self) -> ApiKeysResource:
89
+ """Manage account-level API keys."""
90
+ return ApiKeysResource(self._http, f"{self._base_path}/api-keys")
91
+
92
+ @cached_property
93
+ def domains(self) -> DomainsResource:
94
+ """Manage account-level domains."""
95
+ return DomainsResource(self._http, f"{self._base_path}/domains")
96
+
97
+ @cached_property
98
+ def events(self) -> EventsResource:
99
+ """Query events."""
100
+ return EventsResource(self._http, f"{self._base_path}/events")
101
+
102
+ @cached_property
103
+ def issuers(self) -> IssuersResource:
104
+ """Manage issuers."""
105
+ return IssuersResource(self._http, f"{self._base_path}/issuers")
106
+
107
+ @cached_property
108
+ def notes(self) -> NotesResource:
109
+ """Manage notes."""
110
+ return NotesResource(self._http, f"{self._base_path}/notes")
111
+
112
+ @cached_property
113
+ def tokens(self) -> TokensResource:
114
+ """Manage account-level tokens."""
115
+ return TokensResource(self._http, f"{self._base_path}/tokens")
116
+
117
+ @cached_property
118
+ def webhooks(self) -> WebhooksResource:
119
+ """Manage webhooks."""
120
+ return WebhooksResource(self._http, f"{self._base_path}/webhooks")
121
+
122
+ # --- Scope accessors ---
123
+
124
+ def issuer(self, issuer_id: str) -> IssuerScope:
125
+ """Scope operations to a specific issuer."""
126
+ return IssuerScope(
127
+ client=self._http,
128
+ base_path=f"{self._base_path}/issuers",
129
+ issuer_id=issuer_id,
130
+ )
131
+
132
+ def webhook(self, webhook_id: str) -> WebhookScope:
133
+ """Scope operations to a specific webhook."""
134
+ return WebhookScope(
135
+ client=self._http,
136
+ base_path=f"{self._base_path}/webhooks",
137
+ webhook_id=webhook_id,
138
+ )
139
+
140
+ # --- Lifecycle ---
141
+
142
+ async def close(self) -> None:
143
+ """Close the underlying HTTP client."""
144
+ await self._http.close()
145
+
146
+ async def __aenter__(self) -> AuthPIAdmin:
147
+ return self
148
+
149
+ async def __aexit__(
150
+ self,
151
+ exc_type: type[BaseException] | None,
152
+ exc_val: BaseException | None,
153
+ exc_tb: TracebackType | None,
154
+ ) -> None:
155
+ await self.close()
authpi_admin/errors.py ADDED
@@ -0,0 +1,218 @@
1
+ """Error hierarchy for the AuthPI Admin SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import contextlib
6
+ from typing import Any
7
+
8
+ # Retryable status codes (when not overridden by response body)
9
+ _RETRYABLE_STATUSES = {408, 429, 502, 503, 504}
10
+
11
+
12
+ class ConfigurationError(Exception):
13
+ """Raised when the client is configured incorrectly."""
14
+
15
+
16
+ class ApiError(Exception):
17
+ """Base error for all API errors."""
18
+
19
+ def __init__(
20
+ self,
21
+ *,
22
+ error: str,
23
+ error_description: str,
24
+ status_code: int,
25
+ retryable: bool = False,
26
+ reference: str | None = None,
27
+ raw_body: str | None = None,
28
+ ) -> None:
29
+ super().__init__(error_description)
30
+ self.error = error
31
+ self.error_description = error_description
32
+ self.status_code = status_code
33
+ self.retryable = retryable
34
+ self.reference = reference
35
+ self.raw_body = raw_body
36
+
37
+
38
+ class ValidationError(ApiError):
39
+ """Raised for 400 and 422 responses with field-level validation errors."""
40
+
41
+ def __init__(self, *, fields: list[dict[str, Any]] | None = None, **kwargs: Any) -> None:
42
+ super().__init__(**kwargs)
43
+ self.fields = fields or []
44
+
45
+
46
+ class AuthenticationError(ApiError):
47
+ """Raised for 401 responses."""
48
+
49
+
50
+ class ForbiddenError(ApiError):
51
+ """Raised for 403 responses."""
52
+
53
+
54
+ class NotFoundError(ApiError):
55
+ """Raised for 404 responses."""
56
+
57
+
58
+ class ConflictError(ApiError):
59
+ """Raised for 409 responses."""
60
+
61
+
62
+ class PreconditionFailedError(ApiError):
63
+ """Raised for 412 responses (ETag mismatch)."""
64
+
65
+ def __init__(self, *, current_etag: str | None = None, **kwargs: Any) -> None:
66
+ super().__init__(**kwargs)
67
+ self.current_etag = current_etag
68
+
69
+
70
+ class RateLimitError(ApiError):
71
+ """Raised for 429 responses."""
72
+
73
+ def __init__(self, *, retry_after: int | None = None, **kwargs: Any) -> None:
74
+ kwargs.setdefault("retryable", True)
75
+ super().__init__(**kwargs)
76
+ self.retry_after = retry_after
77
+
78
+
79
+ class ServerError(ApiError):
80
+ """Base class for 5xx server errors."""
81
+
82
+
83
+ class InternalServerError(ServerError):
84
+ """Raised for 500 responses."""
85
+
86
+
87
+ class BadGatewayError(ServerError):
88
+ """Raised for 502 responses."""
89
+
90
+ def __init__(self, **kwargs: Any) -> None:
91
+ kwargs.setdefault("retryable", True)
92
+ super().__init__(**kwargs)
93
+
94
+
95
+ class ServiceUnavailableError(ServerError):
96
+ """Raised for 503 responses."""
97
+
98
+ def __init__(self, **kwargs: Any) -> None:
99
+ kwargs.setdefault("retryable", True)
100
+ super().__init__(**kwargs)
101
+
102
+
103
+ class GatewayTimeoutError(ServerError):
104
+ """Raised for 504 responses."""
105
+
106
+ def __init__(self, **kwargs: Any) -> None:
107
+ kwargs.setdefault("retryable", True)
108
+ super().__init__(**kwargs)
109
+
110
+
111
+ class UnexpectedError(ApiError):
112
+ """Raised for any status code not explicitly mapped."""
113
+
114
+
115
+ class ClosedClientError(ApiError):
116
+ """Raised when attempting to use a closed client."""
117
+
118
+ def __init__(self) -> None:
119
+ super().__init__(
120
+ error="closed_client",
121
+ error_description="Cannot make requests after the client has been closed",
122
+ status_code=0,
123
+ retryable=False,
124
+ )
125
+
126
+
127
+ # HTTP status text fallbacks
128
+ _STATUS_TEXT: dict[int, str] = {
129
+ 400: "Bad Request",
130
+ 401: "Unauthorized",
131
+ 403: "Forbidden",
132
+ 404: "Not Found",
133
+ 408: "Request Timeout",
134
+ 409: "Conflict",
135
+ 412: "Precondition Failed",
136
+ 422: "Unprocessable Entity",
137
+ 429: "Too Many Requests",
138
+ 500: "Internal Server Error",
139
+ 502: "Bad Gateway",
140
+ 503: "Service Unavailable",
141
+ 504: "Gateway Timeout",
142
+ }
143
+
144
+ # Status code -> error class mapping
145
+ _STATUS_MAP: dict[int, type[ApiError]] = {
146
+ 400: ValidationError,
147
+ 401: AuthenticationError,
148
+ 403: ForbiddenError,
149
+ 404: NotFoundError,
150
+ 409: ConflictError,
151
+ 412: PreconditionFailedError,
152
+ 422: ValidationError,
153
+ 429: RateLimitError,
154
+ 500: InternalServerError,
155
+ 502: BadGatewayError,
156
+ 503: ServiceUnavailableError,
157
+ 504: GatewayTimeoutError,
158
+ }
159
+
160
+
161
+ def error_from_response(
162
+ *,
163
+ status_code: int,
164
+ body: dict[str, Any] | None,
165
+ headers: dict[str, str],
166
+ raw_body: str | None = None,
167
+ ) -> ApiError:
168
+ """Create the appropriate error from an HTTP response.
169
+
170
+ Args:
171
+ status_code: HTTP status code.
172
+ body: Parsed JSON body, or None if parsing failed.
173
+ headers: Response headers (lowercase keys).
174
+ raw_body: Raw body string for non-JSON responses.
175
+ """
176
+ body = body or {}
177
+ error = body.get("error", "unknown")
178
+ error_description = body.get("error_description", _STATUS_TEXT.get(status_code, "Unknown Error"))
179
+ reference = body.get("reference")
180
+ retryable = body.get("retryable", status_code in _RETRYABLE_STATUSES)
181
+
182
+ base_kwargs: dict[str, Any] = {
183
+ "error": error,
184
+ "error_description": error_description,
185
+ "status_code": status_code,
186
+ "retryable": retryable,
187
+ "reference": reference,
188
+ "raw_body": raw_body,
189
+ }
190
+
191
+ error_class = _STATUS_MAP.get(status_code)
192
+
193
+ if error_class is None:
194
+ # 408 gets base ApiError with retryable=True
195
+ if status_code == 408:
196
+ return ApiError(**base_kwargs)
197
+ # Unmapped 5xx codes get the base ServerError
198
+ if 500 <= status_code < 600:
199
+ return ServerError(**base_kwargs)
200
+ return UnexpectedError(**base_kwargs)
201
+
202
+ # Build extra kwargs for specific error classes
203
+ if error_class is ValidationError:
204
+ return ValidationError(fields=body.get("fields"), **base_kwargs)
205
+
206
+ if error_class is PreconditionFailedError:
207
+ return PreconditionFailedError(current_etag=body.get("current_etag"), **base_kwargs)
208
+
209
+ if error_class is RateLimitError:
210
+ retry_after = body.get("retry_after")
211
+ if retry_after is None:
212
+ header_val = headers.get("retry-after")
213
+ if header_val is not None:
214
+ with contextlib.suppress(ValueError):
215
+ retry_after = int(header_val)
216
+ return RateLimitError(retry_after=retry_after, **base_kwargs)
217
+
218
+ return error_class(**base_kwargs)
@@ -0,0 +1,28 @@
1
+ """Generated exports — DO NOT EDIT."""
2
+
3
+ from .models import * # noqa: F401,F403
4
+ from .resources.accounts import AccountsResource # noqa: F401
5
+ from .resources.api_keys import ApiKeysResource # noqa: F401
6
+ from .resources.domains import DomainsResource # noqa: F401
7
+ from .resources.events import EventsResource # noqa: F401
8
+ from .resources.issuers import IssuersResource # noqa: F401
9
+ from .resources.agents import AgentsResource # noqa: F401
10
+ from .resources.verifiers import VerifiersResource # noqa: F401
11
+ from .resources.approvals import ApprovalsResource # noqa: F401
12
+ from .resources.auth_methods import AuthMethodsResource # noqa: F401
13
+ from .resources.clients import ClientsResource # noqa: F401
14
+ from .resources.organizations import OrganizationsResource # noqa: F401
15
+ from .resources.groups import GroupsResource # noqa: F401
16
+ from .resources.invitations import InvitationsResource # noqa: F401
17
+ from .resources.members import MembersResource # noqa: F401
18
+ from .resources.sessions import SessionsResource # noqa: F401
19
+ from .resources.users import UsersResource # noqa: F401
20
+ from .resources.tokens import TokensResource # noqa: F401
21
+ from .resources.trusted_devices import TrustedDevicesResource # noqa: F401
22
+ from .resources.notes import NotesResource # noqa: F401
23
+ from .resources.webhooks import WebhooksResource # noqa: F401
24
+ from .resources.deliveries import DeliveriesResource # noqa: F401
25
+ from .scopes.issuer_scope import IssuerScope # noqa: F401
26
+ from .scopes.agent_scope import AgentScope # noqa: F401
27
+ from .scopes.user_scope import UserScope # noqa: F401
28
+ from .scopes.webhook_scope import WebhookScope # noqa: F401