pyferox 0.0.1__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.
Files changed (59) hide show
  1. pyferox-0.0.1/LICENSE +21 -0
  2. pyferox-0.0.1/PKG-INFO +126 -0
  3. pyferox-0.0.1/README.md +93 -0
  4. pyferox-0.0.1/pyferox/__init__.py +363 -0
  5. pyferox-0.0.1/pyferox/auth/__init__.py +26 -0
  6. pyferox-0.0.1/pyferox/auth/contracts.py +79 -0
  7. pyferox-0.0.1/pyferox/auth/session.py +60 -0
  8. pyferox-0.0.1/pyferox/cache/__init__.py +6 -0
  9. pyferox-0.0.1/pyferox/cache/runtime.py +91 -0
  10. pyferox-0.0.1/pyferox/cli/__init__.py +6 -0
  11. pyferox-0.0.1/pyferox/cli/main.py +434 -0
  12. pyferox-0.0.1/pyferox/config/__init__.py +33 -0
  13. pyferox-0.0.1/pyferox/config/settings.py +192 -0
  14. pyferox-0.0.1/pyferox/core/__init__.py +112 -0
  15. pyferox-0.0.1/pyferox/core/app.py +198 -0
  16. pyferox-0.0.1/pyferox/core/context.py +28 -0
  17. pyferox-0.0.1/pyferox/core/di.py +156 -0
  18. pyferox-0.0.1/pyferox/core/dispatcher.py +159 -0
  19. pyferox-0.0.1/pyferox/core/errors.py +98 -0
  20. pyferox-0.0.1/pyferox/core/handlers.py +66 -0
  21. pyferox-0.0.1/pyferox/core/messages.py +73 -0
  22. pyferox-0.0.1/pyferox/core/middleware.py +32 -0
  23. pyferox-0.0.1/pyferox/core/module.py +142 -0
  24. pyferox-0.0.1/pyferox/core/pagination.py +61 -0
  25. pyferox-0.0.1/pyferox/core/results.py +51 -0
  26. pyferox-0.0.1/pyferox/db/__init__.py +28 -0
  27. pyferox-0.0.1/pyferox/db/migrations.py +66 -0
  28. pyferox-0.0.1/pyferox/db/repository.py +16 -0
  29. pyferox-0.0.1/pyferox/db/sqlalchemy.py +96 -0
  30. pyferox-0.0.1/pyferox/events/__init__.py +23 -0
  31. pyferox-0.0.1/pyferox/events/runtime.py +190 -0
  32. pyferox-0.0.1/pyferox/http/__init__.py +6 -0
  33. pyferox-0.0.1/pyferox/http/adapter.py +637 -0
  34. pyferox-0.0.1/pyferox/jobs/__init__.py +27 -0
  35. pyferox-0.0.1/pyferox/jobs/runtime.py +296 -0
  36. pyferox-0.0.1/pyferox/logging/__init__.py +5 -0
  37. pyferox-0.0.1/pyferox/logging/hooks.py +76 -0
  38. pyferox-0.0.1/pyferox/ops/__init__.py +25 -0
  39. pyferox-0.0.1/pyferox/ops/runtime.py +204 -0
  40. pyferox-0.0.1/pyferox/reliability/__init__.py +21 -0
  41. pyferox-0.0.1/pyferox/reliability/runtime.py +142 -0
  42. pyferox-0.0.1/pyferox/rpc/__init__.py +21 -0
  43. pyferox-0.0.1/pyferox/rpc/runtime.py +207 -0
  44. pyferox-0.0.1/pyferox/scheduler/__init__.py +15 -0
  45. pyferox-0.0.1/pyferox/scheduler/runtime.py +155 -0
  46. pyferox-0.0.1/pyferox/schema/__init__.py +25 -0
  47. pyferox-0.0.1/pyferox/schema/runtime.py +266 -0
  48. pyferox-0.0.1/pyferox/testing/__init__.py +5 -0
  49. pyferox-0.0.1/pyferox/testing/utils.py +122 -0
  50. pyferox-0.0.1/pyferox/workflow/__init__.py +17 -0
  51. pyferox-0.0.1/pyferox/workflow/runtime.py +127 -0
  52. pyferox-0.0.1/pyferox.egg-info/PKG-INFO +126 -0
  53. pyferox-0.0.1/pyferox.egg-info/SOURCES.txt +57 -0
  54. pyferox-0.0.1/pyferox.egg-info/dependency_links.txt +1 -0
  55. pyferox-0.0.1/pyferox.egg-info/entry_points.txt +2 -0
  56. pyferox-0.0.1/pyferox.egg-info/requires.txt +13 -0
  57. pyferox-0.0.1/pyferox.egg-info/top_level.txt +1 -0
  58. pyferox-0.0.1/pyproject.toml +60 -0
  59. pyferox-0.0.1/setup.cfg +4 -0
