causeway 0.1.0__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.
- causeway-0.1.0/.gitignore +93 -0
- causeway-0.1.0/CHANGELOG.md +29 -0
- causeway-0.1.0/PKG-INFO +146 -0
- causeway-0.1.0/README.md +94 -0
- causeway-0.1.0/pyproject.toml +159 -0
- causeway-0.1.0/src/causeway/__init__.py +68 -0
- causeway-0.1.0/src/causeway/_cron.py +65 -0
- causeway-0.1.0/src/causeway/_methods.py +42 -0
- causeway-0.1.0/src/causeway/_paths.py +116 -0
- causeway-0.1.0/src/causeway/_scaffold.py +261 -0
- causeway-0.1.0/src/causeway/adapters.py +305 -0
- causeway-0.1.0/src/causeway/app.py +98 -0
- causeway-0.1.0/src/causeway/cli.py +227 -0
- causeway-0.1.0/src/causeway/config.py +107 -0
- causeway-0.1.0/src/causeway/contracts.py +218 -0
- causeway-0.1.0/src/causeway/diagnostics.py +93 -0
- causeway-0.1.0/src/causeway/errors.py +135 -0
- causeway-0.1.0/src/causeway/health.py +54 -0
- causeway-0.1.0/src/causeway/middleware.py +53 -0
- causeway-0.1.0/src/causeway/observability.py +142 -0
- causeway-0.1.0/src/causeway/plugins.py +235 -0
- causeway-0.1.0/src/causeway/py.typed +0 -0
- causeway-0.1.0/src/causeway/routing.py +360 -0
- causeway-0.1.0/src/causeway/scope.py +41 -0
- causeway-0.1.0/src/causeway/tasks.py +328 -0
- causeway-0.1.0/src/causeway/testing.py +124 -0
- causeway-0.1.0/tests/__init__.py +0 -0
- causeway-0.1.0/tests/test_adapters.py +189 -0
- causeway-0.1.0/tests/test_app_factory.py +87 -0
- causeway-0.1.0/tests/test_cli.py +49 -0
- causeway-0.1.0/tests/test_config.py +58 -0
- causeway-0.1.0/tests/test_errors.py +68 -0
- causeway-0.1.0/tests/test_health.py +76 -0
- causeway-0.1.0/tests/test_observability.py +63 -0
- causeway-0.1.0/tests/test_paths.py +73 -0
- causeway-0.1.0/tests/test_plugin_features.py +109 -0
- causeway-0.1.0/tests/test_plugins.py +152 -0
- causeway-0.1.0/tests/test_provider_binding.py +89 -0
- causeway-0.1.0/tests/test_routing_discover.py +154 -0
- causeway-0.1.0/tests/test_routing_register.py +104 -0
- causeway-0.1.0/tests/test_tasks.py +158 -0
- causeway-0.1.0/tests/test_testing_kit.py +63 -0
- causeway-0.1.0/tests/test_version.py +8 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# ---- OS ----
|
|
2
|
+
.DS_Store
|
|
3
|
+
Thumbs.db
|
|
4
|
+
desktop.ini
|
|
5
|
+
|
|
6
|
+
# ---- Editors ----
|
|
7
|
+
.idea/
|
|
8
|
+
*.swp
|
|
9
|
+
*.swo
|
|
10
|
+
*~
|
|
11
|
+
.vscode/*
|
|
12
|
+
!.vscode/settings.json
|
|
13
|
+
!.vscode/extensions.json
|
|
14
|
+
!.vscode/launch.json
|
|
15
|
+
|
|
16
|
+
# ---- Node ----
|
|
17
|
+
node_modules/
|
|
18
|
+
.pnp.*
|
|
19
|
+
.yarn/*
|
|
20
|
+
!.yarn/patches
|
|
21
|
+
!.yarn/plugins
|
|
22
|
+
!.yarn/releases
|
|
23
|
+
!.yarn/sdks
|
|
24
|
+
!.yarn/versions
|
|
25
|
+
.npm/
|
|
26
|
+
.eslintcache
|
|
27
|
+
*.tsbuildinfo
|
|
28
|
+
|
|
29
|
+
# ---- Build output ----
|
|
30
|
+
dist/
|
|
31
|
+
build/
|
|
32
|
+
out/
|
|
33
|
+
.turbo/
|
|
34
|
+
.cache/
|
|
35
|
+
coverage/
|
|
36
|
+
*.log
|
|
37
|
+
npm-debug.log*
|
|
38
|
+
yarn-debug.log*
|
|
39
|
+
yarn-error.log*
|
|
40
|
+
pnpm-debug.log*
|
|
41
|
+
|
|
42
|
+
# ---- Python ----
|
|
43
|
+
__pycache__/
|
|
44
|
+
*.py[cod]
|
|
45
|
+
*$py.class
|
|
46
|
+
*.so
|
|
47
|
+
.Python
|
|
48
|
+
.python-version-local
|
|
49
|
+
.venv/
|
|
50
|
+
venv/
|
|
51
|
+
env/
|
|
52
|
+
ENV/
|
|
53
|
+
.uv-cache/
|
|
54
|
+
.eggs/
|
|
55
|
+
*.egg-info/
|
|
56
|
+
*.egg
|
|
57
|
+
.pytest_cache/
|
|
58
|
+
.mypy_cache/
|
|
59
|
+
.ruff_cache/
|
|
60
|
+
.coverage
|
|
61
|
+
.coverage.*
|
|
62
|
+
htmlcov/
|
|
63
|
+
.tox/
|
|
64
|
+
.nox/
|
|
65
|
+
.hypothesis/
|
|
66
|
+
*.cover
|
|
67
|
+
*.py,cover
|
|
68
|
+
|
|
69
|
+
# ---- Claude Code harness ----
|
|
70
|
+
.claude/
|
|
71
|
+
!.claude/settings.json
|
|
72
|
+
!.claude/commands/
|
|
73
|
+
|
|
74
|
+
# ---- Causeway ----
|
|
75
|
+
.causeway/
|
|
76
|
+
|
|
77
|
+
# ---- Benchmarks ----
|
|
78
|
+
benchmarks/results/
|
|
79
|
+
benchmarks/uv.lock
|
|
80
|
+
|
|
81
|
+
# ---- Env / secrets ----
|
|
82
|
+
.env
|
|
83
|
+
.env.*
|
|
84
|
+
!.env.example
|
|
85
|
+
*.pem
|
|
86
|
+
*.key
|
|
87
|
+
*.crt
|
|
88
|
+
|
|
89
|
+
# ---- Misc ----
|
|
90
|
+
*.tgz
|
|
91
|
+
*.zip
|
|
92
|
+
.DS_Store?
|
|
93
|
+
._*
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Changelog · `causeway`
|
|
2
|
+
|
|
3
|
+
All notable changes to the `causeway` Python package will be documented in this
|
|
4
|
+
file. Managed automatically by [release-please](https://github.com/googleapis/release-please)
|
|
5
|
+
from [Conventional Commits](https://www.conventionalcommits.org/).
|
|
6
|
+
|
|
7
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
|
8
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
9
|
+
|
|
10
|
+
## 0.1.0 (2026-05-16)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### ⚠ BREAKING CHANGES
|
|
14
|
+
|
|
15
|
+
* drops the 0.1.0a0 pre-release line. The first published versions on PyPI will be 0.1.0.
|
|
16
|
+
* the framework is now distributed as `causeway` (and `causeway-<role>-<impl>` for plugins). Every public surface moves:
|
|
17
|
+
|
|
18
|
+
### Chores
|
|
19
|
+
|
|
20
|
+
* prep first 0.1.0 release across all 13 packages ([11ef338](https://github.com/tamimbinhakim/causeway/commit/11ef3382a6263cbe541455dd169a28cf162ebb3d))
|
|
21
|
+
* rename quay → causeway across the repo ([83f1213](https://github.com/tamimbinhakim/causeway/commit/83f1213a2176e8338305f8b30f43379d6a614238))
|
|
22
|
+
|
|
23
|
+
## [Unreleased]
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- Initial package scaffold: file-based router, typed config / DI,
|
|
28
|
+
middleware + scope composition, background-task contract + reference
|
|
29
|
+
adapter, plugin registry, `causeway` CLI.
|
causeway-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: causeway
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A lean backend framework for type-safe Python APIs — file-based routing, typed config/DI, scoped providers, plugin registry, and background tasks.
|
|
5
|
+
Project-URL: Homepage, https://github.com/tamimbinhakim/causeway
|
|
6
|
+
Project-URL: Documentation, https://github.com/tamimbinhakim/causeway/tree/main/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/tamimbinhakim/causeway
|
|
8
|
+
Project-URL: Issues, https://github.com/tamimbinhakim/causeway/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/tamimbinhakim/causeway/blob/main/packages/causeway/CHANGELOG.md
|
|
10
|
+
Author-email: Tamim Bin Hakim <tamimbinhakim.work@gmail.com>
|
|
11
|
+
License: MIT
|
|
12
|
+
Keywords: asgi,backend-framework,background-tasks,dependency-injection,dyadpy,file-based-routing
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Framework :: AsyncIO
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.11
|
|
27
|
+
Requires-Dist: anyio>=4.13.0
|
|
28
|
+
Requires-Dist: dyadpy>=0.1.0a0
|
|
29
|
+
Requires-Dist: msgspec>=0.19
|
|
30
|
+
Requires-Dist: pydantic-settings>=2.14.1
|
|
31
|
+
Requires-Dist: pydantic>=2.13.4
|
|
32
|
+
Requires-Dist: rich>=15.0.0
|
|
33
|
+
Requires-Dist: starlette>=1.0.0
|
|
34
|
+
Requires-Dist: structlog>=25.5.0
|
|
35
|
+
Requires-Dist: typer>=0.25.1
|
|
36
|
+
Requires-Dist: uvicorn[standard]>=0.47.0
|
|
37
|
+
Requires-Dist: watchfiles>=1.1.1
|
|
38
|
+
Provides-Extra: all
|
|
39
|
+
Requires-Dist: dramatiq[redis]>=2.1.0; extra == 'all'
|
|
40
|
+
Requires-Dist: opentelemetry-api>=1.41.1; extra == 'all'
|
|
41
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.41.1; extra == 'all'
|
|
42
|
+
Requires-Dist: opentelemetry-instrumentation-asgi>=0.62b1; extra == 'all'
|
|
43
|
+
Requires-Dist: opentelemetry-sdk>=1.41.1; extra == 'all'
|
|
44
|
+
Provides-Extra: dramatiq
|
|
45
|
+
Requires-Dist: dramatiq[redis]>=2.1.0; extra == 'dramatiq'
|
|
46
|
+
Provides-Extra: otel
|
|
47
|
+
Requires-Dist: opentelemetry-api>=1.41.1; extra == 'otel'
|
|
48
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.41.1; extra == 'otel'
|
|
49
|
+
Requires-Dist: opentelemetry-instrumentation-asgi>=0.62b1; extra == 'otel'
|
|
50
|
+
Requires-Dist: opentelemetry-sdk>=1.41.1; extra == 'otel'
|
|
51
|
+
Description-Content-Type: text/markdown
|
|
52
|
+
|
|
53
|
+
# causeway (Python)
|
|
54
|
+
|
|
55
|
+
> A lean backend framework for type-safe Python APIs.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
uv add 'causeway==0.1.0a0' # alpha — drop the pin once v0.1.0 ships
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
This is the core Python package of [Causeway](https://github.com/tamimbinhakim/causeway). It ships:
|
|
62
|
+
|
|
63
|
+
- A **file-based router** that walks `src/app/routes/**/*.py`, picks up `[id].py`, `(group)/`, `_middleware.py`, and `_scope.py`, and registers handlers into the app's route table.
|
|
64
|
+
- A **typed config layer** that wraps `pydantic-settings` and exposes non-secret fields to the generated TypeScript client.
|
|
65
|
+
- A **scoped DI container** driven by `_scope.py` providers.
|
|
66
|
+
- A **`@task` contract** for background jobs with a Dramatiq reference adapter.
|
|
67
|
+
- A **plugin registry** with Python-entry-point discovery and per-environment activation.
|
|
68
|
+
- A **CLI** (`causeway new`, `causeway dev`, `causeway build`, `causeway deploy <target>`) that runs the whole loop.
|
|
69
|
+
|
|
70
|
+
For the full story, the design rationale, and a side-by-side vs. FastAPI / Django + Ninja / Encore.ts / NestJS, see the [repo README](https://github.com/tamimbinhakim/causeway).
|
|
71
|
+
|
|
72
|
+
## 30-second example
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
my-app/
|
|
76
|
+
├── pyproject.toml
|
|
77
|
+
├── causeway.toml
|
|
78
|
+
└── src/app/
|
|
79
|
+
├── config.py
|
|
80
|
+
├── plugins.py
|
|
81
|
+
└── routes/
|
|
82
|
+
├── _middleware.py
|
|
83
|
+
├── index.py
|
|
84
|
+
└── users/
|
|
85
|
+
├── _scope.py
|
|
86
|
+
├── index.py
|
|
87
|
+
└── [id].py
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
# src/app/routes/users/[id].py
|
|
92
|
+
from typing import Annotated
|
|
93
|
+
from uuid import UUID
|
|
94
|
+
from msgspec import Struct
|
|
95
|
+
from causeway import get, raises
|
|
96
|
+
from causeway.errors import NotFound
|
|
97
|
+
|
|
98
|
+
class User(Struct):
|
|
99
|
+
id: UUID
|
|
100
|
+
name: str
|
|
101
|
+
|
|
102
|
+
@get
|
|
103
|
+
@raises(NotFound)
|
|
104
|
+
async def show(id: UUID) -> User:
|
|
105
|
+
...
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Run it:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
causeway dev
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`causeway dev` discovers the routes, boots uvicorn, regenerates a typed `client.ts` for your frontend on every save, and exposes `/__causeway` with the route tree, registered tasks, current config, and plugin list.
|
|
115
|
+
|
|
116
|
+
## Primitives in this package
|
|
117
|
+
|
|
118
|
+
| Primitive | Purpose |
|
|
119
|
+
| ------------------------------------------------ | ------------------------------------------------------------------------ |
|
|
120
|
+
| File router (`[id].py`, `(group)/`) | Discovery + URL pattern generation. |
|
|
121
|
+
| `_middleware.py` / `_scope.py` | Per-subtree middleware + scoped DI providers. |
|
|
122
|
+
| `Settings` (wraps `pydantic-settings`) | Typed config with allowlisted exposure to the generated client. |
|
|
123
|
+
| `@get` / `@post` / `@put` / `@patch` / `@delete` | HTTP method decorators. |
|
|
124
|
+
| `@task(...)` | Background-job contract; adapter-agnostic. |
|
|
125
|
+
| `@cron(...)` | Scheduled tasks via the same adapter. |
|
|
126
|
+
| `register(...)` | Plugin registration (`TaskAdapter`, `Storage`, `KV`, `AuthProvider`, …). |
|
|
127
|
+
| `TestApp` | Test client with DI overrides and `tasks_eager()` mode. |
|
|
128
|
+
| `causeway` CLI | `new`, `dev`, `build`, `plugins`, `deploy <target>`. |
|
|
129
|
+
|
|
130
|
+
Full reference: <https://github.com/tamimbinhakim/causeway/blob/main/docs/reference.md>
|
|
131
|
+
|
|
132
|
+
## Optional extras
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
uv add 'causeway[dramatiq]' # reference task adapter
|
|
136
|
+
uv add 'causeway[otel]' # OpenTelemetry middleware hooks
|
|
137
|
+
uv add 'causeway[all]' # everything
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Scope
|
|
141
|
+
|
|
142
|
+
Causeway ships project shape and a small set of contracts. It does **not** ship vertical integrations — no ORM, no admin panel, no template engine, no infrastructure provisioning. Those layers compose on top of the fundamentals and live in their own plugin packages or in user code.
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
MIT
|
causeway-0.1.0/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# causeway (Python)
|
|
2
|
+
|
|
3
|
+
> A lean backend framework for type-safe Python APIs.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
uv add 'causeway==0.1.0a0' # alpha — drop the pin once v0.1.0 ships
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
This is the core Python package of [Causeway](https://github.com/tamimbinhakim/causeway). It ships:
|
|
10
|
+
|
|
11
|
+
- A **file-based router** that walks `src/app/routes/**/*.py`, picks up `[id].py`, `(group)/`, `_middleware.py`, and `_scope.py`, and registers handlers into the app's route table.
|
|
12
|
+
- A **typed config layer** that wraps `pydantic-settings` and exposes non-secret fields to the generated TypeScript client.
|
|
13
|
+
- A **scoped DI container** driven by `_scope.py` providers.
|
|
14
|
+
- A **`@task` contract** for background jobs with a Dramatiq reference adapter.
|
|
15
|
+
- A **plugin registry** with Python-entry-point discovery and per-environment activation.
|
|
16
|
+
- A **CLI** (`causeway new`, `causeway dev`, `causeway build`, `causeway deploy <target>`) that runs the whole loop.
|
|
17
|
+
|
|
18
|
+
For the full story, the design rationale, and a side-by-side vs. FastAPI / Django + Ninja / Encore.ts / NestJS, see the [repo README](https://github.com/tamimbinhakim/causeway).
|
|
19
|
+
|
|
20
|
+
## 30-second example
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
my-app/
|
|
24
|
+
├── pyproject.toml
|
|
25
|
+
├── causeway.toml
|
|
26
|
+
└── src/app/
|
|
27
|
+
├── config.py
|
|
28
|
+
├── plugins.py
|
|
29
|
+
└── routes/
|
|
30
|
+
├── _middleware.py
|
|
31
|
+
├── index.py
|
|
32
|
+
└── users/
|
|
33
|
+
├── _scope.py
|
|
34
|
+
├── index.py
|
|
35
|
+
└── [id].py
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
# src/app/routes/users/[id].py
|
|
40
|
+
from typing import Annotated
|
|
41
|
+
from uuid import UUID
|
|
42
|
+
from msgspec import Struct
|
|
43
|
+
from causeway import get, raises
|
|
44
|
+
from causeway.errors import NotFound
|
|
45
|
+
|
|
46
|
+
class User(Struct):
|
|
47
|
+
id: UUID
|
|
48
|
+
name: str
|
|
49
|
+
|
|
50
|
+
@get
|
|
51
|
+
@raises(NotFound)
|
|
52
|
+
async def show(id: UUID) -> User:
|
|
53
|
+
...
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Run it:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
causeway dev
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`causeway dev` discovers the routes, boots uvicorn, regenerates a typed `client.ts` for your frontend on every save, and exposes `/__causeway` with the route tree, registered tasks, current config, and plugin list.
|
|
63
|
+
|
|
64
|
+
## Primitives in this package
|
|
65
|
+
|
|
66
|
+
| Primitive | Purpose |
|
|
67
|
+
| ------------------------------------------------ | ------------------------------------------------------------------------ |
|
|
68
|
+
| File router (`[id].py`, `(group)/`) | Discovery + URL pattern generation. |
|
|
69
|
+
| `_middleware.py` / `_scope.py` | Per-subtree middleware + scoped DI providers. |
|
|
70
|
+
| `Settings` (wraps `pydantic-settings`) | Typed config with allowlisted exposure to the generated client. |
|
|
71
|
+
| `@get` / `@post` / `@put` / `@patch` / `@delete` | HTTP method decorators. |
|
|
72
|
+
| `@task(...)` | Background-job contract; adapter-agnostic. |
|
|
73
|
+
| `@cron(...)` | Scheduled tasks via the same adapter. |
|
|
74
|
+
| `register(...)` | Plugin registration (`TaskAdapter`, `Storage`, `KV`, `AuthProvider`, …). |
|
|
75
|
+
| `TestApp` | Test client with DI overrides and `tasks_eager()` mode. |
|
|
76
|
+
| `causeway` CLI | `new`, `dev`, `build`, `plugins`, `deploy <target>`. |
|
|
77
|
+
|
|
78
|
+
Full reference: <https://github.com/tamimbinhakim/causeway/blob/main/docs/reference.md>
|
|
79
|
+
|
|
80
|
+
## Optional extras
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
uv add 'causeway[dramatiq]' # reference task adapter
|
|
84
|
+
uv add 'causeway[otel]' # OpenTelemetry middleware hooks
|
|
85
|
+
uv add 'causeway[all]' # everything
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Scope
|
|
89
|
+
|
|
90
|
+
Causeway ships project shape and a small set of contracts. It does **not** ship vertical integrations — no ORM, no admin panel, no template engine, no infrastructure provisioning. Those layers compose on top of the fundamentals and live in their own plugin packages or in user code.
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
MIT
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.29.0"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "causeway"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A lean backend framework for type-safe Python APIs — file-based routing, typed config/DI, scoped providers, plugin registry, and background tasks."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.11"
|
|
12
|
+
authors = [{ name = "Tamim Bin Hakim", email = "tamimbinhakim.work@gmail.com" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"asgi",
|
|
15
|
+
"backend-framework",
|
|
16
|
+
"file-based-routing",
|
|
17
|
+
"dependency-injection",
|
|
18
|
+
"background-tasks",
|
|
19
|
+
"dyadpy",
|
|
20
|
+
]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Development Status :: 3 - Alpha",
|
|
23
|
+
"Framework :: AsyncIO",
|
|
24
|
+
"Intended Audience :: Developers",
|
|
25
|
+
"License :: OSI Approved :: MIT License",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
29
|
+
"Programming Language :: Python :: 3.11",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Programming Language :: Python :: 3.13",
|
|
32
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
33
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
34
|
+
"Typing :: Typed",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
dependencies = [
|
|
38
|
+
"dyadpy>=0.1.0a0",
|
|
39
|
+
"pydantic-settings>=2.14.1",
|
|
40
|
+
"pydantic>=2.13.4",
|
|
41
|
+
"msgspec>=0.19",
|
|
42
|
+
"starlette>=1.0.0",
|
|
43
|
+
"uvicorn[standard]>=0.47.0",
|
|
44
|
+
"watchfiles>=1.1.1",
|
|
45
|
+
"typer>=0.25.1",
|
|
46
|
+
"rich>=15.0.0",
|
|
47
|
+
"structlog>=25.5.0",
|
|
48
|
+
"anyio>=4.13.0",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[project.optional-dependencies]
|
|
52
|
+
dramatiq = ["dramatiq[redis]>=2.1.0"]
|
|
53
|
+
otel = [
|
|
54
|
+
"opentelemetry-api>=1.41.1",
|
|
55
|
+
"opentelemetry-sdk>=1.41.1",
|
|
56
|
+
"opentelemetry-instrumentation-asgi>=0.62b1",
|
|
57
|
+
"opentelemetry-exporter-otlp-proto-http>=1.41.1",
|
|
58
|
+
]
|
|
59
|
+
all = ["causeway[dramatiq,otel]"]
|
|
60
|
+
|
|
61
|
+
[project.scripts]
|
|
62
|
+
causeway = "causeway.cli:main"
|
|
63
|
+
|
|
64
|
+
[project.entry-points."causeway.plugins"]
|
|
65
|
+
# Reference adapters shipped in core register themselves under this group.
|
|
66
|
+
|
|
67
|
+
[project.urls]
|
|
68
|
+
Homepage = "https://github.com/tamimbinhakim/causeway"
|
|
69
|
+
Documentation = "https://github.com/tamimbinhakim/causeway/tree/main/docs"
|
|
70
|
+
Repository = "https://github.com/tamimbinhakim/causeway"
|
|
71
|
+
Issues = "https://github.com/tamimbinhakim/causeway/issues"
|
|
72
|
+
Changelog = "https://github.com/tamimbinhakim/causeway/blob/main/packages/causeway/CHANGELOG.md"
|
|
73
|
+
|
|
74
|
+
[dependency-groups]
|
|
75
|
+
dev = [
|
|
76
|
+
"pytest>=9.0.3",
|
|
77
|
+
"pytest-asyncio>=1.3.0",
|
|
78
|
+
"pytest-cov>=7.1.0",
|
|
79
|
+
"httpx>=0.28.1",
|
|
80
|
+
"ruff>=0.15.13",
|
|
81
|
+
"mypy>=2.1.0",
|
|
82
|
+
"types-setuptools",
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
[tool.hatch.build.targets.wheel]
|
|
86
|
+
packages = ["src/causeway"]
|
|
87
|
+
|
|
88
|
+
[tool.hatch.build.targets.sdist]
|
|
89
|
+
include = ["/src", "/tests", "/README.md", "/CHANGELOG.md"]
|
|
90
|
+
|
|
91
|
+
# ---- ruff ----
|
|
92
|
+
[tool.ruff]
|
|
93
|
+
line-length = 100
|
|
94
|
+
target-version = "py311"
|
|
95
|
+
src = ["src", "tests"]
|
|
96
|
+
|
|
97
|
+
[tool.ruff.lint]
|
|
98
|
+
select = [
|
|
99
|
+
"E",
|
|
100
|
+
"F",
|
|
101
|
+
"W", # pycodestyle / pyflakes
|
|
102
|
+
"I", # isort
|
|
103
|
+
"B", # bugbear
|
|
104
|
+
"UP", # pyupgrade
|
|
105
|
+
"SIM", # simplify
|
|
106
|
+
"C4", # comprehensions
|
|
107
|
+
"PT", # pytest
|
|
108
|
+
"RUF", # ruff
|
|
109
|
+
"TID", # tidy imports
|
|
110
|
+
"PYI", # type stubs
|
|
111
|
+
"ANN", # annotations
|
|
112
|
+
]
|
|
113
|
+
ignore = [
|
|
114
|
+
"ANN401", # allow Any in select cases
|
|
115
|
+
"E501", # line length handled by formatter
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
[tool.ruff.lint.per-file-ignores]
|
|
119
|
+
"tests/**" = ["ANN", "B017", "PT011", "RUF012"]
|
|
120
|
+
|
|
121
|
+
[tool.ruff.lint.isort]
|
|
122
|
+
known-first-party = ["causeway"]
|
|
123
|
+
|
|
124
|
+
[tool.ruff.format]
|
|
125
|
+
quote-style = "double"
|
|
126
|
+
indent-style = "space"
|
|
127
|
+
docstring-code-format = true
|
|
128
|
+
|
|
129
|
+
# ---- mypy ----
|
|
130
|
+
[tool.mypy]
|
|
131
|
+
python_version = "3.11"
|
|
132
|
+
strict = true
|
|
133
|
+
warn_unreachable = true
|
|
134
|
+
warn_redundant_casts = true
|
|
135
|
+
warn_unused_ignores = true
|
|
136
|
+
disallow_any_generics = true
|
|
137
|
+
no_implicit_reexport = true
|
|
138
|
+
files = ["src/causeway"]
|
|
139
|
+
|
|
140
|
+
[[tool.mypy.overrides]]
|
|
141
|
+
module = "tests.*"
|
|
142
|
+
disallow_untyped_defs = false
|
|
143
|
+
|
|
144
|
+
# ---- pytest ----
|
|
145
|
+
[tool.pytest.ini_options]
|
|
146
|
+
minversion = "8.0"
|
|
147
|
+
testpaths = ["tests"]
|
|
148
|
+
asyncio_mode = "auto"
|
|
149
|
+
addopts = ["-ra", "--strict-markers", "--strict-config"]
|
|
150
|
+
filterwarnings = ["error"]
|
|
151
|
+
|
|
152
|
+
# ---- coverage ----
|
|
153
|
+
[tool.coverage.run]
|
|
154
|
+
branch = true
|
|
155
|
+
source = ["causeway"]
|
|
156
|
+
omit = ["src/causeway/cli.py"]
|
|
157
|
+
|
|
158
|
+
[tool.coverage.report]
|
|
159
|
+
exclude_lines = ["pragma: no cover", "if TYPE_CHECKING:", "raise NotImplementedError", "\\.\\.\\."]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Causeway — a lean backend framework for type-safe Python APIs.
|
|
2
|
+
|
|
3
|
+
Public API surface for v0.1. Anything not listed in ``__all__`` (or imported
|
|
4
|
+
from a private underscore module like ``causeway._methods``) is implementation
|
|
5
|
+
detail and may change in any release. See ``docs/semver.md``.
|
|
6
|
+
|
|
7
|
+
Slice landed in this release:
|
|
8
|
+
|
|
9
|
+
- File-based routing primitives — :func:`get` / :func:`post` / :func:`put` /
|
|
10
|
+
:func:`patch` / :func:`delete` decorators, :func:`causeway.routing.discover` and
|
|
11
|
+
:func:`causeway.routing.register` for the walker.
|
|
12
|
+
- Scopes — :func:`provide` for ``_scope.py`` providers.
|
|
13
|
+
- Middleware — :class:`Middleware` base class and :func:`guard` decorator.
|
|
14
|
+
- Config — :class:`Settings` (re-export of ``pydantic_settings.BaseSettings``)
|
|
15
|
+
and :class:`Manifest` (parsed ``causeway.toml``).
|
|
16
|
+
|
|
17
|
+
Re-exported from ``dyadpy`` so app code only depends on ``causeway``:
|
|
18
|
+
|
|
19
|
+
- :func:`Depends` — DI marker for handler parameters.
|
|
20
|
+
- :func:`raises` — declare exception types the handler may raise (flows to
|
|
21
|
+
the generated TS client's ``Result<T, E>`` union).
|
|
22
|
+
- :func:`stream` — SSE return type marker.
|
|
23
|
+
- :data:`Bytes` — raw-body sentinel.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from dyadpy import Bytes, Depends, raises, stream
|
|
29
|
+
|
|
30
|
+
from causeway import errors
|
|
31
|
+
from causeway._methods import delete, get, patch, post, put
|
|
32
|
+
from causeway.app import create_app
|
|
33
|
+
from causeway.config import Manifest, Settings
|
|
34
|
+
from causeway.middleware import Middleware, guard
|
|
35
|
+
from causeway.observability import RequestIdMiddleware, configure_logging, configure_otel
|
|
36
|
+
from causeway.plugins import env, register
|
|
37
|
+
from causeway.scope import provide
|
|
38
|
+
from causeway.tasks import cron, task, tasks_eager
|
|
39
|
+
|
|
40
|
+
__version__ = "0.1.0"
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"Bytes",
|
|
44
|
+
"Depends",
|
|
45
|
+
"Manifest",
|
|
46
|
+
"Middleware",
|
|
47
|
+
"RequestIdMiddleware",
|
|
48
|
+
"Settings",
|
|
49
|
+
"__version__",
|
|
50
|
+
"configure_logging",
|
|
51
|
+
"configure_otel",
|
|
52
|
+
"create_app",
|
|
53
|
+
"cron",
|
|
54
|
+
"delete",
|
|
55
|
+
"env",
|
|
56
|
+
"errors",
|
|
57
|
+
"get",
|
|
58
|
+
"guard",
|
|
59
|
+
"patch",
|
|
60
|
+
"post",
|
|
61
|
+
"provide",
|
|
62
|
+
"put",
|
|
63
|
+
"raises",
|
|
64
|
+
"register",
|
|
65
|
+
"stream",
|
|
66
|
+
"task",
|
|
67
|
+
"tasks_eager",
|
|
68
|
+
]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Tiny crontab parser.
|
|
2
|
+
|
|
3
|
+
Five fields: minute hour day-of-month month day-of-week. Each field is one
|
|
4
|
+
of: ``*``, an integer, a comma list ``1,3,5``, or a step ``*/N``. That's
|
|
5
|
+
all we need for the in-process reference adapter — production adapters
|
|
6
|
+
delegate to their own scheduler.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from datetime import datetime, timedelta
|
|
12
|
+
|
|
13
|
+
_FIELD_RANGES = [
|
|
14
|
+
(0, 59), # minute
|
|
15
|
+
(0, 23), # hour
|
|
16
|
+
(1, 31), # day of month
|
|
17
|
+
(1, 12), # month
|
|
18
|
+
(0, 6), # day of week (0=Mon...6=Sun for simplicity)
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _parse_field(spec: str, lo: int, hi: int) -> set[int]:
|
|
23
|
+
if spec == "*":
|
|
24
|
+
return set(range(lo, hi + 1))
|
|
25
|
+
if spec.startswith("*/"):
|
|
26
|
+
step = int(spec[2:])
|
|
27
|
+
return set(range(lo, hi + 1, step))
|
|
28
|
+
out: set[int] = set()
|
|
29
|
+
for piece in spec.split(","):
|
|
30
|
+
out.add(int(piece))
|
|
31
|
+
return out
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _matches(expr: str, now: datetime) -> bool:
|
|
35
|
+
parts = expr.split()
|
|
36
|
+
if len(parts) != 5:
|
|
37
|
+
msg = f"cron expr must be 5 fields: {expr!r}"
|
|
38
|
+
raise ValueError(msg)
|
|
39
|
+
minute, hour, dom, month, dow = (
|
|
40
|
+
_parse_field(p, *_FIELD_RANGES[i]) for i, p in enumerate(parts)
|
|
41
|
+
)
|
|
42
|
+
return (
|
|
43
|
+
now.minute in minute
|
|
44
|
+
and now.hour in hour
|
|
45
|
+
and now.day in dom
|
|
46
|
+
and now.month in month
|
|
47
|
+
# weekday(): Mon=0..Sun=6 — matches our field convention.
|
|
48
|
+
and now.weekday() in dow
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def next_fire(expr: str, after: datetime) -> float:
|
|
53
|
+
"""Seconds until the next minute that matches ``expr`` after ``after``.
|
|
54
|
+
|
|
55
|
+
Scans minute-by-minute up to 31 days ahead — fine for the in-memory
|
|
56
|
+
reference adapter. If no match is found, raises.
|
|
57
|
+
"""
|
|
58
|
+
candidate = (after + timedelta(minutes=1)).replace(second=0, microsecond=0)
|
|
59
|
+
end = candidate + timedelta(days=31)
|
|
60
|
+
while candidate < end:
|
|
61
|
+
if _matches(expr, candidate):
|
|
62
|
+
return (candidate - after).total_seconds()
|
|
63
|
+
candidate += timedelta(minutes=1)
|
|
64
|
+
msg = f"no cron match within 31 days for {expr!r}"
|
|
65
|
+
raise ValueError(msg)
|