devora-fastapi 0.1.1__tar.gz → 0.1.2__tar.gz
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.
- {devora_fastapi-0.1.1 → devora_fastapi-0.1.2}/PKG-INFO +42 -31
- {devora_fastapi-0.1.1 → devora_fastapi-0.1.2}/README.md +40 -29
- {devora_fastapi-0.1.1 → devora_fastapi-0.1.2}/pyproject.toml +2 -2
- {devora_fastapi-0.1.1 → devora_fastapi-0.1.2}/.gitignore +0 -0
- {devora_fastapi-0.1.1 → devora_fastapi-0.1.2}/LICENSE +0 -0
- {devora_fastapi-0.1.1 → devora_fastapi-0.1.2}/src/devora_sdk_fastapi/__init__.py +0 -0
- {devora_fastapi-0.1.1 → devora_fastapi-0.1.2}/src/devora_sdk_fastapi/adapter.py +0 -0
- {devora_fastapi-0.1.1 → devora_fastapi-0.1.2}/src/devora_sdk_fastapi/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: devora-fastapi
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: FastAPI adapter for the Devora Python backend SDK
|
|
5
5
|
Project-URL: Documentation, https://docs.devora.sh
|
|
6
6
|
Project-URL: Repository, https://github.com/getdevora/devora-sdks
|
|
@@ -21,7 +21,7 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
21
21
|
Classifier: Typing :: Typed
|
|
22
22
|
Requires-Python: <4.0,>=3.10
|
|
23
23
|
Requires-Dist: anyio>=4.14.2
|
|
24
|
-
Requires-Dist: devora-python<0.2.0,>=0.1.
|
|
24
|
+
Requires-Dist: devora-python<0.2.0,>=0.1.2
|
|
25
25
|
Requires-Dist: fastapi<1.0.0,>=0.133.0
|
|
26
26
|
Requires-Dist: idna>=3.15
|
|
27
27
|
Requires-Dist: starlette>=1.3.1
|
|
@@ -41,13 +41,12 @@ FastAPI adapter for the Devora Python backend SDK.
|
|
|
41
41
|
|
|
42
42
|
- Python `>=3.10,<4.0`
|
|
43
43
|
- FastAPI `>=0.133.0,<1.0.0`
|
|
44
|
-
-
|
|
45
|
-
Redis 6.2 or newer, for `SET ... PXAT`)
|
|
44
|
+
- Outbound HTTPS access from your backend to the Devora API
|
|
46
45
|
|
|
47
46
|
## Install
|
|
48
47
|
|
|
49
48
|
```bash
|
|
50
|
-
pip install devora-python devora-fastapi
|
|
49
|
+
pip install devora-python devora-fastapi
|
|
51
50
|
```
|
|
52
51
|
|
|
53
52
|
## Quick Start
|
|
@@ -55,56 +54,68 @@ pip install devora-python devora-fastapi redis
|
|
|
55
54
|
```python
|
|
56
55
|
import os
|
|
57
56
|
|
|
58
|
-
import redis
|
|
59
57
|
from fastapi import FastAPI
|
|
60
58
|
from devora_sdk import DEVORA_ENDPOINTS, devora_sdk
|
|
61
59
|
from devora_sdk_fastapi import fastapi_router
|
|
62
60
|
|
|
63
|
-
# Replay protection shared by every worker and instance (required in production).
|
|
64
|
-
redis_client = redis.Redis.from_url(os.environ["REDIS_URL"])
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class RedisReplayStore:
|
|
68
|
-
def consume(self, namespace: str, request_id: str, expires_at: int) -> bool:
|
|
69
|
-
# Atomic insert-if-absent kept until expires_at; an exception makes the SDK fail closed (503).
|
|
70
|
-
key = f"devora:replay:{namespace}:{request_id}"
|
|
71
|
-
return bool(redis_client.set(key, b"1", nx=True, pxat=expires_at))
|
|
72
|
-
|
|
73
61
|
|
|
74
62
|
sdk = devora_sdk(
|
|
75
63
|
api_key=os.environ["DEVORA_API_KEY"], # pk_server_live_...
|
|
76
64
|
secret_key=os.environ["DEVORA_SECRET_KEY"], # sk_server_live_...
|
|
77
65
|
org_id=os.environ["DEVORA_ORG_ID"],
|
|
78
|
-
replay_store=RedisReplayStore(),
|
|
79
66
|
)
|
|
80
67
|
|
|
81
68
|
|
|
82
69
|
@sdk.register(DEVORA_ENDPOINTS.USER_SEARCH)
|
|
83
70
|
async def search_users(req):
|
|
84
|
-
|
|
71
|
+
# Match name, email and the exact user ID with a parameterised query.
|
|
72
|
+
term = req.query.get("term", "")
|
|
73
|
+
limit = int(req.query.get("limit", 10))
|
|
74
|
+
users = await search_customer_users(term, limit)
|
|
75
|
+
return {
|
|
76
|
+
"users": [
|
|
77
|
+
{
|
|
78
|
+
"id": u.id,
|
|
79
|
+
"name": u.name,
|
|
80
|
+
"email": u.email,
|
|
81
|
+
"attributes": {"company": u.company, "role": u.role, "plan": u.plan},
|
|
82
|
+
}
|
|
83
|
+
for u in users
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@sdk.register(DEVORA_ENDPOINTS.TERMINATE)
|
|
89
|
+
async def terminate(req):
|
|
90
|
+
session_id = req.params["id"]
|
|
91
|
+
reason = (req.body or {}).get("reason")
|
|
92
|
+
# YOU IMPLEMENT: mark this Devora session revoked so every credential issued for it
|
|
93
|
+
# is rejected, including one issued after this call. Must be idempotent: Devora
|
|
94
|
+
# retries with a new request id (up to 5 attempts over 6 hours).
|
|
95
|
+
await auth.revoke_impersonation_session(session_id=session_id, reason=reason)
|
|
96
|
+
return {"success": True}
|
|
85
97
|
|
|
86
98
|
|
|
87
99
|
app = FastAPI()
|
|
88
100
|
app.include_router(fastapi_router(sdk), prefix="/devora")
|
|
89
101
|
```
|
|
90
102
|
|
|
103
|
+
User search should match name, email and the exact user ID. `attributes` are optional display fields such as company, role or plan: up to 12 per user, lowercase keys like `last_login`, and string, number, boolean or `null` values. Devora drops invalid entries silently; see [Search results and templates](https://docs.devora.sh/guide/search-results) for the limits and how your team lays out results.
|
|
104
|
+
|
|
91
105
|
Register every handler before calling `fastapi_router(sdk)`; routes registered
|
|
92
106
|
later are not mounted. Handlers may be sync or async; sync handlers run in a
|
|
93
107
|
worker thread.
|
|
94
108
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
`DEVORA_ENV=development` (or pass `environment="development"`). The SDK then
|
|
106
|
-
keeps request ids in memory, which protects a single process only. Never use
|
|
107
|
-
this in production.
|
|
109
|
+
Each verified request is claimed once from Devora before your handler runs, so
|
|
110
|
+
replay protection needs no storage on your side; your backend only needs
|
|
111
|
+
outbound HTTPS access to the Devora API. See
|
|
112
|
+
[SIGNING.md](https://github.com/getdevora/devora-sdks/blob/main/SIGNING.md#request-claims).
|
|
113
|
+
|
|
114
|
+
The terminate body is `{"reason": ..., "terminatedBy": ...}`; Devora retries a
|
|
115
|
+
failed call (up to 5 attempts over 6 hours) with a new request id, so keep the
|
|
116
|
+
handler idempotent. Return a 2xx, or 429/503 with `Retry-After` when overloaded; any
|
|
117
|
+
other 4xx stops the retries. See
|
|
118
|
+
[Session lifecycle & cleanup](https://docs.devora.sh/guide/session-lifecycle).
|
|
108
119
|
|
|
109
120
|
For protected application routes, install the guard middleware and return the
|
|
110
121
|
context dict your `IMPERSONATE` handler stored, read back from your
|
|
@@ -12,13 +12,12 @@ FastAPI adapter for the Devora Python backend SDK.
|
|
|
12
12
|
|
|
13
13
|
- Python `>=3.10,<4.0`
|
|
14
14
|
- FastAPI `>=0.133.0,<1.0.0`
|
|
15
|
-
-
|
|
16
|
-
Redis 6.2 or newer, for `SET ... PXAT`)
|
|
15
|
+
- Outbound HTTPS access from your backend to the Devora API
|
|
17
16
|
|
|
18
17
|
## Install
|
|
19
18
|
|
|
20
19
|
```bash
|
|
21
|
-
pip install devora-python devora-fastapi
|
|
20
|
+
pip install devora-python devora-fastapi
|
|
22
21
|
```
|
|
23
22
|
|
|
24
23
|
## Quick Start
|
|
@@ -26,56 +25,68 @@ pip install devora-python devora-fastapi redis
|
|
|
26
25
|
```python
|
|
27
26
|
import os
|
|
28
27
|
|
|
29
|
-
import redis
|
|
30
28
|
from fastapi import FastAPI
|
|
31
29
|
from devora_sdk import DEVORA_ENDPOINTS, devora_sdk
|
|
32
30
|
from devora_sdk_fastapi import fastapi_router
|
|
33
31
|
|
|
34
|
-
# Replay protection shared by every worker and instance (required in production).
|
|
35
|
-
redis_client = redis.Redis.from_url(os.environ["REDIS_URL"])
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class RedisReplayStore:
|
|
39
|
-
def consume(self, namespace: str, request_id: str, expires_at: int) -> bool:
|
|
40
|
-
# Atomic insert-if-absent kept until expires_at; an exception makes the SDK fail closed (503).
|
|
41
|
-
key = f"devora:replay:{namespace}:{request_id}"
|
|
42
|
-
return bool(redis_client.set(key, b"1", nx=True, pxat=expires_at))
|
|
43
|
-
|
|
44
32
|
|
|
45
33
|
sdk = devora_sdk(
|
|
46
34
|
api_key=os.environ["DEVORA_API_KEY"], # pk_server_live_...
|
|
47
35
|
secret_key=os.environ["DEVORA_SECRET_KEY"], # sk_server_live_...
|
|
48
36
|
org_id=os.environ["DEVORA_ORG_ID"],
|
|
49
|
-
replay_store=RedisReplayStore(),
|
|
50
37
|
)
|
|
51
38
|
|
|
52
39
|
|
|
53
40
|
@sdk.register(DEVORA_ENDPOINTS.USER_SEARCH)
|
|
54
41
|
async def search_users(req):
|
|
55
|
-
|
|
42
|
+
# Match name, email and the exact user ID with a parameterised query.
|
|
43
|
+
term = req.query.get("term", "")
|
|
44
|
+
limit = int(req.query.get("limit", 10))
|
|
45
|
+
users = await search_customer_users(term, limit)
|
|
46
|
+
return {
|
|
47
|
+
"users": [
|
|
48
|
+
{
|
|
49
|
+
"id": u.id,
|
|
50
|
+
"name": u.name,
|
|
51
|
+
"email": u.email,
|
|
52
|
+
"attributes": {"company": u.company, "role": u.role, "plan": u.plan},
|
|
53
|
+
}
|
|
54
|
+
for u in users
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@sdk.register(DEVORA_ENDPOINTS.TERMINATE)
|
|
60
|
+
async def terminate(req):
|
|
61
|
+
session_id = req.params["id"]
|
|
62
|
+
reason = (req.body or {}).get("reason")
|
|
63
|
+
# YOU IMPLEMENT: mark this Devora session revoked so every credential issued for it
|
|
64
|
+
# is rejected, including one issued after this call. Must be idempotent: Devora
|
|
65
|
+
# retries with a new request id (up to 5 attempts over 6 hours).
|
|
66
|
+
await auth.revoke_impersonation_session(session_id=session_id, reason=reason)
|
|
67
|
+
return {"success": True}
|
|
56
68
|
|
|
57
69
|
|
|
58
70
|
app = FastAPI()
|
|
59
71
|
app.include_router(fastapi_router(sdk), prefix="/devora")
|
|
60
72
|
```
|
|
61
73
|
|
|
74
|
+
User search should match name, email and the exact user ID. `attributes` are optional display fields such as company, role or plan: up to 12 per user, lowercase keys like `last_login`, and string, number, boolean or `null` values. Devora drops invalid entries silently; see [Search results and templates](https://docs.devora.sh/guide/search-results) for the limits and how your team lays out results.
|
|
75
|
+
|
|
62
76
|
Register every handler before calling `fastapi_router(sdk)`; routes registered
|
|
63
77
|
later are not mounted. Handlers may be sync or async; sync handlers run in a
|
|
64
78
|
worker thread.
|
|
65
79
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
`DEVORA_ENV=development` (or pass `environment="development"`). The SDK then
|
|
77
|
-
keeps request ids in memory, which protects a single process only. Never use
|
|
78
|
-
this in production.
|
|
80
|
+
Each verified request is claimed once from Devora before your handler runs, so
|
|
81
|
+
replay protection needs no storage on your side; your backend only needs
|
|
82
|
+
outbound HTTPS access to the Devora API. See
|
|
83
|
+
[SIGNING.md](https://github.com/getdevora/devora-sdks/blob/main/SIGNING.md#request-claims).
|
|
84
|
+
|
|
85
|
+
The terminate body is `{"reason": ..., "terminatedBy": ...}`; Devora retries a
|
|
86
|
+
failed call (up to 5 attempts over 6 hours) with a new request id, so keep the
|
|
87
|
+
handler idempotent. Return a 2xx, or 429/503 with `Retry-After` when overloaded; any
|
|
88
|
+
other 4xx stops the retries. See
|
|
89
|
+
[Session lifecycle & cleanup](https://docs.devora.sh/guide/session-lifecycle).
|
|
79
90
|
|
|
80
91
|
For protected application routes, install the guard middleware and return the
|
|
81
92
|
context dict your `IMPERSONATE` handler stored, read back from your
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "devora-fastapi"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "FastAPI adapter for the Devora Python backend SDK"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10,<4.0"
|
|
@@ -25,7 +25,7 @@ classifiers = [
|
|
|
25
25
|
"Typing :: Typed",
|
|
26
26
|
]
|
|
27
27
|
dependencies = [
|
|
28
|
-
"devora-python>=0.1.
|
|
28
|
+
"devora-python>=0.1.2,<0.2.0",
|
|
29
29
|
# Security floors: the lowest versions without known advisories (audited 2026-09-28).
|
|
30
30
|
"fastapi>=0.133.0,<1.0.0",
|
|
31
31
|
"starlette>=1.3.1",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|