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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: devora-fastapi
3
- Version: 0.1.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.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
- - In production, a replay store shared by every worker (the example below uses
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 redis
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
- return {"users": await search_customer_users(req.query.get("term", ""))}
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
- The environment comes from `environment=`, else `DEVORA_ENV`, else `NODE_ENV`.
96
- Anything other than `development` or `test` (including unset) is production,
97
- and production refuses to start without a `replay_store`. Any store whose
98
- `consume` is an atomic insert-if-absent shared by every worker works; see the
99
- [replay-store contract](https://github.com/getdevora/devora-sdks/blob/main/SIGNING.md#replay-store).
100
- `consume` must be a regular method: the SDK calls it from a worker thread, so
101
- use the synchronous `redis.Redis` client even in an async app. An
102
- `async def consume` fails closed with a 503.
103
-
104
- **Local development only:** to run without Redis, omit `replay_store` and set
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
- - In production, a replay store shared by every worker (the example below uses
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 redis
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
- return {"users": await search_customer_users(req.query.get("term", ""))}
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
- The environment comes from `environment=`, else `DEVORA_ENV`, else `NODE_ENV`.
67
- Anything other than `development` or `test` (including unset) is production,
68
- and production refuses to start without a `replay_store`. Any store whose
69
- `consume` is an atomic insert-if-absent shared by every worker works; see the
70
- [replay-store contract](https://github.com/getdevora/devora-sdks/blob/main/SIGNING.md#replay-store).
71
- `consume` must be a regular method: the SDK calls it from a worker thread, so
72
- use the synchronous `redis.Redis` client even in an async app. An
73
- `async def consume` fails closed with a 503.
74
-
75
- **Local development only:** to run without Redis, omit `replay_store` and set
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.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.1,<0.2.0",
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