devora-fastapi 0.1.0__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.
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.5
2
+ Name: devora-fastapi
3
+ Version: 0.1.2
4
+ Summary: FastAPI adapter for the Devora Python backend SDK
5
+ Project-URL: Documentation, https://docs.devora.sh
6
+ Project-URL: Repository, https://github.com/getdevora/devora-sdks
7
+ Project-URL: Issues, https://github.com/getdevora/devora-sdks/issues
8
+ Author: Devora
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: devora,fastapi,impersonation,sdk,security
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Framework :: FastAPI
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: <4.0,>=3.10
23
+ Requires-Dist: anyio>=4.14.2
24
+ Requires-Dist: devora-python<0.2.0,>=0.1.2
25
+ Requires-Dist: fastapi<1.0.0,>=0.133.0
26
+ Requires-Dist: idna>=3.15
27
+ Requires-Dist: starlette>=1.3.1
28
+ Description-Content-Type: text/markdown
29
+
30
+ # devora-fastapi
31
+
32
+ Recording, masking and capture policy are configured in the Devora dashboard and
33
+ authorized server-side for each session. This backend SDK takes no capture
34
+ settings, and `devora_sdk()` ignores keyword arguments it does not recognize,
35
+ so check option names carefully. See
36
+ [capture settings](https://github.com/getdevora/devora-sdks/blob/main/SETTINGS.md).
37
+
38
+ FastAPI adapter for the Devora Python backend SDK.
39
+
40
+ ## Requirements
41
+
42
+ - Python `>=3.10,<4.0`
43
+ - FastAPI `>=0.133.0,<1.0.0`
44
+ - Outbound HTTPS access from your backend to the Devora API
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ pip install devora-python devora-fastapi
50
+ ```
51
+
52
+ ## Quick Start
53
+
54
+ ```python
55
+ import os
56
+
57
+ from fastapi import FastAPI
58
+ from devora_sdk import DEVORA_ENDPOINTS, devora_sdk
59
+ from devora_sdk_fastapi import fastapi_router
60
+
61
+
62
+ sdk = devora_sdk(
63
+ api_key=os.environ["DEVORA_API_KEY"], # pk_server_live_...
64
+ secret_key=os.environ["DEVORA_SECRET_KEY"], # sk_server_live_...
65
+ org_id=os.environ["DEVORA_ORG_ID"],
66
+ )
67
+
68
+
69
+ @sdk.register(DEVORA_ENDPOINTS.USER_SEARCH)
70
+ async def search_users(req):
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}
97
+
98
+
99
+ app = FastAPI()
100
+ app.include_router(fastapi_router(sdk), prefix="/devora")
101
+ ```
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
+
105
+ Register every handler before calling `fastapi_router(sdk)`; routes registered
106
+ later are not mounted. Handlers may be sync or async; sync handlers run in a
107
+ worker thread.
108
+
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).
119
+
120
+ For protected application routes, install the guard middleware and return the
121
+ context dict your `IMPERSONATE` handler stored, read back from your
122
+ authenticated request state.
123
+
124
+ ```python
125
+ from devora_sdk_fastapi import DevoraImpersonationGuardMiddleware
126
+
127
+
128
+ def get_impersonation_context(request) -> dict | None:
129
+ # YOU IMPLEMENT: return the dict your IMPERSONATE handler stored, read from
130
+ # your auth middleware (never from headers or JSON the browser can set), or None.
131
+ return getattr(request.state, "devora", None)
132
+
133
+
134
+ app.add_middleware(
135
+ DevoraImpersonationGuardMiddleware,
136
+ sdk=sdk,
137
+ get_impersonation_context=get_impersonation_context,
138
+ )
139
+ ```
140
+
141
+ Register your own authentication middleware **after** this call — Starlette
142
+ runs middleware in the reverse of its registration order, so it must be the
143
+ outer layer that runs first for `request.state` to carry verified claims by
144
+ the time the guard reads them.
145
+
146
+ Resolve method overrides and route rewrites **before** the guard as well
147
+ (register that middleware after this call too, so it runs first). The guard
148
+ also judges every `X-HTTP-Method-Override`, `X-HTTP-Method` and
149
+ `X-Method-Override` value and any `_method` query parameter, but it cannot see a
150
+ `_method` field inside a request body, and it judges the path it receives.
151
+
152
+ `expires_at` must be a Unix timestamp in milliseconds. If your JWT stores Unix
153
+ seconds, multiply by `1000` when building the impersonation context. `actor`,
154
+ `subject`, `auth_method`, `authorization_source`, and `recording_allowed` are
155
+ all required — the guard rejects the context as invalid without them, even
156
+ though the dataclass marks them optional for construction convenience.
157
+
158
+ Options, error codes and the browser-session bridge are documented in the
159
+ [Python reference](https://docs.devora.sh/reference/python).
@@ -0,0 +1,130 @@
1
+ # devora-fastapi
2
+
3
+ Recording, masking and capture policy are configured in the Devora dashboard and
4
+ authorized server-side for each session. This backend SDK takes no capture
5
+ settings, and `devora_sdk()` ignores keyword arguments it does not recognize,
6
+ so check option names carefully. See
7
+ [capture settings](https://github.com/getdevora/devora-sdks/blob/main/SETTINGS.md).
8
+
9
+ FastAPI adapter for the Devora Python backend SDK.
10
+
11
+ ## Requirements
12
+
13
+ - Python `>=3.10,<4.0`
14
+ - FastAPI `>=0.133.0,<1.0.0`
15
+ - Outbound HTTPS access from your backend to the Devora API
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ pip install devora-python devora-fastapi
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```python
26
+ import os
27
+
28
+ from fastapi import FastAPI
29
+ from devora_sdk import DEVORA_ENDPOINTS, devora_sdk
30
+ from devora_sdk_fastapi import fastapi_router
31
+
32
+
33
+ sdk = devora_sdk(
34
+ api_key=os.environ["DEVORA_API_KEY"], # pk_server_live_...
35
+ secret_key=os.environ["DEVORA_SECRET_KEY"], # sk_server_live_...
36
+ org_id=os.environ["DEVORA_ORG_ID"],
37
+ )
38
+
39
+
40
+ @sdk.register(DEVORA_ENDPOINTS.USER_SEARCH)
41
+ async def search_users(req):
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}
68
+
69
+
70
+ app = FastAPI()
71
+ app.include_router(fastapi_router(sdk), prefix="/devora")
72
+ ```
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
+
76
+ Register every handler before calling `fastapi_router(sdk)`; routes registered
77
+ later are not mounted. Handlers may be sync or async; sync handlers run in a
78
+ worker thread.
79
+
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).
90
+
91
+ For protected application routes, install the guard middleware and return the
92
+ context dict your `IMPERSONATE` handler stored, read back from your
93
+ authenticated request state.
94
+
95
+ ```python
96
+ from devora_sdk_fastapi import DevoraImpersonationGuardMiddleware
97
+
98
+
99
+ def get_impersonation_context(request) -> dict | None:
100
+ # YOU IMPLEMENT: return the dict your IMPERSONATE handler stored, read from
101
+ # your auth middleware (never from headers or JSON the browser can set), or None.
102
+ return getattr(request.state, "devora", None)
103
+
104
+
105
+ app.add_middleware(
106
+ DevoraImpersonationGuardMiddleware,
107
+ sdk=sdk,
108
+ get_impersonation_context=get_impersonation_context,
109
+ )
110
+ ```
111
+
112
+ Register your own authentication middleware **after** this call — Starlette
113
+ runs middleware in the reverse of its registration order, so it must be the
114
+ outer layer that runs first for `request.state` to carry verified claims by
115
+ the time the guard reads them.
116
+
117
+ Resolve method overrides and route rewrites **before** the guard as well
118
+ (register that middleware after this call too, so it runs first). The guard
119
+ also judges every `X-HTTP-Method-Override`, `X-HTTP-Method` and
120
+ `X-Method-Override` value and any `_method` query parameter, but it cannot see a
121
+ `_method` field inside a request body, and it judges the path it receives.
122
+
123
+ `expires_at` must be a Unix timestamp in milliseconds. If your JWT stores Unix
124
+ seconds, multiply by `1000` when building the impersonation context. `actor`,
125
+ `subject`, `auth_method`, `authorization_source`, and `recording_allowed` are
126
+ all required — the guard rejects the context as invalid without them, even
127
+ though the dataclass marks them optional for construction convenience.
128
+
129
+ Options, error codes and the browser-session bridge are documented in the
130
+ [Python reference](https://docs.devora.sh/reference/python).
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "devora-fastapi"
7
- version = "0.1.0"
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.0,<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",
@@ -1,122 +0,0 @@
1
- Metadata-Version: 2.5
2
- Name: devora-fastapi
3
- Version: 0.1.0
4
- Summary: FastAPI adapter for the Devora Python backend SDK
5
- Project-URL: Documentation, https://docs.devora.sh
6
- Project-URL: Repository, https://github.com/getdevora/devora-sdks
7
- Project-URL: Issues, https://github.com/getdevora/devora-sdks/issues
8
- Author: Devora
9
- License-Expression: MIT
10
- License-File: LICENSE
11
- Keywords: devora,fastapi,impersonation,sdk,security
12
- Classifier: Development Status :: 5 - Production/Stable
13
- Classifier: Framework :: FastAPI
14
- Classifier: Intended Audience :: Developers
15
- Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.10
17
- Classifier: Programming Language :: Python :: 3.11
18
- Classifier: Programming Language :: Python :: 3.12
19
- Classifier: Programming Language :: Python :: 3.13
20
- Classifier: Programming Language :: Python :: 3.14
21
- Classifier: Typing :: Typed
22
- Requires-Python: <4.0,>=3.10
23
- Requires-Dist: anyio>=4.14.2
24
- Requires-Dist: devora-python<0.2.0,>=0.1.0
25
- Requires-Dist: fastapi<1.0.0,>=0.133.0
26
- Requires-Dist: idna>=3.15
27
- Requires-Dist: starlette>=1.3.1
28
- Description-Content-Type: text/markdown
29
-
30
- # devora-fastapi
31
-
32
- Recording, masking and activity preferences are configured in Devora Settings.
33
- SDK initialization overrides are ignored. New sessions retain the server's policy
34
- snapshot across exchange and resume. Developer privacy labels take effect only
35
- when selected in Settings; sensitive-field protection remains mandatory.
36
- See [migration details](https://github.com/getdevora/devora-sdks/blob/main/SETTINGS.md).
37
-
38
- FastAPI adapter for the Devora Python backend SDK.
39
-
40
- ## Requirements
41
-
42
- - Python `>=3.10,<4.0`
43
- - FastAPI `>=0.100.0,<1.0.0`
44
-
45
- ## Install
46
-
47
- ```bash
48
- pip install devora-python devora-fastapi
49
- ```
50
-
51
- ## Quick Start
52
-
53
- ```python
54
- from fastapi import FastAPI
55
- from devora_sdk import DEVORA_ENDPOINTS, devora_sdk
56
- from devora_sdk_fastapi import fastapi_router
57
-
58
- sdk = devora_sdk(
59
- api_key="pk_server_live_...",
60
- secret_key="sk_server_live_...",
61
- org_id="org_...",
62
- )
63
-
64
-
65
- @sdk.register(DEVORA_ENDPOINTS.USER_SEARCH)
66
- async def search_users(req):
67
- return {"users": await search_customer_users(req.query.get("term", ""))}
68
-
69
-
70
- app = FastAPI()
71
- app.include_router(fastapi_router(sdk), prefix="/devora")
72
- ```
73
-
74
- For protected application routes, install the guard middleware and extract a
75
- trusted impersonation context from authenticated request state.
76
-
77
- ```python
78
- from devora_sdk import ImpersonationContext
79
- from devora_sdk_fastapi import DevoraImpersonationGuardMiddleware
80
-
81
-
82
- def get_impersonation_context(request) -> ImpersonationContext | None:
83
- devora = getattr(request.state, "devora", None)
84
- if not devora:
85
- return None
86
- return ImpersonationContext(
87
- is_impersonation=devora["isImpersonation"],
88
- scope=devora["scope"],
89
- session_id=devora["sessionId"],
90
- expires_at=devora["expiresAt"],
91
- actor=devora["actor"],
92
- subject=devora["subject"],
93
- auth_method=devora["authMethod"],
94
- authorization_source=devora["authorizationSource"],
95
- recording_allowed=devora["recordingAllowed"],
96
- impersonator=devora.get("impersonator"),
97
- )
98
-
99
-
100
- app.add_middleware(
101
- DevoraImpersonationGuardMiddleware,
102
- sdk=sdk,
103
- get_impersonation_context=get_impersonation_context,
104
- )
105
- ```
106
-
107
- Register your own authentication middleware **after** this call — Starlette
108
- runs middleware in the reverse of its registration order, so it must be the
109
- outer layer that runs first for `request.state` to carry verified claims by
110
- the time the guard reads them.
111
-
112
- Resolve method overrides and route rewrites **before** the guard as well
113
- (register that middleware after this call too, so it runs first). The guard
114
- also judges every `X-HTTP-Method-Override`, `X-HTTP-Method` and
115
- `X-Method-Override` value and any `_method` query parameter, but it cannot see a
116
- `_method` field inside a request body, and it judges the path it receives.
117
-
118
- `expires_at` must be a Unix timestamp in milliseconds. If your JWT stores Unix
119
- seconds, multiply by `1000` when building the impersonation context. `actor`,
120
- `subject`, `auth_method`, `authorization_source`, and `recording_allowed` are
121
- all required — the guard rejects the context as invalid without them, even
122
- though the dataclass marks them optional for construction convenience.
@@ -1,93 +0,0 @@
1
- # devora-fastapi
2
-
3
- Recording, masking and activity preferences are configured in Devora Settings.
4
- SDK initialization overrides are ignored. New sessions retain the server's policy
5
- snapshot across exchange and resume. Developer privacy labels take effect only
6
- when selected in Settings; sensitive-field protection remains mandatory.
7
- See [migration details](https://github.com/getdevora/devora-sdks/blob/main/SETTINGS.md).
8
-
9
- FastAPI adapter for the Devora Python backend SDK.
10
-
11
- ## Requirements
12
-
13
- - Python `>=3.10,<4.0`
14
- - FastAPI `>=0.100.0,<1.0.0`
15
-
16
- ## Install
17
-
18
- ```bash
19
- pip install devora-python devora-fastapi
20
- ```
21
-
22
- ## Quick Start
23
-
24
- ```python
25
- from fastapi import FastAPI
26
- from devora_sdk import DEVORA_ENDPOINTS, devora_sdk
27
- from devora_sdk_fastapi import fastapi_router
28
-
29
- sdk = devora_sdk(
30
- api_key="pk_server_live_...",
31
- secret_key="sk_server_live_...",
32
- org_id="org_...",
33
- )
34
-
35
-
36
- @sdk.register(DEVORA_ENDPOINTS.USER_SEARCH)
37
- async def search_users(req):
38
- return {"users": await search_customer_users(req.query.get("term", ""))}
39
-
40
-
41
- app = FastAPI()
42
- app.include_router(fastapi_router(sdk), prefix="/devora")
43
- ```
44
-
45
- For protected application routes, install the guard middleware and extract a
46
- trusted impersonation context from authenticated request state.
47
-
48
- ```python
49
- from devora_sdk import ImpersonationContext
50
- from devora_sdk_fastapi import DevoraImpersonationGuardMiddleware
51
-
52
-
53
- def get_impersonation_context(request) -> ImpersonationContext | None:
54
- devora = getattr(request.state, "devora", None)
55
- if not devora:
56
- return None
57
- return ImpersonationContext(
58
- is_impersonation=devora["isImpersonation"],
59
- scope=devora["scope"],
60
- session_id=devora["sessionId"],
61
- expires_at=devora["expiresAt"],
62
- actor=devora["actor"],
63
- subject=devora["subject"],
64
- auth_method=devora["authMethod"],
65
- authorization_source=devora["authorizationSource"],
66
- recording_allowed=devora["recordingAllowed"],
67
- impersonator=devora.get("impersonator"),
68
- )
69
-
70
-
71
- app.add_middleware(
72
- DevoraImpersonationGuardMiddleware,
73
- sdk=sdk,
74
- get_impersonation_context=get_impersonation_context,
75
- )
76
- ```
77
-
78
- Register your own authentication middleware **after** this call — Starlette
79
- runs middleware in the reverse of its registration order, so it must be the
80
- outer layer that runs first for `request.state` to carry verified claims by
81
- the time the guard reads them.
82
-
83
- Resolve method overrides and route rewrites **before** the guard as well
84
- (register that middleware after this call too, so it runs first). The guard
85
- also judges every `X-HTTP-Method-Override`, `X-HTTP-Method` and
86
- `X-Method-Override` value and any `_method` query parameter, but it cannot see a
87
- `_method` field inside a request body, and it judges the path it receives.
88
-
89
- `expires_at` must be a Unix timestamp in milliseconds. If your JWT stores Unix
90
- seconds, multiply by `1000` when building the impersonation context. `actor`,
91
- `subject`, `auth_method`, `authorization_source`, and `recording_allowed` are
92
- all required — the guard rejects the context as invalid without them, even
93
- though the dataclass marks them optional for construction convenience.
File without changes