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.
Files changed (133) hide show
  1. rakaia_streams-0.1.0/.gitignore +56 -0
  2. rakaia_streams-0.1.0/PKG-INFO +234 -0
  3. rakaia_streams-0.1.0/README.md +187 -0
  4. rakaia_streams-0.1.0/pyproject.toml +124 -0
  5. rakaia_streams-0.1.0/src/django_rakaia/PROTOCOL_API.md +356 -0
  6. rakaia_streams-0.1.0/src/django_rakaia/__init__.py +44 -0
  7. rakaia_streams-0.1.0/src/django_rakaia/admin.py +298 -0
  8. rakaia_streams-0.1.0/src/django_rakaia/apps.py +42 -0
  9. rakaia_streams-0.1.0/src/django_rakaia/channels_signals.py +93 -0
  10. rakaia_streams-0.1.0/src/django_rakaia/channels_views.py +163 -0
  11. rakaia_streams-0.1.0/src/django_rakaia/decorators.py +266 -0
  12. rakaia_streams-0.1.0/src/django_rakaia/django_store.py +251 -0
  13. rakaia_streams-0.1.0/src/django_rakaia/effect_executor.py +216 -0
  14. rakaia_streams-0.1.0/src/django_rakaia/handlers_autodiscover.py +18 -0
  15. rakaia_streams-0.1.0/src/django_rakaia/hermeticity.py +91 -0
  16. rakaia_streams-0.1.0/src/django_rakaia/history.py +60 -0
  17. rakaia_streams-0.1.0/src/django_rakaia/integration.py +21 -0
  18. rakaia_streams-0.1.0/src/django_rakaia/management/__init__.py +0 -0
  19. rakaia_streams-0.1.0/src/django_rakaia/management/commands/__init__.py +0 -0
  20. rakaia_streams-0.1.0/src/django_rakaia/management/commands/replay.py +92 -0
  21. rakaia_streams-0.1.0/src/django_rakaia/middleware.py +48 -0
  22. rakaia_streams-0.1.0/src/django_rakaia/migrations/0001_initial.py +171 -0
  23. rakaia_streams-0.1.0/src/django_rakaia/migrations/0002_streamevent_metadata.py +17 -0
  24. rakaia_streams-0.1.0/src/django_rakaia/migrations/0003_consumercursor.py +32 -0
  25. rakaia_streams-0.1.0/src/django_rakaia/migrations/0004_streamevent_event_ts.py +17 -0
  26. rakaia_streams-0.1.0/src/django_rakaia/migrations/0005_streamoffsetwatermark.py +25 -0
  27. rakaia_streams-0.1.0/src/django_rakaia/migrations/0006_alter_streamevent_data_alter_streamevent_metadata.py +29 -0
  28. rakaia_streams-0.1.0/src/django_rakaia/migrations/__init__.py +0 -0
  29. rakaia_streams-0.1.0/src/django_rakaia/models.py +372 -0
  30. rakaia_streams-0.1.0/src/django_rakaia/projection_reader.py +53 -0
  31. rakaia_streams-0.1.0/src/django_rakaia/protocol_views.py +502 -0
  32. rakaia_streams-0.1.0/src/django_rakaia/py.typed +0 -0
  33. rakaia_streams-0.1.0/src/django_rakaia/replay.py +64 -0
  34. rakaia_streams-0.1.0/src/django_rakaia/store.py +37 -0
  35. rakaia_streams-0.1.0/src/django_rakaia/streams.py +156 -0
  36. rakaia_streams-0.1.0/src/django_rakaia/subscription.py +46 -0
  37. rakaia_streams-0.1.0/src/django_rakaia/templates/django_rakaia/_activity_item.html +1 -0
  38. rakaia_streams-0.1.0/src/django_rakaia/templates/django_rakaia/_translation_rows.html +11 -0
  39. rakaia_streams-0.1.0/src/django_rakaia/templates/django_rakaia/base.html +235 -0
  40. rakaia_streams-0.1.0/src/django_rakaia/templates/django_rakaia/stream_detail.html +522 -0
  41. rakaia_streams-0.1.0/src/django_rakaia/templates/django_rakaia/streams_index.html +137 -0
  42. rakaia_streams-0.1.0/src/django_rakaia/templates/django_rakaia/translations_index.html +167 -0
  43. rakaia_streams-0.1.0/src/django_rakaia/urls.py +74 -0
  44. rakaia_streams-0.1.0/src/django_rakaia/verification.py +417 -0
  45. rakaia_streams-0.1.0/src/django_rakaia/views.py +474 -0
  46. rakaia_streams-0.1.0/src/rakaia/__init__.py +197 -0
  47. rakaia_streams-0.1.0/src/rakaia/__main__.py +43 -0
  48. rakaia_streams-0.1.0/src/rakaia/_asgi.py +159 -0
  49. rakaia_streams-0.1.0/src/rakaia/append.py +89 -0
  50. rakaia_streams-0.1.0/src/rakaia/context.py +63 -0
  51. rakaia_streams-0.1.0/src/rakaia/cursor.py +84 -0
  52. rakaia_streams-0.1.0/src/rakaia/effects.py +417 -0
  53. rakaia_streams-0.1.0/src/rakaia/executors.py +36 -0
  54. rakaia_streams-0.1.0/src/rakaia/handler.py +1234 -0
  55. rakaia_streams-0.1.0/src/rakaia/history.py +139 -0
  56. rakaia_streams-0.1.0/src/rakaia/json_mode.py +94 -0
  57. rakaia_streams-0.1.0/src/rakaia/projections.py +476 -0
  58. rakaia_streams-0.1.0/src/rakaia/protocols.py +164 -0
  59. rakaia_streams-0.1.0/src/rakaia/py.typed +0 -0
  60. rakaia_streams-0.1.0/src/rakaia/registry.py +1180 -0
  61. rakaia_streams-0.1.0/src/rakaia/replay.py +605 -0
  62. rakaia_streams-0.1.0/src/rakaia/source_hash.py +33 -0
  63. rakaia_streams-0.1.0/src/rakaia/store.py +746 -0
  64. rakaia_streams-0.1.0/src/rakaia/subscription.py +142 -0
  65. rakaia_streams-0.1.0/src/rakaia/types.py +286 -0
  66. rakaia_streams-0.1.0/tests/__init__.py +0 -0
  67. rakaia_streams-0.1.0/tests/store_contract.py +217 -0
  68. rakaia_streams-0.1.0/tests/test_django_rakaia/__init__.py +0 -0
  69. rakaia_streams-0.1.0/tests/test_django_rakaia/apps.py +6 -0
  70. rakaia_streams-0.1.0/tests/test_django_rakaia/asgi.py +7 -0
  71. rakaia_streams-0.1.0/tests/test_django_rakaia/dev_settings.py +25 -0
  72. rakaia_streams-0.1.0/tests/test_django_rakaia/handlers.py +27 -0
  73. rakaia_streams-0.1.0/tests/test_django_rakaia/migrations/0001_initial.py +116 -0
  74. rakaia_streams-0.1.0/tests/test_django_rakaia/migrations/0002_history.py +35 -0
  75. rakaia_streams-0.1.0/tests/test_django_rakaia/migrations/0003_alert.py +42 -0
  76. rakaia_streams-0.1.0/tests/test_django_rakaia/migrations/0004_alert_dismissed_version.py +15 -0
  77. rakaia_streams-0.1.0/tests/test_django_rakaia/migrations/0005_measure.py +29 -0
  78. rakaia_streams-0.1.0/tests/test_django_rakaia/migrations/0006_sukuprojection.py +30 -0
  79. rakaia_streams-0.1.0/tests/test_django_rakaia/migrations/0007_archiveddoc_softdeletedoc.py +44 -0
  80. rakaia_streams-0.1.0/tests/test_django_rakaia/migrations/__init__.py +0 -0
  81. rakaia_streams-0.1.0/tests/test_django_rakaia/models.py +393 -0
  82. rakaia_streams-0.1.0/tests/test_django_rakaia/settings.py +55 -0
  83. rakaia_streams-0.1.0/tests/test_django_rakaia/test_admin.py +142 -0
  84. rakaia_streams-0.1.0/tests/test_django_rakaia/test_alerts.py +716 -0
  85. rakaia_streams-0.1.0/tests/test_django_rakaia/test_apps.py +57 -0
  86. rakaia_streams-0.1.0/tests/test_django_rakaia/test_autodiscover.py +33 -0
  87. rakaia_streams-0.1.0/tests/test_django_rakaia/test_channels.py +297 -0
  88. rakaia_streams-0.1.0/tests/test_django_rakaia/test_decorators.py +358 -0
  89. rakaia_streams-0.1.0/tests/test_django_rakaia/test_decorators_consumer_contract.py +396 -0
  90. rakaia_streams-0.1.0/tests/test_django_rakaia/test_django_store.py +398 -0
  91. rakaia_streams-0.1.0/tests/test_django_rakaia/test_effect_executor.py +517 -0
  92. rakaia_streams-0.1.0/tests/test_django_rakaia/test_hermeticity.py +126 -0
  93. rakaia_streams-0.1.0/tests/test_django_rakaia/test_history_materialization.py +121 -0
  94. rakaia_streams-0.1.0/tests/test_django_rakaia/test_history_materializer.py +86 -0
  95. rakaia_streams-0.1.0/tests/test_django_rakaia/test_middleware.py +39 -0
  96. rakaia_streams-0.1.0/tests/test_django_rakaia/test_model_stream_reader.py +259 -0
  97. rakaia_streams-0.1.0/tests/test_django_rakaia/test_preloaded_reader.py +170 -0
  98. rakaia_streams-0.1.0/tests/test_django_rakaia/test_projection_reader.py +281 -0
  99. rakaia_streams-0.1.0/tests/test_django_rakaia/test_protocol.py +574 -0
  100. rakaia_streams-0.1.0/tests/test_django_rakaia/test_refs.py +127 -0
  101. rakaia_streams-0.1.0/tests/test_django_rakaia/test_replay_command.py +139 -0
  102. rakaia_streams-0.1.0/tests/test_django_rakaia/test_replay_helper.py +83 -0
  103. rakaia_streams-0.1.0/tests/test_django_rakaia/test_store_contract.py +19 -0
  104. rakaia_streams-0.1.0/tests/test_django_rakaia/test_subscription.py +135 -0
  105. rakaia_streams-0.1.0/tests/test_django_rakaia/test_using_seam.py +162 -0
  106. rakaia_streams-0.1.0/tests/test_django_rakaia/test_verification.py +257 -0
  107. rakaia_streams-0.1.0/tests/test_django_rakaia/test_views.py +258 -0
  108. rakaia_streams-0.1.0/tests/test_django_rakaia/upcasters.py +17 -0
  109. rakaia_streams-0.1.0/tests/test_django_rakaia/urls.py +12 -0
  110. rakaia_streams-0.1.0/tests/test_rakaia/__init__.py +0 -0
  111. rakaia_streams-0.1.0/tests/test_rakaia/conftest.py +22 -0
  112. rakaia_streams-0.1.0/tests/test_rakaia/test_alerts.py +139 -0
  113. rakaia_streams-0.1.0/tests/test_rakaia/test_append.py +85 -0
  114. rakaia_streams-0.1.0/tests/test_rakaia/test_asgi_helpers.py +183 -0
  115. rakaia_streams-0.1.0/tests/test_rakaia/test_context.py +56 -0
  116. rakaia_streams-0.1.0/tests/test_rakaia/test_cursor.py +74 -0
  117. rakaia_streams-0.1.0/tests/test_rakaia/test_effects.py +529 -0
  118. rakaia_streams-0.1.0/tests/test_rakaia/test_executors.py +64 -0
  119. rakaia_streams-0.1.0/tests/test_rakaia/test_handler.py +385 -0
  120. rakaia_streams-0.1.0/tests/test_rakaia/test_history.py +148 -0
  121. rakaia_streams-0.1.0/tests/test_rakaia/test_json_mode.py +104 -0
  122. rakaia_streams-0.1.0/tests/test_rakaia/test_offset_patterns.py +61 -0
  123. rakaia_streams-0.1.0/tests/test_rakaia/test_projections.py +549 -0
  124. rakaia_streams-0.1.0/tests/test_rakaia/test_refs.py +143 -0
  125. rakaia_streams-0.1.0/tests/test_rakaia/test_registry.py +404 -0
  126. rakaia_streams-0.1.0/tests/test_rakaia/test_registry_isolation.py +120 -0
  127. rakaia_streams-0.1.0/tests/test_rakaia/test_registry_persistence.py +296 -0
  128. rakaia_streams-0.1.0/tests/test_rakaia/test_replay.py +1401 -0
  129. rakaia_streams-0.1.0/tests/test_rakaia/test_source_hash.py +48 -0
  130. rakaia_streams-0.1.0/tests/test_rakaia/test_store.py +711 -0
  131. rakaia_streams-0.1.0/tests/test_rakaia/test_store_contract.py +14 -0
  132. rakaia_streams-0.1.0/tests/test_rakaia/test_subscription.py +123 -0
  133. 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
+