pyferox-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PyFerOx
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
pyferox-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,126 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyferox
3
+ Version: 0.0.1
4
+ Summary: Transport-agnostic, async-first modular service application framework.
5
+ Author: PyFerOx Maintainers
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/pablo0723/pyferox
8
+ Project-URL: Repository, https://github.com/pablo0723/pyferox
9
+ Project-URL: Issues, https://github.com/pablo0723/pyferox/issues
10
+ Keywords: framework,service,cqrs,event-driven,async,backend
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Framework :: AsyncIO
17
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
18
+ Requires-Python: >=3.12
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: anyio>=4.4.0
22
+ Requires-Dist: msgspec>=0.20.0
23
+ Requires-Dist: sqlalchemy>=2.0.30
24
+ Provides-Extra: dev
25
+ Requires-Dist: build>=1.2.1; extra == "dev"
26
+ Requires-Dist: twine>=5.1.1; extra == "dev"
27
+ Requires-Dist: pytest>=8.3.0; extra == "dev"
28
+ Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
29
+ Requires-Dist: aiosqlite>=0.20.0; extra == "dev"
30
+ Provides-Extra: http
31
+ Requires-Dist: uvicorn>=0.30.0; extra == "http"
32
+ Dynamic: license-file
33
+
34
+ # PyFerOx
35
+
36
+ Transport-agnostic, async-first Python service framework built around:
37
+
38
+ - typed command/query/event contracts
39
+ - modular composition
40
+ - execution middleware + lifecycle hooks
41
+ - pluggable transports (HTTP, jobs/worker, RPC, scheduler)
42
+
43
+ ## Project Status
44
+
45
+ Phase 1-3 implementation is complete according to repository audits:
46
+
47
+ - [Phase 1 audit](phase1_audit.md)
48
+ - [Phase 2 audit](phase2_audit.md)
49
+ - [Phase 3 audit](phase3_audit.md)
50
+
51
+ ## Documentation
52
+
53
+ Full user docs are in [`docs/`](docs/README.md).
54
+
55
+ Recommended start:
56
+
57
+ 1. [Quickstart](docs/quickstart.md)
58
+ 2. [Core Concepts](docs/core-concepts.md)
59
+ 3. [HTTP, Auth, OpenAPI](docs/http-auth-openapi.md)
60
+ 4. [Persistence (SQLAlchemy)](docs/persistence-sqlalchemy.md)
61
+ 5. [Background and Distributed Runtime](docs/background-and-distributed-runtime.md)
62
+ 6. [Testing](docs/testing.md)
63
+ 7. [Configuration](docs/configuration.md)
64
+ 8. [CLI Reference](docs/cli.md)
65
+ 9. [Operations and Tuning](docs/operations-and-tuning.md)
66
+
67
+ ## Install
68
+
69
+ ```bash
70
+ pip install -e ".[dev,http]"
71
+ ```
72
+
73
+ ## Minimal Example
74
+
75
+ ```python
76
+ from pyferox import App, HTTPAdapter, Module, StructCommand, StructQuery, handle, singleton
77
+
78
+
79
+ class UserRepo:
80
+ async def create(self, email: str, name: str) -> int:
81
+ return 1
82
+
83
+ async def get(self, user_id: int) -> dict[str, str]:
84
+ return {"id": str(user_id), "email": "user@example.com", "name": "User"}
85
+
86
+
87
+ class CreateUser(StructCommand):
88
+ email: str
89
+ name: str
90
+
91
+
92
+ class GetUser(StructQuery):
93
+ user_id: int
94
+
95
+
96
+ @handle(CreateUser)
97
+ async def create_user(cmd: CreateUser, users: UserRepo) -> dict[str, int]:
98
+ return {"id": await users.create(cmd.email, cmd.name)}
99
+
100
+
101
+ @handle(GetUser)
102
+ async def get_user(query: GetUser, users: UserRepo) -> dict[str, str]:
103
+ return await users.get(query.user_id)
104
+
105
+
106
+ app = App(modules=[Module(handlers=[create_user, get_user], providers=[singleton(UserRepo())])])
107
+ http = HTTPAdapter(app)
108
+ http.command("POST", "/users", CreateUser, status_code=201)
109
+ http.query("GET", "/users/{user_id}", GetUser)
110
+ ```
111
+
112
+ PyFerOx is msgspec-backed. For new transport-facing contracts, prefer `StructCommand` / `StructQuery` / `StructEvent`. Dataclass contracts are still supported, but they are not the primary style to teach in user-facing examples.
113
+
114
+ Run:
115
+
116
+ ```bash
117
+ uvicorn app.main:http --reload
118
+ ```
119
+
120
+ Or scaffold first:
121
+
122
+ ```bash
123
+ pyferox create-project demo_service --template api
124
+ cd demo_service
125
+ pyferox run-dev --target app.main:http
126
+ ```
@@ -0,0 +1,93 @@
1
+ # PyFerOx
2
+
3
+ Transport-agnostic, async-first Python service framework built around:
4
+
5
+ - typed command/query/event contracts
6
+ - modular composition
7
+ - execution middleware + lifecycle hooks
8
+ - pluggable transports (HTTP, jobs/worker, RPC, scheduler)
9
+
10
+ ## Project Status
11
+
12
+ Phase 1-3 implementation is complete according to repository audits:
13
+
14
+ - [Phase 1 audit](phase1_audit.md)
15
+ - [Phase 2 audit](phase2_audit.md)
16
+ - [Phase 3 audit](phase3_audit.md)
17
+
18
+ ## Documentation
19
+
20
+ Full user docs are in [`docs/`](docs/README.md).
21
+
22
+ Recommended start:
23
+
24
+ 1. [Quickstart](docs/quickstart.md)
25
+ 2. [Core Concepts](docs/core-concepts.md)
26
+ 3. [HTTP, Auth, OpenAPI](docs/http-auth-openapi.md)
27
+ 4. [Persistence (SQLAlchemy)](docs/persistence-sqlalchemy.md)
28
+ 5. [Background and Distributed Runtime](docs/background-and-distributed-runtime.md)
29
+ 6. [Testing](docs/testing.md)
30
+ 7. [Configuration](docs/configuration.md)
31
+ 8. [CLI Reference](docs/cli.md)
32
+ 9. [Operations and Tuning](docs/operations-and-tuning.md)
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install -e ".[dev,http]"
38
+ ```
39
+
40
+ ## Minimal Example
41
+
42
+ ```python
43
+ from pyferox import App, HTTPAdapter, Module, StructCommand, StructQuery, handle, singleton
44
+
45
+
46
+ class UserRepo:
47
+ async def create(self, email: str, name: str) -> int:
48
+ return 1
49
+
50
+ async def get(self, user_id: int) -> dict[str, str]:
51
+ return {"id": str(user_id), "email": "user@example.com", "name": "User"}
52
+
53
+
54
+ class CreateUser(StructCommand):
55
+ email: str
56
+ name: str
57
+
58
+
59
+ class GetUser(StructQuery):
60
+ user_id: int
61
+
62
+
63
+ @handle(CreateUser)
64
+ async def create_user(cmd: CreateUser, users: UserRepo) -> dict[str, int]:
65
+ return {"id": await users.create(cmd.email, cmd.name)}
66
+
67
+
68
+ @handle(GetUser)
69
+ async def get_user(query: GetUser, users: UserRepo) -> dict[str, str]:
70
+ return await users.get(query.user_id)
71
+
72
+
73
+ app = App(modules=[Module(handlers=[create_user, get_user], providers=[singleton(UserRepo())])])
74
+ http = HTTPAdapter(app)
75
+ http.command("POST", "/users", CreateUser, status_code=201)
76
+ http.query("GET", "/users/{user_id}", GetUser)
77
+ ```
78
+
79
+ PyFerOx is msgspec-backed. For new transport-facing contracts, prefer `StructCommand` / `StructQuery` / `StructEvent`. Dataclass contracts are still supported, but they are not the primary style to teach in user-facing examples.
80
+
81
+ Run:
82
+
83
+ ```bash
84
+ uvicorn app.main:http --reload
85
+ ```
86
+
87
+ Or scaffold first:
88
+
89
+ ```bash
90
+ pyferox create-project demo_service --template api
91
+ cd demo_service
92
+ pyferox run-dev --target app.main:http
93
+ ```
@@ -0,0 +1,363 @@
1
+ """PyFerOx service framework public API."""
2
+
3
+ # Package namespaces (discoverable grouped imports)
4
+ from pyferox import (
5
+ auth,
6
+ cache,
7
+ config,
8
+ core,
9
+ db,
10
+ events,
11
+ http,
12
+ jobs,
13
+ ops,
14
+ reliability,
15
+ rpc,
16
+ schema,
17
+ scheduler,
18
+ testing,
19
+ workflow,
20
+ )
21
+
22
+ # Core runtime and composition
23
+ from pyferox.core import (
24
+ App,
25
+ AppState,
26
+ ExecutionContext,
27
+ LifecycleManager,
28
+ Module,
29
+ ModuleDefinition,
30
+ ProviderRegistry,
31
+ TransportRegistry,
32
+ module_diagnostics,
33
+ )
34
+
35
+ # Messaging and handler contracts
36
+ from pyferox.core import (
37
+ Command,
38
+ Event,
39
+ ListQuery,
40
+ Message,
41
+ PageParams,
42
+ Query,
43
+ StructCommand,
44
+ StructEvent,
45
+ StructQuery,
46
+ SortDirection,
47
+ SortField,
48
+ build_page_params,
49
+ contract,
50
+ get_contract_metadata,
51
+ parse_sort,
52
+ )
53
+
54
+ # Dispatch, middleware, dependency injection
55
+ from pyferox.core import (
56
+ Dispatcher,
57
+ ExceptionHook,
58
+ Middleware,
59
+ ResolutionError,
60
+ Scope,
61
+ SyncPolicy,
62
+ handle,
63
+ listen,
64
+ provider,
65
+ singleton,
66
+ )
67
+
68
+ # Results, errors, transport mapping
69
+ from pyferox.core import (
70
+ AuthError,
71
+ ConflictError,
72
+ DomainError,
73
+ Empty,
74
+ ForbiddenError,
75
+ FrameworkError,
76
+ HandlerNotFoundError,
77
+ InfrastructureError,
78
+ NotFoundError,
79
+ Paginated,
80
+ PermissionDeniedError,
81
+ PyFerOxError,
82
+ Response,
83
+ RouteConflictError,
84
+ Streamed,
85
+ Success,
86
+ UnauthorizedError,
87
+ ValidationError,
88
+ map_exception_to_transport,
89
+ normalize_execution_exception,
90
+ to_payload,
91
+ )
92
+
93
+ # HTTP transport
94
+ from pyferox.http import HTTPAdapter
95
+
96
+ # Schema layer
97
+ from pyferox.schema import (
98
+ DTO,
99
+ Schema,
100
+ SchemaModel,
101
+ TypedSchema,
102
+ format_validation_error,
103
+ get_schema_metadata,
104
+ parse_input,
105
+ schema_metadata,
106
+ serialize_output,
107
+ )
108
+
109
+ # Auth contracts and implementations
110
+ from pyferox.auth import (
111
+ AccessToken,
112
+ AuthBackend,
113
+ Identity,
114
+ InMemorySessionStore,
115
+ PermissionChecker,
116
+ Principal,
117
+ Session,
118
+ SessionAuthBackend,
119
+ SessionStore,
120
+ requires,
121
+ )
122
+
123
+ # Jobs runtime
124
+ from pyferox.jobs import (
125
+ InMemoryJobQueue,
126
+ Job,
127
+ JobDispatcher,
128
+ JobExecutionResult,
129
+ JobQueue,
130
+ JobStatus,
131
+ LocalJobWorker,
132
+ StructJob,
133
+ WorkerRuntime,
134
+ create_worker_runtime,
135
+ )
136
+
137
+ # Cache abstractions
138
+ from pyferox.cache import Cache, InMemoryCache
139
+
140
+ # Phase 3 distributed runtime
141
+ from pyferox.events import (
142
+ DistributedEventBus,
143
+ EventBroker,
144
+ EventDispatchResult,
145
+ EventEnvelope,
146
+ EventResultStatus,
147
+ InMemoryEventBroker,
148
+ LocalEventBus,
149
+ event_metadata,
150
+ )
151
+ from pyferox.ops import (
152
+ CheckStatus,
153
+ HealthCheckResult,
154
+ HealthRegistry,
155
+ HealthReport,
156
+ InMemoryTraceCollector,
157
+ TraceCollector,
158
+ TraceSpan,
159
+ TracingMiddleware,
160
+ )
161
+ from pyferox.reliability import (
162
+ IdempotencyStore,
163
+ InMemoryIdempotencyStore,
164
+ RetryClassifier,
165
+ RetryDecision,
166
+ RetryDisposition,
167
+ RetryPolicy,
168
+ default_retry_classifier,
169
+ )
170
+ from pyferox.rpc import InMemoryRPCTransport, RPCClient, RPCError, RPCRequest, RPCResponse, RPCServer, RPCTransport
171
+ from pyferox.scheduler import ScheduleRunResult, ScheduleResultStatus, ScheduledTask, SchedulerRuntime
172
+ from pyferox.workflow import Workflow, WorkflowContext, WorkflowExecutionResult, WorkflowStatus, WorkflowStep
173
+
174
+ # DB persistence and migrations
175
+ from pyferox.db import (
176
+ DBSessionMiddleware,
177
+ MigrationResult,
178
+ Repository,
179
+ SQLAlchemySettings,
180
+ UnitOfWork,
181
+ migration_current,
182
+ migration_downgrade,
183
+ migration_init,
184
+ migration_revision,
185
+ migration_upgrade,
186
+ run_alembic,
187
+ sqlalchemy_module,
188
+ )
189
+
190
+ # Testing helpers
191
+ from pyferox.testing import FakeDispatcher, TestHTTPClient, create_test_app, create_test_module, override_dependencies
192
+
193
+ __all__ = [
194
+ # Namespaces
195
+ "auth",
196
+ "cache",
197
+ "config",
198
+ "core",
199
+ "db",
200
+ "events",
201
+ "http",
202
+ "jobs",
203
+ "ops",
204
+ "reliability",
205
+ "rpc",
206
+ "schema",
207
+ "scheduler",
208
+ "testing",
209
+ "workflow",
210
+ # Core runtime
211
+ "App",
212
+ "AppState",
213
+ "ExecutionContext",
214
+ "LifecycleManager",
215
+ "Module",
216
+ "ModuleDefinition",
217
+ "ProviderRegistry",
218
+ "TransportRegistry",
219
+ "module_diagnostics",
220
+ # Contracts
221
+ "Command",
222
+ "Event",
223
+ "ListQuery",
224
+ "Message",
225
+ "PageParams",
226
+ "Query",
227
+ "StructCommand",
228
+ "StructEvent",
229
+ "StructQuery",
230
+ "SortDirection",
231
+ "SortField",
232
+ "build_page_params",
233
+ "contract",
234
+ "get_contract_metadata",
235
+ "parse_sort",
236
+ # Dispatch and DI
237
+ "Dispatcher",
238
+ "ExceptionHook",
239
+ "Middleware",
240
+ "ResolutionError",
241
+ "Scope",
242
+ "SyncPolicy",
243
+ "handle",
244
+ "listen",
245
+ "provider",
246
+ "singleton",
247
+ # Results and errors
248
+ "AuthError",
249
+ "ConflictError",
250
+ "DomainError",
251
+ "Empty",
252
+ "ForbiddenError",
253
+ "FrameworkError",
254
+ "HandlerNotFoundError",
255
+ "InfrastructureError",
256
+ "NotFoundError",
257
+ "Paginated",
258
+ "PermissionDeniedError",
259
+ "PyFerOxError",
260
+ "Response",
261
+ "RouteConflictError",
262
+ "Streamed",
263
+ "Success",
264
+ "UnauthorizedError",
265
+ "ValidationError",
266
+ "map_exception_to_transport",
267
+ "normalize_execution_exception",
268
+ "to_payload",
269
+ # HTTP and schema
270
+ "HTTPAdapter",
271
+ "DTO",
272
+ "Schema",
273
+ "SchemaModel",
274
+ "TypedSchema",
275
+ "format_validation_error",
276
+ "get_schema_metadata",
277
+ "parse_input",
278
+ "schema_metadata",
279
+ "serialize_output",
280
+ # Auth
281
+ "AccessToken",
282
+ "AuthBackend",
283
+ "Identity",
284
+ "InMemorySessionStore",
285
+ "PermissionChecker",
286
+ "Principal",
287
+ "Session",
288
+ "SessionAuthBackend",
289
+ "SessionStore",
290
+ "requires",
291
+ # Jobs and cache
292
+ "InMemoryJobQueue",
293
+ "Job",
294
+ "JobDispatcher",
295
+ "JobExecutionResult",
296
+ "JobQueue",
297
+ "JobStatus",
298
+ "LocalJobWorker",
299
+ "StructJob",
300
+ "WorkerRuntime",
301
+ "create_worker_runtime",
302
+ "Cache",
303
+ "InMemoryCache",
304
+ # Distributed runtime
305
+ "DistributedEventBus",
306
+ "EventBroker",
307
+ "EventDispatchResult",
308
+ "EventEnvelope",
309
+ "EventResultStatus",
310
+ "InMemoryEventBroker",
311
+ "LocalEventBus",
312
+ "event_metadata",
313
+ "CheckStatus",
314
+ "HealthCheckResult",
315
+ "HealthRegistry",
316
+ "HealthReport",
317
+ "InMemoryTraceCollector",
318
+ "TraceCollector",
319
+ "TraceSpan",
320
+ "TracingMiddleware",
321
+ "IdempotencyStore",
322
+ "InMemoryIdempotencyStore",
323
+ "RetryClassifier",
324
+ "RetryDecision",
325
+ "RetryDisposition",
326
+ "RetryPolicy",
327
+ "default_retry_classifier",
328
+ "InMemoryRPCTransport",
329
+ "RPCClient",
330
+ "RPCError",
331
+ "RPCRequest",
332
+ "RPCResponse",
333
+ "RPCServer",
334
+ "RPCTransport",
335
+ "ScheduleRunResult",
336
+ "ScheduleResultStatus",
337
+ "ScheduledTask",
338
+ "SchedulerRuntime",
339
+ "Workflow",
340
+ "WorkflowContext",
341
+ "WorkflowExecutionResult",
342
+ "WorkflowStatus",
343
+ "WorkflowStep",
344
+ # DB
345
+ "DBSessionMiddleware",
346
+ "MigrationResult",
347
+ "Repository",
348
+ "SQLAlchemySettings",
349
+ "UnitOfWork",
350
+ "migration_current",
351
+ "migration_downgrade",
352
+ "migration_init",
353
+ "migration_revision",
354
+ "migration_upgrade",
355
+ "run_alembic",
356
+ "sqlalchemy_module",
357
+ # Testing
358
+ "FakeDispatcher",
359
+ "TestHTTPClient",
360
+ "create_test_app",
361
+ "create_test_module",
362
+ "override_dependencies",
363
+ ]
@@ -0,0 +1,26 @@
1
+ """Authentication and authorization contracts."""
2
+
3
+ from pyferox.auth.contracts import (
4
+ AccessToken,
5
+ AuthBackend,
6
+ Identity,
7
+ PermissionChecker,
8
+ Principal,
9
+ Session,
10
+ SessionStore,
11
+ requires,
12
+ )
13
+ from pyferox.auth.session import InMemorySessionStore, SessionAuthBackend
14
+
15
+ __all__ = [
16
+ "AccessToken",
17
+ "AuthBackend",
18
+ "Identity",
19
+ "InMemorySessionStore",
20
+ "PermissionChecker",
21
+ "Principal",
22
+ "Session",
23
+ "SessionAuthBackend",
24
+ "SessionStore",
25
+ "requires",
26
+ ]
@@ -0,0 +1,79 @@
1
+ """Core auth interfaces."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from datetime import datetime, timezone
7
+ from typing import Any, Protocol
8
+
9
+
10
+ @dataclass(slots=True)
11
+ class Identity:
12
+ subject: str
13
+ email: str | None = None
14
+
15
+
16
+ @dataclass(slots=True)
17
+ class Principal:
18
+ identity: Identity
19
+ roles: set[str] = field(default_factory=set)
20
+ permissions: set[str] = field(default_factory=set)
21
+
22
+
23
+ @dataclass(slots=True)
24
+ class AccessToken:
25
+ token: str
26
+ subject: str
27
+ expires_at: datetime | None = None
28
+ scopes: set[str] = field(default_factory=set)
29
+
30
+ def is_expired(self, *, now: datetime | None = None) -> bool:
31
+ if self.expires_at is None:
32
+ return False
33
+ current = now or datetime.now(tz=timezone.utc)
34
+ return self.expires_at <= current
35
+
36
+
37
+ @dataclass(slots=True)
38
+ class Session:
39
+ session_id: str
40
+ subject: str
41
+ expires_at: datetime | None = None
42
+ data: dict[str, Any] = field(default_factory=dict)
43
+
44
+ def is_expired(self, *, now: datetime | None = None) -> bool:
45
+ if self.expires_at is None:
46
+ return False
47
+ current = now or datetime.now(tz=timezone.utc)
48
+ return self.expires_at <= current
49
+
50
+
51
+ class AuthBackend(Protocol):
52
+ async def authenticate(self, token: str | None) -> Principal | None:
53
+ ...
54
+
55
+
56
+ class PermissionChecker(Protocol):
57
+ async def allowed(self, principal: Principal | None, permission: str) -> bool:
58
+ ...
59
+
60
+
61
+ class SessionStore(Protocol):
62
+ async def get(self, session_id: str) -> Session | None:
63
+ ...
64
+
65
+ async def set(self, session: Session) -> None:
66
+ ...
67
+
68
+ async def revoke(self, session_id: str) -> None:
69
+ ...
70
+
71
+
72
+ def requires(permission: str):
73
+ """Declare handler-level permission requirement."""
74
+
75
+ def decorator(fn: Any) -> Any:
76
+ setattr(fn, "__pyferox_permission__", permission)
77
+ return fn
78
+
79
+ return decorator