rakaia-streams 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.
- rakaia_streams-0.1.0/.gitignore +56 -0
- rakaia_streams-0.1.0/PKG-INFO +234 -0
- rakaia_streams-0.1.0/README.md +187 -0
- rakaia_streams-0.1.0/pyproject.toml +124 -0
- rakaia_streams-0.1.0/src/django_rakaia/PROTOCOL_API.md +356 -0
- rakaia_streams-0.1.0/src/django_rakaia/__init__.py +44 -0
- rakaia_streams-0.1.0/src/django_rakaia/admin.py +298 -0
- rakaia_streams-0.1.0/src/django_rakaia/apps.py +42 -0
- rakaia_streams-0.1.0/src/django_rakaia/channels_signals.py +93 -0
- rakaia_streams-0.1.0/src/django_rakaia/channels_views.py +163 -0
- rakaia_streams-0.1.0/src/django_rakaia/decorators.py +266 -0
- rakaia_streams-0.1.0/src/django_rakaia/django_store.py +251 -0
- rakaia_streams-0.1.0/src/django_rakaia/effect_executor.py +216 -0
- rakaia_streams-0.1.0/src/django_rakaia/handlers_autodiscover.py +18 -0
- rakaia_streams-0.1.0/src/django_rakaia/hermeticity.py +91 -0
- rakaia_streams-0.1.0/src/django_rakaia/history.py +60 -0
- rakaia_streams-0.1.0/src/django_rakaia/integration.py +21 -0
- rakaia_streams-0.1.0/src/django_rakaia/management/__init__.py +0 -0
- rakaia_streams-0.1.0/src/django_rakaia/management/commands/__init__.py +0 -0
- rakaia_streams-0.1.0/src/django_rakaia/management/commands/replay.py +92 -0
- rakaia_streams-0.1.0/src/django_rakaia/middleware.py +48 -0
- rakaia_streams-0.1.0/src/django_rakaia/migrations/0001_initial.py +171 -0
- rakaia_streams-0.1.0/src/django_rakaia/migrations/0002_streamevent_metadata.py +17 -0
- rakaia_streams-0.1.0/src/django_rakaia/migrations/0003_consumercursor.py +32 -0
- rakaia_streams-0.1.0/src/django_rakaia/migrations/0004_streamevent_event_ts.py +17 -0
- rakaia_streams-0.1.0/src/django_rakaia/migrations/0005_streamoffsetwatermark.py +25 -0
- rakaia_streams-0.1.0/src/django_rakaia/migrations/0006_alter_streamevent_data_alter_streamevent_metadata.py +29 -0
- rakaia_streams-0.1.0/src/django_rakaia/migrations/__init__.py +0 -0
- rakaia_streams-0.1.0/src/django_rakaia/models.py +372 -0
- rakaia_streams-0.1.0/src/django_rakaia/projection_reader.py +53 -0
- rakaia_streams-0.1.0/src/django_rakaia/protocol_views.py +502 -0
- rakaia_streams-0.1.0/src/django_rakaia/py.typed +0 -0
- rakaia_streams-0.1.0/src/django_rakaia/replay.py +64 -0
- rakaia_streams-0.1.0/src/django_rakaia/store.py +37 -0
- rakaia_streams-0.1.0/src/django_rakaia/streams.py +156 -0
- rakaia_streams-0.1.0/src/django_rakaia/subscription.py +46 -0
- rakaia_streams-0.1.0/src/django_rakaia/templates/django_rakaia/_activity_item.html +1 -0
- rakaia_streams-0.1.0/src/django_rakaia/templates/django_rakaia/_translation_rows.html +11 -0
- rakaia_streams-0.1.0/src/django_rakaia/templates/django_rakaia/base.html +235 -0
- rakaia_streams-0.1.0/src/django_rakaia/templates/django_rakaia/stream_detail.html +522 -0
- rakaia_streams-0.1.0/src/django_rakaia/templates/django_rakaia/streams_index.html +137 -0
- rakaia_streams-0.1.0/src/django_rakaia/templates/django_rakaia/translations_index.html +167 -0
- rakaia_streams-0.1.0/src/django_rakaia/urls.py +74 -0
- rakaia_streams-0.1.0/src/django_rakaia/verification.py +417 -0
- rakaia_streams-0.1.0/src/django_rakaia/views.py +474 -0
- rakaia_streams-0.1.0/src/rakaia/__init__.py +197 -0
- rakaia_streams-0.1.0/src/rakaia/__main__.py +43 -0
- rakaia_streams-0.1.0/src/rakaia/_asgi.py +159 -0
- rakaia_streams-0.1.0/src/rakaia/append.py +89 -0
- rakaia_streams-0.1.0/src/rakaia/context.py +63 -0
- rakaia_streams-0.1.0/src/rakaia/cursor.py +84 -0
- rakaia_streams-0.1.0/src/rakaia/effects.py +417 -0
- rakaia_streams-0.1.0/src/rakaia/executors.py +36 -0
- rakaia_streams-0.1.0/src/rakaia/handler.py +1234 -0
- rakaia_streams-0.1.0/src/rakaia/history.py +139 -0
- rakaia_streams-0.1.0/src/rakaia/json_mode.py +94 -0
- rakaia_streams-0.1.0/src/rakaia/projections.py +476 -0
- rakaia_streams-0.1.0/src/rakaia/protocols.py +164 -0
- rakaia_streams-0.1.0/src/rakaia/py.typed +0 -0
- rakaia_streams-0.1.0/src/rakaia/registry.py +1180 -0
- rakaia_streams-0.1.0/src/rakaia/replay.py +605 -0
- rakaia_streams-0.1.0/src/rakaia/source_hash.py +33 -0
- rakaia_streams-0.1.0/src/rakaia/store.py +746 -0
- rakaia_streams-0.1.0/src/rakaia/subscription.py +142 -0
- rakaia_streams-0.1.0/src/rakaia/types.py +286 -0
- rakaia_streams-0.1.0/tests/__init__.py +0 -0
- rakaia_streams-0.1.0/tests/store_contract.py +217 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/__init__.py +0 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/apps.py +6 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/asgi.py +7 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/dev_settings.py +25 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/handlers.py +27 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/migrations/0001_initial.py +116 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/migrations/0002_history.py +35 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/migrations/0003_alert.py +42 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/migrations/0004_alert_dismissed_version.py +15 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/migrations/0005_measure.py +29 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/migrations/0006_sukuprojection.py +30 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/migrations/0007_archiveddoc_softdeletedoc.py +44 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/migrations/__init__.py +0 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/models.py +393 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/settings.py +55 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_admin.py +142 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_alerts.py +716 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_apps.py +57 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_autodiscover.py +33 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_channels.py +297 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_decorators.py +358 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_decorators_consumer_contract.py +396 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_django_store.py +398 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_effect_executor.py +517 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_hermeticity.py +126 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_history_materialization.py +121 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_history_materializer.py +86 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_middleware.py +39 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_model_stream_reader.py +259 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_preloaded_reader.py +170 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_projection_reader.py +281 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_protocol.py +574 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_refs.py +127 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_replay_command.py +139 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_replay_helper.py +83 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_store_contract.py +19 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_subscription.py +135 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_using_seam.py +162 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_verification.py +257 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/test_views.py +258 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/upcasters.py +17 -0
- rakaia_streams-0.1.0/tests/test_django_rakaia/urls.py +12 -0
- rakaia_streams-0.1.0/tests/test_rakaia/__init__.py +0 -0
- rakaia_streams-0.1.0/tests/test_rakaia/conftest.py +22 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_alerts.py +139 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_append.py +85 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_asgi_helpers.py +183 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_context.py +56 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_cursor.py +74 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_effects.py +529 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_executors.py +64 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_handler.py +385 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_history.py +148 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_json_mode.py +104 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_offset_patterns.py +61 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_projections.py +549 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_refs.py +143 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_registry.py +404 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_registry_isolation.py +120 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_registry_persistence.py +296 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_replay.py +1401 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_source_hash.py +48 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_store.py +711 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_store_contract.py +14 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_subscription.py +123 -0
- rakaia_streams-0.1.0/tests/test_rakaia/test_upcasters.py +369 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
env/
|
|
8
|
+
build/
|
|
9
|
+
develop-eggs/
|
|
10
|
+
dist/
|
|
11
|
+
downloads/
|
|
12
|
+
eggs/
|
|
13
|
+
.eggs/
|
|
14
|
+
lib/
|
|
15
|
+
lib64/
|
|
16
|
+
parts/
|
|
17
|
+
sdist/
|
|
18
|
+
var/
|
|
19
|
+
wheels/
|
|
20
|
+
*.egg-info/
|
|
21
|
+
.installed.cfg
|
|
22
|
+
*.egg
|
|
23
|
+
|
|
24
|
+
# Virtual Environment
|
|
25
|
+
.venv/
|
|
26
|
+
venv/
|
|
27
|
+
ENV/
|
|
28
|
+
|
|
29
|
+
# IDEs
|
|
30
|
+
.idea/
|
|
31
|
+
.vscode/
|
|
32
|
+
.history/
|
|
33
|
+
.windsurf/
|
|
34
|
+
|
|
35
|
+
# Coverage
|
|
36
|
+
.coverage
|
|
37
|
+
htmlcov/
|
|
38
|
+
|
|
39
|
+
# Durable Streams temporary/test data
|
|
40
|
+
/tmp/
|
|
41
|
+
.durable-streams/
|
|
42
|
+
django_rakaia_dev.db
|
|
43
|
+
db.sqlite3
|
|
44
|
+
|
|
45
|
+
# Collected static files (chat sample → WhiteNoise)
|
|
46
|
+
examples/chat/staticfiles/
|
|
47
|
+
|
|
48
|
+
# Zensical build output
|
|
49
|
+
/site/
|
|
50
|
+
.zensical-cache/
|
|
51
|
+
|
|
52
|
+
# Conformance harness (JS deps + run logs)
|
|
53
|
+
conformance/node_modules/
|
|
54
|
+
conformance/conformance-results.json
|
|
55
|
+
conformance-results.log
|
|
56
|
+
conformance-server.log
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rakaia-streams
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python server implementation of the Durable Streams protocol
|
|
5
|
+
Project-URL: Homepage, https://github.com/joshbrooks/rakaia
|
|
6
|
+
Project-URL: Repository, https://github.com/joshbrooks/rakaia
|
|
7
|
+
Project-URL: Issues, https://github.com/joshbrooks/rakaia/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/joshbrooks/rakaia/blob/main/CHANGELOG.md
|
|
9
|
+
Author: Josh Brooks
|
|
10
|
+
License: MIT
|
|
11
|
+
Keywords: asgi,durable-streams,http,sse,streaming
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
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: Topic :: Internet :: WWW/HTTP
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: django-types>=0.23.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: django>=4.2; extra == 'dev'
|
|
26
|
+
Requires-Dist: httpx>=0.27.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pyright>=1.1.400; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-cov>=6.0.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-django>=4.9.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: ruff>=0.8.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: uvicorn>=0.30.0; extra == 'dev'
|
|
34
|
+
Provides-Extra: django
|
|
35
|
+
Requires-Dist: channels>=4.0; extra == 'django'
|
|
36
|
+
Requires-Dist: daphne>=4.2.1; extra == 'django'
|
|
37
|
+
Requires-Dist: django>=4.2.0; extra == 'django'
|
|
38
|
+
Provides-Extra: docs
|
|
39
|
+
Requires-Dist: markdown>=3.10.2; extra == 'docs'
|
|
40
|
+
Requires-Dist: pymdown-extensions>=11.0.1; extra == 'docs'
|
|
41
|
+
Requires-Dist: zensical>=0.0.51; extra == 'docs'
|
|
42
|
+
Provides-Extra: prod
|
|
43
|
+
Requires-Dist: channels-redis>=4.1.0; extra == 'prod'
|
|
44
|
+
Requires-Dist: hypercorn>=0.17.0; extra == 'prod'
|
|
45
|
+
Requires-Dist: whitenoise>=6.0; extra == 'prod'
|
|
46
|
+
Description-Content-Type: text/markdown
|
|
47
|
+
|
|
48
|
+
# Rakaia
|
|
49
|
+
|
|
50
|
+
**Rakaia** is a Python implementation of the [Durable Streams protocol](docs/protocol.md) —
|
|
51
|
+
an HTTP-based protocol for append-only, ordered, durable byte streams.
|
|
52
|
+
|
|
53
|
+
It ships two installable packages:
|
|
54
|
+
|
|
55
|
+
- **`rakaia`** — A zero-dependency ASGI app implementing the protocol. Run it
|
|
56
|
+
standalone (`uvicorn`/`daphne`/`granian`) or mount it inside Django/FastAPI/Starlette.
|
|
57
|
+
- **`django_rakaia`** — A Django app with normalized stream models, a
|
|
58
|
+
`@stream_model` decorator, Channels-based SSE broadcasting, and an admin
|
|
59
|
+
interface.
|
|
60
|
+
|
|
61
|
+
## Install
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install rakaia-streams # core protocol server
|
|
65
|
+
pip install "rakaia-streams[django]" # + Django integration
|
|
66
|
+
pip install "rakaia-streams[django,prod]" # + channels-redis + hypercorn for prod
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The distribution is named `rakaia-streams` on PyPI (plain `rakaia` was already
|
|
70
|
+
taken); the import names are unchanged — `import rakaia`, `import django_rakaia`.
|
|
71
|
+
|
|
72
|
+
## Quick start (standalone)
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install rakaia-streams uvicorn
|
|
76
|
+
uvicorn rakaia:app --port 4437
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from rakaia import create_app, StreamStore
|
|
81
|
+
|
|
82
|
+
app = create_app() # in-memory store
|
|
83
|
+
app = create_app(store=StreamStore())
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Quick start (Django)
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
# models.py
|
|
90
|
+
from dataclasses import dataclass
|
|
91
|
+
from django.db import models
|
|
92
|
+
from django_rakaia.decorators import stream_model
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class RoomData:
|
|
96
|
+
id: int
|
|
97
|
+
name: str
|
|
98
|
+
|
|
99
|
+
@stream_model(
|
|
100
|
+
stream_paths=lambda obj: f"room:{obj.id}:messages",
|
|
101
|
+
to_dataclass=lambda obj: RoomData(id=obj.id, name=obj.name),
|
|
102
|
+
)
|
|
103
|
+
class ChatRoom(models.Model):
|
|
104
|
+
name = models.CharField(max_length=100)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Every save/delete now emits a stream event you can subscribe to over SSE.
|
|
108
|
+
|
|
109
|
+
## Documentation
|
|
110
|
+
|
|
111
|
+
The documentation site is built with [Zensical](https://zensical.org/).
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
uv sync --extra docs
|
|
115
|
+
uv run zensical serve # live preview at http://localhost:8000
|
|
116
|
+
uv run zensical build # static build into ./site
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Pages live in [`docs/`](docs/) and the site config is in
|
|
120
|
+
[`zensical.toml`](zensical.toml):
|
|
121
|
+
|
|
122
|
+
- [Overview & quick start](docs/index.md)
|
|
123
|
+
- [**What's new — a guided tour**](docs/whats-new.md) — start here to see the recent features, each with a one-command demo
|
|
124
|
+
- [Glossary](docs/glossary.md) — plain-language definitions of the event-sourcing terms
|
|
125
|
+
- [Django integration](docs/django-integration.md)
|
|
126
|
+
- [Versioned handlers](docs/versioned-handlers.md)
|
|
127
|
+
- [Projections & fan-out](docs/projections-and-fan-out.md)
|
|
128
|
+
- [Dry-run & executors](docs/dry-run-and-executors.md)
|
|
129
|
+
- [Translations](docs/translations.md)
|
|
130
|
+
- [Deployment](docs/deployment.md)
|
|
131
|
+
- [Protocol specification](docs/protocol.md) · [Backend storage](docs/streams-backend-storage.md)
|
|
132
|
+
|
|
133
|
+
## Versioned handlers (event replay with history)
|
|
134
|
+
|
|
135
|
+
Rakaia ships a subsystem for replaying a stream through handlers whose
|
|
136
|
+
*current* and *historical* versions are both kept in source. Handlers are
|
|
137
|
+
pure — they return `Effect` descriptions that an executor applies via
|
|
138
|
+
idempotent `update_or_create`, so replay can be re-run safely.
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from rakaia import Effect, register_handler, register_upcaster
|
|
142
|
+
|
|
143
|
+
@register_handler(name="mogrify", event_match="room:*:messages",
|
|
144
|
+
effective_from=0, effective_to=10_000)
|
|
145
|
+
def mogrify_v1(event):
|
|
146
|
+
return Effect(op="update_or_create", model_label="myapp.Room",
|
|
147
|
+
lookup={"id": event["room_id"]},
|
|
148
|
+
defaults={"name": event["name"]})
|
|
149
|
+
|
|
150
|
+
@register_handler(name="mogrify", event_match="room:*:messages",
|
|
151
|
+
effective_from=10_000)
|
|
152
|
+
def mogrify_v2(event): # bugfix only for events from seq 10_000 onward
|
|
153
|
+
...
|
|
154
|
+
|
|
155
|
+
@register_upcaster(event_match="room:*:messages", from_version=1)
|
|
156
|
+
def upcast_v1_to_v2(event): # schema-shape change handled separately
|
|
157
|
+
return {**event, "currency": "USD"}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`python manage.py replay room:5:messages --from 0` then runs every event
|
|
161
|
+
through its time-correct handler version, with drift detection (`--strict-drift`)
|
|
162
|
+
and dry-run (`--dry-run`) modes.
|
|
163
|
+
|
|
164
|
+
See [`docs/versioned-handlers.md`](docs/versioned-handlers.md) for the
|
|
165
|
+
full story, including a worked example based on Partisipa's submissions
|
|
166
|
+
pipeline.
|
|
167
|
+
|
|
168
|
+
## Sample applications
|
|
169
|
+
|
|
170
|
+
Each example demonstrates one feature area end-to-end. Most are standalone Django
|
|
171
|
+
projects; two are zero-dependency scripts (no Django). Run them all with
|
|
172
|
+
`just demo`, or individually:
|
|
173
|
+
|
|
174
|
+
| Example | Demonstrates | Run |
|
|
175
|
+
|---|---|---|
|
|
176
|
+
| [`examples/orders/`](examples/orders/) | Versioned handlers, upcasters, replay, dry-run | `just orders-demo` |
|
|
177
|
+
| [`examples/formkit_submissions/`](examples/formkit_submissions/) | Projections/fan-out, `reconcile_children`, migration parity | `just formkit-demo` |
|
|
178
|
+
| [`examples/protocol_streams/`](examples/protocol_streams/) | Protocol layer (no Django): producer fencing, close, `poll` cursors | `just protocol-demo` |
|
|
179
|
+
| [`examples/multi_owner/`](examples/multi_owner/) | Effect primitives (no Django): `Ref`, `reconcile_aggregate(owns=)` | `just multi-owner-demo` |
|
|
180
|
+
| [`examples/chat/`](examples/chat/) | `@stream_model`, multi-stream events, live SSE | `just dev` |
|
|
181
|
+
| [`examples/polyglot/`](examples/polyglot/) | Language-scoped streams, live-editable translations | `just polyglot-dev` |
|
|
182
|
+
|
|
183
|
+
For the full catalog with a concept-coverage matrix, see
|
|
184
|
+
[`docs/examples.md`](docs/examples.md) (human) or the machine-readable
|
|
185
|
+
[Open Knowledge Format](https://github.com/GoogleCloudPlatform/knowledge-catalog)
|
|
186
|
+
bundle in [`okf/`](okf/) (agents/tools). For a narrated walkthrough, see
|
|
187
|
+
[`docs/whats-new.md`](docs/whats-new.md).
|
|
188
|
+
|
|
189
|
+
## Running it
|
|
190
|
+
|
|
191
|
+
If you have [`just`](https://github.com/casey/just) and `podman`:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
just install
|
|
195
|
+
just dev # single-worker dev server
|
|
196
|
+
just serve # production-style: 4 hypercorn workers + Redis (podman)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`just --list` shows everything. The full guide is in
|
|
200
|
+
[`docs/deployment.md`](docs/deployment.md).
|
|
201
|
+
|
|
202
|
+
## Development
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
just install
|
|
206
|
+
just check # lint + format + types + tests + docs build
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Or by hand:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
uv sync --extra dev --extra django
|
|
213
|
+
uv run pytest
|
|
214
|
+
uv run ruff check src/
|
|
215
|
+
uv run pyright src/
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Protocol conformance
|
|
219
|
+
|
|
220
|
+
Beyond the pytest suite, rakaia is checked against the upstream, language-agnostic
|
|
221
|
+
[`@durable-streams/server-conformance-tests`](https://github.com/durable-streams/durable-streams/tree/main/packages/server-conformance-tests)
|
|
222
|
+
compliance suite:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
just conformance # starts rakaia, runs the suite against it, tears it down (needs node/npm)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
This runs in CI as a non-blocking check (`.github/workflows/conformance.yml`).
|
|
229
|
+
rakaia passes the full protocol surface today except the stream **forking**
|
|
230
|
+
family, which is not yet implemented. See [`conformance/README.md`](conformance/README.md).
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
MIT.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Rakaia
|
|
2
|
+
|
|
3
|
+
**Rakaia** is a Python implementation of the [Durable Streams protocol](docs/protocol.md) —
|
|
4
|
+
an HTTP-based protocol for append-only, ordered, durable byte streams.
|
|
5
|
+
|
|
6
|
+
It ships two installable packages:
|
|
7
|
+
|
|
8
|
+
- **`rakaia`** — A zero-dependency ASGI app implementing the protocol. Run it
|
|
9
|
+
standalone (`uvicorn`/`daphne`/`granian`) or mount it inside Django/FastAPI/Starlette.
|
|
10
|
+
- **`django_rakaia`** — A Django app with normalized stream models, a
|
|
11
|
+
`@stream_model` decorator, Channels-based SSE broadcasting, and an admin
|
|
12
|
+
interface.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install rakaia-streams # core protocol server
|
|
18
|
+
pip install "rakaia-streams[django]" # + Django integration
|
|
19
|
+
pip install "rakaia-streams[django,prod]" # + channels-redis + hypercorn for prod
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The distribution is named `rakaia-streams` on PyPI (plain `rakaia` was already
|
|
23
|
+
taken); the import names are unchanged — `import rakaia`, `import django_rakaia`.
|
|
24
|
+
|
|
25
|
+
## Quick start (standalone)
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install rakaia-streams uvicorn
|
|
29
|
+
uvicorn rakaia:app --port 4437
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from rakaia import create_app, StreamStore
|
|
34
|
+
|
|
35
|
+
app = create_app() # in-memory store
|
|
36
|
+
app = create_app(store=StreamStore())
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Quick start (Django)
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
# models.py
|
|
43
|
+
from dataclasses import dataclass
|
|
44
|
+
from django.db import models
|
|
45
|
+
from django_rakaia.decorators import stream_model
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class RoomData:
|
|
49
|
+
id: int
|
|
50
|
+
name: str
|
|
51
|
+
|
|
52
|
+
@stream_model(
|
|
53
|
+
stream_paths=lambda obj: f"room:{obj.id}:messages",
|
|
54
|
+
to_dataclass=lambda obj: RoomData(id=obj.id, name=obj.name),
|
|
55
|
+
)
|
|
56
|
+
class ChatRoom(models.Model):
|
|
57
|
+
name = models.CharField(max_length=100)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Every save/delete now emits a stream event you can subscribe to over SSE.
|
|
61
|
+
|
|
62
|
+
## Documentation
|
|
63
|
+
|
|
64
|
+
The documentation site is built with [Zensical](https://zensical.org/).
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
uv sync --extra docs
|
|
68
|
+
uv run zensical serve # live preview at http://localhost:8000
|
|
69
|
+
uv run zensical build # static build into ./site
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Pages live in [`docs/`](docs/) and the site config is in
|
|
73
|
+
[`zensical.toml`](zensical.toml):
|
|
74
|
+
|
|
75
|
+
- [Overview & quick start](docs/index.md)
|
|
76
|
+
- [**What's new — a guided tour**](docs/whats-new.md) — start here to see the recent features, each with a one-command demo
|
|
77
|
+
- [Glossary](docs/glossary.md) — plain-language definitions of the event-sourcing terms
|
|
78
|
+
- [Django integration](docs/django-integration.md)
|
|
79
|
+
- [Versioned handlers](docs/versioned-handlers.md)
|
|
80
|
+
- [Projections & fan-out](docs/projections-and-fan-out.md)
|
|
81
|
+
- [Dry-run & executors](docs/dry-run-and-executors.md)
|
|
82
|
+
- [Translations](docs/translations.md)
|
|
83
|
+
- [Deployment](docs/deployment.md)
|
|
84
|
+
- [Protocol specification](docs/protocol.md) · [Backend storage](docs/streams-backend-storage.md)
|
|
85
|
+
|
|
86
|
+
## Versioned handlers (event replay with history)
|
|
87
|
+
|
|
88
|
+
Rakaia ships a subsystem for replaying a stream through handlers whose
|
|
89
|
+
*current* and *historical* versions are both kept in source. Handlers are
|
|
90
|
+
pure — they return `Effect` descriptions that an executor applies via
|
|
91
|
+
idempotent `update_or_create`, so replay can be re-run safely.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from rakaia import Effect, register_handler, register_upcaster
|
|
95
|
+
|
|
96
|
+
@register_handler(name="mogrify", event_match="room:*:messages",
|
|
97
|
+
effective_from=0, effective_to=10_000)
|
|
98
|
+
def mogrify_v1(event):
|
|
99
|
+
return Effect(op="update_or_create", model_label="myapp.Room",
|
|
100
|
+
lookup={"id": event["room_id"]},
|
|
101
|
+
defaults={"name": event["name"]})
|
|
102
|
+
|
|
103
|
+
@register_handler(name="mogrify", event_match="room:*:messages",
|
|
104
|
+
effective_from=10_000)
|
|
105
|
+
def mogrify_v2(event): # bugfix only for events from seq 10_000 onward
|
|
106
|
+
...
|
|
107
|
+
|
|
108
|
+
@register_upcaster(event_match="room:*:messages", from_version=1)
|
|
109
|
+
def upcast_v1_to_v2(event): # schema-shape change handled separately
|
|
110
|
+
return {**event, "currency": "USD"}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`python manage.py replay room:5:messages --from 0` then runs every event
|
|
114
|
+
through its time-correct handler version, with drift detection (`--strict-drift`)
|
|
115
|
+
and dry-run (`--dry-run`) modes.
|
|
116
|
+
|
|
117
|
+
See [`docs/versioned-handlers.md`](docs/versioned-handlers.md) for the
|
|
118
|
+
full story, including a worked example based on Partisipa's submissions
|
|
119
|
+
pipeline.
|
|
120
|
+
|
|
121
|
+
## Sample applications
|
|
122
|
+
|
|
123
|
+
Each example demonstrates one feature area end-to-end. Most are standalone Django
|
|
124
|
+
projects; two are zero-dependency scripts (no Django). Run them all with
|
|
125
|
+
`just demo`, or individually:
|
|
126
|
+
|
|
127
|
+
| Example | Demonstrates | Run |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| [`examples/orders/`](examples/orders/) | Versioned handlers, upcasters, replay, dry-run | `just orders-demo` |
|
|
130
|
+
| [`examples/formkit_submissions/`](examples/formkit_submissions/) | Projections/fan-out, `reconcile_children`, migration parity | `just formkit-demo` |
|
|
131
|
+
| [`examples/protocol_streams/`](examples/protocol_streams/) | Protocol layer (no Django): producer fencing, close, `poll` cursors | `just protocol-demo` |
|
|
132
|
+
| [`examples/multi_owner/`](examples/multi_owner/) | Effect primitives (no Django): `Ref`, `reconcile_aggregate(owns=)` | `just multi-owner-demo` |
|
|
133
|
+
| [`examples/chat/`](examples/chat/) | `@stream_model`, multi-stream events, live SSE | `just dev` |
|
|
134
|
+
| [`examples/polyglot/`](examples/polyglot/) | Language-scoped streams, live-editable translations | `just polyglot-dev` |
|
|
135
|
+
|
|
136
|
+
For the full catalog with a concept-coverage matrix, see
|
|
137
|
+
[`docs/examples.md`](docs/examples.md) (human) or the machine-readable
|
|
138
|
+
[Open Knowledge Format](https://github.com/GoogleCloudPlatform/knowledge-catalog)
|
|
139
|
+
bundle in [`okf/`](okf/) (agents/tools). For a narrated walkthrough, see
|
|
140
|
+
[`docs/whats-new.md`](docs/whats-new.md).
|
|
141
|
+
|
|
142
|
+
## Running it
|
|
143
|
+
|
|
144
|
+
If you have [`just`](https://github.com/casey/just) and `podman`:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
just install
|
|
148
|
+
just dev # single-worker dev server
|
|
149
|
+
just serve # production-style: 4 hypercorn workers + Redis (podman)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`just --list` shows everything. The full guide is in
|
|
153
|
+
[`docs/deployment.md`](docs/deployment.md).
|
|
154
|
+
|
|
155
|
+
## Development
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
just install
|
|
159
|
+
just check # lint + format + types + tests + docs build
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Or by hand:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
uv sync --extra dev --extra django
|
|
166
|
+
uv run pytest
|
|
167
|
+
uv run ruff check src/
|
|
168
|
+
uv run pyright src/
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Protocol conformance
|
|
172
|
+
|
|
173
|
+
Beyond the pytest suite, rakaia is checked against the upstream, language-agnostic
|
|
174
|
+
[`@durable-streams/server-conformance-tests`](https://github.com/durable-streams/durable-streams/tree/main/packages/server-conformance-tests)
|
|
175
|
+
compliance suite:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
just conformance # starts rakaia, runs the suite against it, tears it down (needs node/npm)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
This runs in CI as a non-blocking check (`.github/workflows/conformance.yml`).
|
|
182
|
+
rakaia passes the full protocol surface today except the stream **forking**
|
|
183
|
+
family, which is not yet implemented. See [`conformance/README.md`](conformance/README.md).
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
MIT.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
# Distribution name is `rakaia-streams` because `rakaia` is already taken on
|
|
3
|
+
# PyPI by an unrelated placeholder. The import name is unaffected:
|
|
4
|
+
# pip install rakaia-streams -> import rakaia
|
|
5
|
+
name = "rakaia-streams"
|
|
6
|
+
version = "0.1.0"
|
|
7
|
+
description = "Python server implementation of the Durable Streams protocol"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [{ name = "Josh Brooks" }]
|
|
12
|
+
keywords = ["durable-streams", "streaming", "http", "sse", "asgi"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
23
|
+
"Typing :: Typed",
|
|
24
|
+
]
|
|
25
|
+
# Zero runtime dependencies - pure ASGI, stdlib only
|
|
26
|
+
dependencies = []
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/joshbrooks/rakaia"
|
|
30
|
+
Repository = "https://github.com/joshbrooks/rakaia"
|
|
31
|
+
Issues = "https://github.com/joshbrooks/rakaia/issues"
|
|
32
|
+
Changelog = "https://github.com/joshbrooks/rakaia/blob/main/CHANGELOG.md"
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
django = [
|
|
36
|
+
"django>=4.2.0",
|
|
37
|
+
"daphne>=4.2.1",
|
|
38
|
+
"channels>=4.0",
|
|
39
|
+
]
|
|
40
|
+
prod = [
|
|
41
|
+
"channels-redis>=4.1.0",
|
|
42
|
+
"hypercorn>=0.17.0",
|
|
43
|
+
"whitenoise>=6.0",
|
|
44
|
+
]
|
|
45
|
+
dev = [
|
|
46
|
+
"ruff>=0.8.0",
|
|
47
|
+
"pyright>=1.1.400",
|
|
48
|
+
"pytest>=8.0.0",
|
|
49
|
+
"pytest-asyncio>=0.24.0",
|
|
50
|
+
"pytest-cov>=6.0.0",
|
|
51
|
+
"httpx>=0.27.0",
|
|
52
|
+
"uvicorn>=0.30.0",
|
|
53
|
+
"django>=4.2",
|
|
54
|
+
"django-types>=0.23.0",
|
|
55
|
+
"pytest-django>=4.9.0",
|
|
56
|
+
]
|
|
57
|
+
docs = [
|
|
58
|
+
"zensical>=0.0.51",
|
|
59
|
+
"markdown>=3.10.2",
|
|
60
|
+
"pymdown-extensions>=11.0.1",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[build-system]
|
|
64
|
+
requires = ["hatchling"]
|
|
65
|
+
build-backend = "hatchling.build"
|
|
66
|
+
|
|
67
|
+
[tool.hatch.build.targets.wheel]
|
|
68
|
+
packages = ["src/rakaia", "src/django_rakaia"]
|
|
69
|
+
|
|
70
|
+
[tool.hatch.build.targets.sdist]
|
|
71
|
+
include = [
|
|
72
|
+
"/src",
|
|
73
|
+
"/tests",
|
|
74
|
+
"/README.md",
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
[tool.ruff]
|
|
78
|
+
line-length = 88
|
|
79
|
+
target-version = "py310"
|
|
80
|
+
|
|
81
|
+
[tool.ruff.lint]
|
|
82
|
+
select = [
|
|
83
|
+
"E", # pycodestyle errors
|
|
84
|
+
"W", # pycodestyle warnings
|
|
85
|
+
"F", # pyflakes
|
|
86
|
+
"I", # isort
|
|
87
|
+
"B", # flake8-bugbear
|
|
88
|
+
"C4", # flake8-comprehensions
|
|
89
|
+
"UP", # pyupgrade
|
|
90
|
+
"ARG", # flake8-unused-arguments
|
|
91
|
+
"SIM", # flake8-simplify
|
|
92
|
+
]
|
|
93
|
+
ignore = [
|
|
94
|
+
"E501", # line too long (handled by formatter)
|
|
95
|
+
"B008", # do not perform function calls in argument defaults
|
|
96
|
+
"B904", # raise without from in except
|
|
97
|
+
"SIM108", # use ternary operator
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
[tool.ruff.lint.isort]
|
|
101
|
+
known-first-party = ["rakaia", "django_rakaia"]
|
|
102
|
+
|
|
103
|
+
[tool.pyright]
|
|
104
|
+
pythonVersion = "3.10"
|
|
105
|
+
# `standard` (the pyright default) catches genuine type errors but is
|
|
106
|
+
# tolerant of Django ORM dynamism that pyright can't introspect. Set this
|
|
107
|
+
# to `strict` if you want to enforce full annotation coverage on the core
|
|
108
|
+
# rakaia package.
|
|
109
|
+
typeCheckingMode = "standard"
|
|
110
|
+
include = ["src"]
|
|
111
|
+
exclude = ["tests"]
|
|
112
|
+
|
|
113
|
+
[tool.pytest.ini_options]
|
|
114
|
+
testpaths = ["tests"]
|
|
115
|
+
asyncio_mode = "auto"
|
|
116
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
117
|
+
DJANGO_SETTINGS_MODULE = "tests.test_django_rakaia.settings"
|
|
118
|
+
pythonpath = [".", "src"]
|
|
119
|
+
|
|
120
|
+
[dependency-groups]
|
|
121
|
+
dev = [
|
|
122
|
+
"django-extensions>=4.1",
|
|
123
|
+
]
|
|
124
|
+
|