golit 1.0.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 (230) hide show
  1. golit-1.0.0/.dockerignore +15 -0
  2. golit-1.0.0/.github/workflows/ci.yml +58 -0
  3. golit-1.0.0/.github/workflows/release.yml +82 -0
  4. golit-1.0.0/.gitignore +23 -0
  5. golit-1.0.0/.python-version +1 -0
  6. golit-1.0.0/CHANGELOG.md +75 -0
  7. golit-1.0.0/Cargo.lock +212 -0
  8. golit-1.0.0/Cargo.toml +24 -0
  9. golit-1.0.0/DEPLOYMENT.md +163 -0
  10. golit-1.0.0/LICENSE +202 -0
  11. golit-1.0.0/Makefile +89 -0
  12. golit-1.0.0/PKG-INFO +407 -0
  13. golit-1.0.0/README.md +349 -0
  14. golit-1.0.0/bench/README.md +469 -0
  15. golit-1.0.0/bench/__init__.py +5 -0
  16. golit-1.0.0/bench/apps/__init__.py +1 -0
  17. golit-1.0.0/bench/apps/dash_app.py +122 -0
  18. golit-1.0.0/bench/apps/dash_memo.py +46 -0
  19. golit-1.0.0/bench/apps/dash_memo_server.py +33 -0
  20. golit-1.0.0/bench/apps/dash_server.py +45 -0
  21. golit-1.0.0/bench/apps/marimo_gen.py +108 -0
  22. golit-1.0.0/bench/apps/streamlit_app.py +63 -0
  23. golit-1.0.0/bench/gen_app.py +222 -0
  24. golit-1.0.0/bench/http/__init__.py +7 -0
  25. golit-1.0.0/bench/http/drive.py +108 -0
  26. golit-1.0.0/bench/http/load.py +115 -0
  27. golit-1.0.0/bench/http/run_b1_http.py +105 -0
  28. golit-1.0.0/bench/http/run_b2.py +168 -0
  29. golit-1.0.0/bench/http/run_b2_push.py +245 -0
  30. golit-1.0.0/bench/http/serve.py +31 -0
  31. golit-1.0.0/bench/http/serve_memo.py +21 -0
  32. golit-1.0.0/bench/http/serverctl.py +70 -0
  33. golit-1.0.0/bench/instrument.py +127 -0
  34. golit-1.0.0/bench/plot.py +424 -0
  35. golit-1.0.0/bench/results/.gitignore +3 -0
  36. golit-1.0.0/bench/results/b1.csv +131 -0
  37. golit-1.0.0/bench/results/b1_compare_hero.svg +513 -0
  38. golit-1.0.0/bench/results/b1_dash.csv +19 -0
  39. golit-1.0.0/bench/results/b1_dash_bytes.csv +3 -0
  40. golit-1.0.0/bench/results/b1_dash_crossover.svg +427 -0
  41. golit-1.0.0/bench/results/b1_dash_http.csv +5 -0
  42. golit-1.0.0/bench/results/b1_dash_http.svg +371 -0
  43. golit-1.0.0/bench/results/b1_dash_render.csv +4 -0
  44. golit-1.0.0/bench/results/b1_dash_render.svg +363 -0
  45. golit-1.0.0/bench/results/b1_hero.svg +480 -0
  46. golit-1.0.0/bench/results/b1_http.csv +13 -0
  47. golit-1.0.0/bench/results/b1_http_hero.svg +479 -0
  48. golit-1.0.0/bench/results/b1_marimo.csv +19 -0
  49. golit-1.0.0/bench/results/b1_streamlit.csv +19 -0
  50. golit-1.0.0/bench/results/b2.csv +11 -0
  51. golit-1.0.0/bench/results/b2_push.csv +7 -0
  52. golit-1.0.0/bench/results/b2_saturation.svg +441 -0
  53. golit-1.0.0/bench/results/b2_scaling.svg +433 -0
  54. golit-1.0.0/bench/results/b_memo.csv +13 -0
  55. golit-1.0.0/bench/results/b_memo_http.csv +9 -0
  56. golit-1.0.0/bench/run_b1.py +156 -0
  57. golit-1.0.0/bench/run_b1_dash.py +321 -0
  58. golit-1.0.0/bench/run_b1_dash_http.py +156 -0
  59. golit-1.0.0/bench/run_b1_marimo.py +224 -0
  60. golit-1.0.0/bench/run_b1_streamlit.py +136 -0
  61. golit-1.0.0/bench/run_memo.py +171 -0
  62. golit-1.0.0/bench/run_memo_http.py +140 -0
  63. golit-1.0.0/deploy/Dockerfile +37 -0
  64. golit-1.0.0/deploy/docker-compose.yml +52 -0
  65. golit-1.0.0/deploy/nginx.conf +36 -0
  66. golit-1.0.0/deploy/scaling_demo/app.py +58 -0
  67. golit-1.0.0/deploy/verify_scaling.py +115 -0
  68. golit-1.0.0/docs/about/benchmarks.md +89 -0
  69. golit-1.0.0/docs/about/comparison.md +51 -0
  70. golit-1.0.0/docs/about/contributing.md +87 -0
  71. golit-1.0.0/docs/about/faq.md +53 -0
  72. golit-1.0.0/docs/advanced/audio.md +105 -0
  73. golit-1.0.0/docs/advanced/custom-rendering.md +72 -0
  74. golit-1.0.0/docs/advanced/deployment.md +97 -0
  75. golit-1.0.0/docs/advanced/index.md +34 -0
  76. golit-1.0.0/docs/advanced/live-sources.md +76 -0
  77. golit-1.0.0/docs/advanced/security.md +82 -0
  78. golit-1.0.0/docs/advanced/server-push.md +93 -0
  79. golit-1.0.0/docs/advanced/sessions.md +46 -0
  80. golit-1.0.0/docs/advanced/video-streams.md +211 -0
  81. golit-1.0.0/docs/advanced/websockets.md +148 -0
  82. golit-1.0.0/docs/concepts/architecture.md +68 -0
  83. golit-1.0.0/docs/concepts/data-flow.md +78 -0
  84. golit-1.0.0/docs/concepts/index.md +30 -0
  85. golit-1.0.0/docs/concepts/reactivity.md +85 -0
  86. golit-1.0.0/docs/index.md +171 -0
  87. golit-1.0.0/docs/install.md +76 -0
  88. golit-1.0.0/docs/reference/app.md +23 -0
  89. golit-1.0.0/docs/reference/charts.md +27 -0
  90. golit-1.0.0/docs/reference/data.md +9 -0
  91. golit-1.0.0/docs/reference/gis.md +82 -0
  92. golit-1.0.0/docs/reference/index.md +59 -0
  93. golit-1.0.0/docs/reference/layout.md +12 -0
  94. golit-1.0.0/docs/reference/server.md +47 -0
  95. golit-1.0.0/docs/reference/ui.md +9 -0
  96. golit-1.0.0/docs/reference/widgets.md +11 -0
  97. golit-1.0.0/docs/stylesheets/extra.css +52 -0
  98. golit-1.0.0/docs/tutorial/charts.md +135 -0
  99. golit-1.0.0/docs/tutorial/first-app.md +63 -0
  100. golit-1.0.0/docs/tutorial/index.md +37 -0
  101. golit-1.0.0/docs/tutorial/inputs.md +190 -0
  102. golit-1.0.0/docs/tutorial/layout.md +84 -0
  103. golit-1.0.0/docs/tutorial/maps.md +336 -0
  104. golit-1.0.0/docs/tutorial/running.md +148 -0
  105. golit-1.0.0/docs/tutorial/sql.md +71 -0
  106. golit-1.0.0/docs/tutorial/the-graph.md +118 -0
  107. golit-1.0.0/docs/tutorial/ui-components.md +110 -0
  108. golit-1.0.0/docs/tutorial/views.md +107 -0
  109. golit-1.0.0/examples/audio_recorder/app.py +100 -0
  110. golit-1.0.0/examples/browser_camera/app.py +73 -0
  111. golit-1.0.0/examples/charts_gallery/app.py +110 -0
  112. golit-1.0.0/examples/chat/app.py +36 -0
  113. golit-1.0.0/examples/components_gallery/app.py +126 -0
  114. golit-1.0.0/examples/duckdb_sql/app.py +66 -0
  115. golit-1.0.0/examples/earth_engine/app.py +54 -0
  116. golit-1.0.0/examples/face_detect/app.py +59 -0
  117. golit-1.0.0/examples/geo_explorer/app.py +85 -0
  118. golit-1.0.0/examples/geo_explorer/districts.geojson +1085 -0
  119. golit-1.0.0/examples/great_tables/app.py +53 -0
  120. golit-1.0.0/examples/live_great_table/app.py +103 -0
  121. golit-1.0.0/examples/live_sheets/app.py +129 -0
  122. golit-1.0.0/examples/modular/README.md +58 -0
  123. golit-1.0.0/examples/modular/_app.py +13 -0
  124. golit-1.0.0/examples/modular/app.py +32 -0
  125. golit-1.0.0/examples/modular/reactives.py +37 -0
  126. golit-1.0.0/examples/modular/sources.py +24 -0
  127. golit-1.0.0/examples/modular/views.py +35 -0
  128. golit-1.0.0/examples/raster_explorer/app.py +77 -0
  129. golit-1.0.0/examples/rgb_composite/app.py +89 -0
  130. golit-1.0.0/examples/sales_explorer/app.py +94 -0
  131. golit-1.0.0/examples/terrain_analysis/app.py +82 -0
  132. golit-1.0.0/examples/tiled_raster/app.py +87 -0
  133. golit-1.0.0/examples/vector_tiles/app.py +78 -0
  134. golit-1.0.0/examples/webcam_stream/app.py +98 -0
  135. golit-1.0.0/golit_benchmark.md +92 -0
  136. golit-1.0.0/golit_pages/golit_api_reference/code.html +397 -0
  137. golit-1.0.0/golit_pages/golit_api_reference/screen.png +0 -0
  138. golit-1.0.0/golit_pages/golit_app_dashboard_preview/code.html +343 -0
  139. golit-1.0.0/golit_pages/golit_app_dashboard_preview/screen.png +0 -0
  140. golit-1.0.0/golit_pages/golit_architecture_scaling/code.html +355 -0
  141. golit-1.0.0/golit_pages/golit_architecture_scaling/screen.png +0 -0
  142. golit-1.0.0/golit_pages/golit_benchmark_methodology_document.md +32 -0
  143. golit-1.0.0/golit_pages/golit_benchmarks_performance/code.html +455 -0
  144. golit-1.0.0/golit_pages/golit_benchmarks_performance/screen.png +0 -0
  145. golit-1.0.0/golit_pages/golit_component_gallery/code.html +369 -0
  146. golit-1.0.0/golit_pages/golit_component_gallery/screen.png +0 -0
  147. golit-1.0.0/golit_pages/golit_contributing_community/code.html +351 -0
  148. golit-1.0.0/golit_pages/golit_contributing_community/screen.png +0 -0
  149. golit-1.0.0/golit_pages/golit_dag_graph_explorer/code.html +345 -0
  150. golit-1.0.0/golit_pages/golit_dag_graph_explorer/screen.png +0 -0
  151. golit-1.0.0/golit_pages/golit_deployment_scaling_manager/code.html +412 -0
  152. golit-1.0.0/golit_pages/golit_deployment_scaling_manager/screen.png +0 -0
  153. golit-1.0.0/golit_pages/golit_documentation_hub/code.html +308 -0
  154. golit-1.0.0/golit_pages/golit_documentation_hub/screen.png +0 -0
  155. golit-1.0.0/golit_pages/golit_error_boundary_logs/code.html +344 -0
  156. golit-1.0.0/golit_pages/golit_error_boundary_logs/screen.png +0 -0
  157. golit-1.0.0/golit_pages/golit_getting_started/code.html +304 -0
  158. golit-1.0.0/golit_pages/golit_getting_started/screen.png +0 -0
  159. golit-1.0.0/golit_pages/golit_home/code.html +348 -0
  160. golit-1.0.0/golit_pages/golit_home/screen.png +0 -0
  161. golit-1.0.0/golit_pages/golit_logic/DESIGN.md +88 -0
  162. golit-1.0.0/golit_pages/golit_login_auth_portal/code.html +194 -0
  163. golit-1.0.0/golit_pages/golit_login_auth_portal/screen.png +0 -0
  164. golit-1.0.0/golit_pages/golit_project_roadmap/code.html +328 -0
  165. golit-1.0.0/golit_pages/golit_project_roadmap/screen.png +0 -0
  166. golit-1.0.0/mkdocs.yml +156 -0
  167. golit-1.0.0/project_scope.md +213 -0
  168. golit-1.0.0/pyproject.toml +136 -0
  169. golit-1.0.0/python/golit/__init__.py +84 -0
  170. golit-1.0.0/python/golit/__main__.py +8 -0
  171. golit-1.0.0/python/golit/_golit.pyi +70 -0
  172. golit-1.0.0/python/golit/_loader.py +21 -0
  173. golit-1.0.0/python/golit/app.py +316 -0
  174. golit-1.0.0/python/golit/charts.py +18 -0
  175. golit-1.0.0/python/golit/cli.py +90 -0
  176. golit-1.0.0/python/golit/data.py +95 -0
  177. golit-1.0.0/python/golit/engine.py +123 -0
  178. golit-1.0.0/python/golit/gis.py +1169 -0
  179. golit-1.0.0/python/golit/hashing.py +96 -0
  180. golit-1.0.0/python/golit/layout.py +197 -0
  181. golit-1.0.0/python/golit/nodes.py +62 -0
  182. golit-1.0.0/python/golit/py.typed +0 -0
  183. golit-1.0.0/python/golit/registry.py +43 -0
  184. golit-1.0.0/python/golit/rendering/__init__.py +20 -0
  185. golit-1.0.0/python/golit/rendering/charts.py +37 -0
  186. golit-1.0.0/python/golit/rendering/html.py +716 -0
  187. golit-1.0.0/python/golit/rendering/interactive.py +126 -0
  188. golit-1.0.0/python/golit/rendering/protocol.py +173 -0
  189. golit-1.0.0/python/golit/server/__init__.py +30 -0
  190. golit-1.0.0/python/golit/server/audio.py +70 -0
  191. golit-1.0.0/python/golit/server/chat.py +168 -0
  192. golit-1.0.0/python/golit/server/factory.py +119 -0
  193. golit-1.0.0/python/golit/server/polling.py +87 -0
  194. golit-1.0.0/python/golit/server/processing.py +86 -0
  195. golit-1.0.0/python/golit/server/pubsub.py +53 -0
  196. golit-1.0.0/python/golit/server/redis_pubsub.py +84 -0
  197. golit-1.0.0/python/golit/server/routes.py +163 -0
  198. golit-1.0.0/python/golit/server/session.py +214 -0
  199. golit-1.0.0/python/golit/server/session_store.py +105 -0
  200. golit-1.0.0/python/golit/server/sse.py +92 -0
  201. golit-1.0.0/python/golit/server/streaming.py +216 -0
  202. golit-1.0.0/python/golit/server/tiles.py +84 -0
  203. golit-1.0.0/python/golit/server/vector_tiles.py +124 -0
  204. golit-1.0.0/python/golit/ui.py +794 -0
  205. golit-1.0.0/python/golit/widgets.py +517 -0
  206. golit-1.0.0/src/core.rs +804 -0
  207. golit-1.0.0/src/lib.rs +172 -0
  208. golit-1.0.0/tests/test_audio.py +132 -0
  209. golit-1.0.0/tests/test_chat.py +125 -0
  210. golit-1.0.0/tests/test_duckdb.py +70 -0
  211. golit-1.0.0/tests/test_engine.py +165 -0
  212. golit-1.0.0/tests/test_gis.py +481 -0
  213. golit-1.0.0/tests/test_gis_ee.py +94 -0
  214. golit-1.0.0/tests/test_gis_terrain.py +86 -0
  215. golit-1.0.0/tests/test_gis_tiles.py +132 -0
  216. golit-1.0.0/tests/test_gis_vector_tiles.py +157 -0
  217. golit-1.0.0/tests/test_great_tables.py +50 -0
  218. golit-1.0.0/tests/test_interactive.py +118 -0
  219. golit-1.0.0/tests/test_layout.py +140 -0
  220. golit-1.0.0/tests/test_polling.py +94 -0
  221. golit-1.0.0/tests/test_processing.py +156 -0
  222. golit-1.0.0/tests/test_redis_pubsub.py +64 -0
  223. golit-1.0.0/tests/test_rendering.py +61 -0
  224. golit-1.0.0/tests/test_server.py +85 -0
  225. golit-1.0.0/tests/test_session_store.py +101 -0
  226. golit-1.0.0/tests/test_sse.py +100 -0
  227. golit-1.0.0/tests/test_streaming.py +231 -0
  228. golit-1.0.0/tests/test_ui.py +143 -0
  229. golit-1.0.0/tests/test_widgets.py +77 -0
  230. golit-1.0.0/uv.lock +4856 -0
@@ -0,0 +1,15 @@
1
+ # Keep the build context lean — the Dockerfile only needs pyproject/Cargo,
2
+ # src/, python/, examples/, and README. Everything below is build output,
3
+ # caches, or design assets the image doesn't use.
4
+ .git
5
+ target
6
+ .venv
7
+ golit_pages
8
+ tests
9
+ **/__pycache__
10
+ *.pyc
11
+ .pytest_cache
12
+ .ruff_cache
13
+ .mypy_cache
14
+ *.whl
15
+ .DS_Store
@@ -0,0 +1,58 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ # Cancel superseded runs on the same ref.
9
+ concurrency:
10
+ group: ci-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ rust:
15
+ name: rust (fmt · clippy · test)
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: actions/setup-python@v5
20
+ with:
21
+ python-version: "3.11" # pyo3 needs an interpreter to configure + link
22
+ - uses: dtolnay/rust-toolchain@stable
23
+ with:
24
+ components: rustfmt, clippy
25
+ - uses: Swatinem/rust-cache@v2
26
+ - run: cargo fmt --all --check
27
+ - run: cargo clippy --all-targets -- -D warnings
28
+ - run: cargo test
29
+
30
+ python:
31
+ name: py${{ matrix.python-version }} (lint · type · test)
32
+ runs-on: ubuntu-latest
33
+ strategy:
34
+ fail-fast: false
35
+ matrix:
36
+ python-version: ["3.11", "3.12"]
37
+ steps:
38
+ - uses: actions/checkout@v4
39
+ - uses: dtolnay/rust-toolchain@stable
40
+ - uses: Swatinem/rust-cache@v2
41
+ - uses: astral-sh/setup-uv@v5
42
+ - name: Create the virtualenv
43
+ run: uv venv --python ${{ matrix.python-version }}
44
+ - name: Install tooling + dev deps
45
+ # uv pip install (never `uv sync`, which would prune the optional extras).
46
+ run: uv pip install maturin ruff mypy pytest pytest-asyncio httpx fakeredis
47
+ - name: Build the extension + install the test extras
48
+ # gis-terrain is omitted on purpose: WhiteboxTools downloads a binary at
49
+ # runtime, so its tests importorskip and skip cleanly here.
50
+ run: >
51
+ uv run maturin develop
52
+ -E sql,charts,gis,gis-vector-tiles,gis-raster,gis-tiles,gis-ee,vision,vision-cv,tables,redis
53
+ - name: Ruff
54
+ run: uv run ruff check .
55
+ - name: Mypy
56
+ run: uv run mypy
57
+ - name: Pytest
58
+ run: uv run pytest -q
@@ -0,0 +1,82 @@
1
+ name: Release
2
+
3
+ # Tagging v* builds the full wheel matrix + sdist and publishes to PyPI.
4
+ #
5
+ # One-time setup (no secrets needed — uses PyPI Trusted Publishing / OIDC):
6
+ # 1. Create the project on PyPI (or let the first publish create it).
7
+ # 2. PyPI → project → Publishing → add a GitHub Actions publisher:
8
+ # owner = Boadzie, repo = golit, workflow = release.yml, environment = pypi
9
+ # 3. (Repo) Settings → Environments → create an environment named `pypi`.
10
+ # Then: bump the version (pyproject.toml + Cargo.toml), tag `vX.Y.Z`, and push the tag.
11
+
12
+ on:
13
+ push:
14
+ tags: ["v*"]
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ wheels:
21
+ name: wheels (${{ matrix.platform.runner }} · ${{ matrix.platform.target }})
22
+ runs-on: ${{ matrix.platform.runner }}
23
+ strategy:
24
+ fail-fast: false
25
+ matrix:
26
+ platform:
27
+ - { runner: ubuntu-latest, target: x86_64 }
28
+ - { runner: ubuntu-latest, target: aarch64 }
29
+ - { runner: macos-14, target: aarch64 } # Apple Silicon; Intel (macos-13/x86_64) dropped
30
+ - { runner: windows-latest, target: x64 }
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+ - uses: actions/setup-python@v5
34
+ with:
35
+ python-version: "3.11"
36
+ - name: Build wheels
37
+ uses: PyO3/maturin-action@v1
38
+ with:
39
+ target: ${{ matrix.platform.target }}
40
+ args: --release --out dist
41
+ manylinux: auto # ignored off Linux; builds manylinux on Linux runners
42
+ sccache: "true"
43
+ - uses: actions/upload-artifact@v4
44
+ with:
45
+ name: wheels-${{ matrix.platform.runner }}-${{ matrix.platform.target }}
46
+ path: dist
47
+
48
+ sdist:
49
+ name: sdist
50
+ runs-on: ubuntu-latest
51
+ steps:
52
+ - uses: actions/checkout@v4
53
+ - name: Build sdist
54
+ uses: PyO3/maturin-action@v1
55
+ with:
56
+ command: sdist
57
+ args: --out dist
58
+ - uses: actions/upload-artifact@v4
59
+ with:
60
+ name: wheels-sdist
61
+ path: dist
62
+
63
+ publish:
64
+ name: publish to PyPI + attach to the release
65
+ needs: [wheels, sdist]
66
+ runs-on: ubuntu-latest
67
+ environment: pypi
68
+ permissions:
69
+ id-token: write # PyPI Trusted Publishing (OIDC) — no API token in secrets
70
+ contents: write # attach the built artifacts to the GitHub Release
71
+ steps:
72
+ - uses: actions/download-artifact@v4
73
+ with:
74
+ pattern: wheels-*
75
+ merge-multiple: true
76
+ path: dist
77
+ - name: Publish to PyPI
78
+ uses: pypa/gh-action-pypi-publish@release/v1
79
+ - name: Attach artifacts to the GitHub Release
80
+ uses: softprops/action-gh-release@v2
81
+ with:
82
+ files: dist/*
golit-1.0.0/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ # Rust
2
+ /target
3
+ Cargo.lock
4
+
5
+ # Python
6
+ .venv/
7
+ __pycache__/
8
+ *.py[cod]
9
+ *.so
10
+ *.pyd
11
+ dist/
12
+ build/
13
+ *.egg-info/
14
+ .pytest_cache/
15
+ .mypy_cache/
16
+ .ruff_cache/
17
+
18
+ # Docs
19
+ site/
20
+
21
+ # Tooling
22
+ .DS_Store
23
+ golit_pages/
@@ -0,0 +1 @@
1
+ 3.11
@@ -0,0 +1,75 @@
1
+ # Changelog
2
+
3
+ All notable changes to Golit are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/), and the project adheres to
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [1.0.0] — 2026-06-06
8
+
9
+ First stable release. Golit is a high-performance reactive **DAG** framework for Python —
10
+ *reactive data apps that actually ship* — with a Rust kernel and a server-rendered HTMX
11
+ transport, where update cost is proportional to the change, not the program.
12
+
13
+ ### Reactive core
14
+
15
+ - Rust + PyO3 reactive kernel: dirty tracking, topological scheduling, memoized propagation.
16
+ - `@app.source` / `@app.reactive` / `@app.view` — dependencies inferred from parameter names; a
17
+ node re-executes only when an upstream node or input changes, and unchanged outputs cascade
18
+ into memo hits (nothing on the wire).
19
+ - Per-session state (worker-local Polars values) over a shared, immutable topology.
20
+ - A larger app can be split across modules (one shared `App` instance + import-for-side-effects).
21
+
22
+ ### Inputs & components
23
+
24
+ - Reactive input widgets: `slider`, `number`, `select`, `text`, `checkbox`, `upload`, `radio`,
25
+ `multiselect`, `switch`, `date`, `textarea`, `button`.
26
+ - `golit.ui` — shadcn-styled, server-rendered builders: `card`, `columns`, `grid`, `tabs`,
27
+ `expander`, `accordion`, `divider`, `metric`, `scorecard`, `alert`, `badge`, `progress`,
28
+ `skeleton`, `spinner`, `table`, `markdown`, `code`, `json_view`, `heading`, `caption`.
29
+ - Page layout (`golit.layout`): a sidebar/rows/tabs scaffold, validated at build time.
30
+
31
+ ### Rendering
32
+
33
+ - A view may return a `str` (trusted markup), a Polars `DataFrame`, a DuckDB relation, a chart,
34
+ a map, a Great Tables `GT`, anything with `_repr_html_()`, a Matplotlib figure, or `bytes`.
35
+ - Charts: Lets-Plot static SVG; interactive **Plotly / Altair / Bokeh / AnyChart** that hydrate
36
+ on load and across POST/SSE swaps; `chart_spec(lib, dict)` for the raw wire spec.
37
+ - Tables: return a `great_tables` **`GT`** object and Golit auto-renders its self-contained HTML;
38
+ `ui.gt_theme` restyles it to match golit's surface. (`golit[tables]`)
39
+
40
+ ### Maps & GIS (`golit.gis`)
41
+
42
+ - Native **MapLibre GL** maps from a GeoDataFrame; choropleths, tooltips, and DuckDB spatial SQL.
43
+ - MVT **vector tiles** for large vector data; single-band, RGB-composite, and tiled-COG **raster**
44
+ maps; **WhiteboxTools** terrain analysis; **Google Earth Engine** overlays.
45
+
46
+ ### Realtime
47
+
48
+ - **SSE** server-push channel with a pluggable pub/sub (in-memory single-node, **Redis** fleet).
49
+ - **Live data sources** — `@app.poll(name, interval)`: external data that changes on its own (a
50
+ Google Sheet, an API) is fetched in the background and pushed on a content-hash change.
51
+ - **WebSocket chat** — `@app.on_message` + `ui.chat`.
52
+ - **Video** — server-side **MJPEG** streams (`@app.stream` + `ui.webcam`, with `shared=True`
53
+ fan-out) and **browser-camera** computer vision (`@app.on_frame` + `ui.camera`).
54
+ - **Audio** — a microphone **recorder** (`@app.on_audio` + `ui.recorder`) with in-browser WAV
55
+ capture, inline playback, and download.
56
+
57
+ ### SQL
58
+
59
+ - `golit.sql(query, **frames)` — in-process **DuckDB** SQL over Polars frames as a reactive node.
60
+ (`golit[sql]`)
61
+
62
+ ### Server, deployment & tooling
63
+
64
+ - Litestar ASGI app via `create_app`; the `golit run app.py` CLI (uvicorn).
65
+ - Horizontal scale: N single-worker instances behind a sticky load balancer + Redis fan-out, with
66
+ a `deploy/` compose stack and an automated, self-validating **cross-node fan-out verifier**.
67
+ - Optional extras: `charts`, `sql`, `gis` / `gis-raster` / `gis-tiles` / `gis-terrain` /
68
+ `gis-ee` / `gis-vector-tiles`, `vision` / `vision-cv`, `tables`, `redis`.
69
+
70
+ ### Quality
71
+
72
+ - 17 Rust + 209 Python tests; ruff + mypy clean; a benchmark harness (`bench/`) with measured
73
+ Golit-vs-Dash results.
74
+
75
+ [1.0.0]: https://github.com/Boadzie/golit/releases/tag/v1.0.0
golit-1.0.0/Cargo.lock ADDED
@@ -0,0 +1,212 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "autocfg"
7
+ version = "1.5.1"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
10
+
11
+ [[package]]
12
+ name = "cc"
13
+ version = "1.2.63"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
16
+ dependencies = [
17
+ "find-msvc-tools",
18
+ "shlex",
19
+ ]
20
+
21
+ [[package]]
22
+ name = "cfg-if"
23
+ version = "1.0.4"
24
+ source = "registry+https://github.com/rust-lang/crates.io-index"
25
+ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
26
+
27
+ [[package]]
28
+ name = "find-msvc-tools"
29
+ version = "0.1.9"
30
+ source = "registry+https://github.com/rust-lang/crates.io-index"
31
+ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
32
+
33
+ [[package]]
34
+ name = "golit"
35
+ version = "1.0.0"
36
+ dependencies = [
37
+ "pyo3",
38
+ ]
39
+
40
+ [[package]]
41
+ name = "heck"
42
+ version = "0.5.0"
43
+ source = "registry+https://github.com/rust-lang/crates.io-index"
44
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
45
+
46
+ [[package]]
47
+ name = "indoc"
48
+ version = "2.0.7"
49
+ source = "registry+https://github.com/rust-lang/crates.io-index"
50
+ checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
51
+ dependencies = [
52
+ "rustversion",
53
+ ]
54
+
55
+ [[package]]
56
+ name = "libc"
57
+ version = "0.2.186"
58
+ source = "registry+https://github.com/rust-lang/crates.io-index"
59
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
60
+
61
+ [[package]]
62
+ name = "memoffset"
63
+ version = "0.9.1"
64
+ source = "registry+https://github.com/rust-lang/crates.io-index"
65
+ checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
66
+ dependencies = [
67
+ "autocfg",
68
+ ]
69
+
70
+ [[package]]
71
+ name = "once_cell"
72
+ version = "1.21.4"
73
+ source = "registry+https://github.com/rust-lang/crates.io-index"
74
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
75
+
76
+ [[package]]
77
+ name = "portable-atomic"
78
+ version = "1.13.1"
79
+ source = "registry+https://github.com/rust-lang/crates.io-index"
80
+ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
81
+
82
+ [[package]]
83
+ name = "proc-macro2"
84
+ version = "1.0.106"
85
+ source = "registry+https://github.com/rust-lang/crates.io-index"
86
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
87
+ dependencies = [
88
+ "unicode-ident",
89
+ ]
90
+
91
+ [[package]]
92
+ name = "pyo3"
93
+ version = "0.23.5"
94
+ source = "registry+https://github.com/rust-lang/crates.io-index"
95
+ checksum = "7778bffd85cf38175ac1f545509665d0b9b92a198ca7941f131f85f7a4f9a872"
96
+ dependencies = [
97
+ "cfg-if",
98
+ "indoc",
99
+ "libc",
100
+ "memoffset",
101
+ "once_cell",
102
+ "portable-atomic",
103
+ "pyo3-build-config",
104
+ "pyo3-ffi",
105
+ "pyo3-macros",
106
+ "unindent",
107
+ ]
108
+
109
+ [[package]]
110
+ name = "pyo3-build-config"
111
+ version = "0.23.5"
112
+ source = "registry+https://github.com/rust-lang/crates.io-index"
113
+ checksum = "94f6cbe86ef3bf18998d9df6e0f3fc1050a8c5efa409bf712e661a4366e010fb"
114
+ dependencies = [
115
+ "once_cell",
116
+ "python3-dll-a",
117
+ "target-lexicon",
118
+ ]
119
+
120
+ [[package]]
121
+ name = "pyo3-ffi"
122
+ version = "0.23.5"
123
+ source = "registry+https://github.com/rust-lang/crates.io-index"
124
+ checksum = "e9f1b4c431c0bb1c8fb0a338709859eed0d030ff6daa34368d3b152a63dfdd8d"
125
+ dependencies = [
126
+ "libc",
127
+ "pyo3-build-config",
128
+ ]
129
+
130
+ [[package]]
131
+ name = "pyo3-macros"
132
+ version = "0.23.5"
133
+ source = "registry+https://github.com/rust-lang/crates.io-index"
134
+ checksum = "fbc2201328f63c4710f68abdf653c89d8dbc2858b88c5d88b0ff38a75288a9da"
135
+ dependencies = [
136
+ "proc-macro2",
137
+ "pyo3-macros-backend",
138
+ "quote",
139
+ "syn",
140
+ ]
141
+
142
+ [[package]]
143
+ name = "pyo3-macros-backend"
144
+ version = "0.23.5"
145
+ source = "registry+https://github.com/rust-lang/crates.io-index"
146
+ checksum = "fca6726ad0f3da9c9de093d6f116a93c1a38e417ed73bf138472cf4064f72028"
147
+ dependencies = [
148
+ "heck",
149
+ "proc-macro2",
150
+ "pyo3-build-config",
151
+ "quote",
152
+ "syn",
153
+ ]
154
+
155
+ [[package]]
156
+ name = "python3-dll-a"
157
+ version = "0.2.15"
158
+ source = "registry+https://github.com/rust-lang/crates.io-index"
159
+ checksum = "d80ba7540edb18890d444c5aa8e1f1f99b1bdf26fb26ae383135325f4a36042b"
160
+ dependencies = [
161
+ "cc",
162
+ ]
163
+
164
+ [[package]]
165
+ name = "quote"
166
+ version = "1.0.45"
167
+ source = "registry+https://github.com/rust-lang/crates.io-index"
168
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
169
+ dependencies = [
170
+ "proc-macro2",
171
+ ]
172
+
173
+ [[package]]
174
+ name = "rustversion"
175
+ version = "1.0.22"
176
+ source = "registry+https://github.com/rust-lang/crates.io-index"
177
+ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
178
+
179
+ [[package]]
180
+ name = "shlex"
181
+ version = "2.0.1"
182
+ source = "registry+https://github.com/rust-lang/crates.io-index"
183
+ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
184
+
185
+ [[package]]
186
+ name = "syn"
187
+ version = "2.0.117"
188
+ source = "registry+https://github.com/rust-lang/crates.io-index"
189
+ checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
190
+ dependencies = [
191
+ "proc-macro2",
192
+ "quote",
193
+ "unicode-ident",
194
+ ]
195
+
196
+ [[package]]
197
+ name = "target-lexicon"
198
+ version = "0.12.16"
199
+ source = "registry+https://github.com/rust-lang/crates.io-index"
200
+ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
201
+
202
+ [[package]]
203
+ name = "unicode-ident"
204
+ version = "1.0.24"
205
+ source = "registry+https://github.com/rust-lang/crates.io-index"
206
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
207
+
208
+ [[package]]
209
+ name = "unindent"
210
+ version = "0.2.4"
211
+ source = "registry+https://github.com/rust-lang/crates.io-index"
212
+ checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
golit-1.0.0/Cargo.toml ADDED
@@ -0,0 +1,24 @@
1
+ [package]
2
+ name = "golit"
3
+ version = "1.0.0"
4
+ edition = "2021"
5
+ description = "Reactive DAG kernel for Golit (dirty tracking, topological scheduling, propagation)."
6
+ license = "Apache-2.0"
7
+ readme = "README.md"
8
+
9
+ [lib]
10
+ name = "_golit"
11
+ crate-type = ["cdylib", "rlib"]
12
+
13
+ # NOTE: `extension-module` is intentionally NOT enabled here. maturin enables it
14
+ # for wheel builds via [tool.maturin] features. Leaving it off lets `cargo test`
15
+ # link libpython and run the kernel's unit tests without a wheel build.
16
+ # `generate-import-lib` lets pyo3 synthesize the Windows python3.lib from the abi3
17
+ # stable ABI, so Windows wheels cross-compile (cargo-xwin) without a Windows Python.
18
+ # It's a no-op on non-Windows targets.
19
+ [dependencies]
20
+ pyo3 = { version = "0.23", features = ["abi3-py311", "generate-import-lib"] }
21
+
22
+ [profile.release]
23
+ lto = "thin"
24
+ codegen-units = 1
@@ -0,0 +1,163 @@
1
+ # Deploying Golit
2
+
3
+ Golit runs as a single process out of the box. This guide covers going wider:
4
+ multiple workers and hosts, with the SSE push channel intact.
5
+
6
+ ## The one thing to understand: session state is worker-local
7
+
8
+ Each client gets a server-side **session** — its own kernel graph and its current
9
+ Polars values — kept in the worker's memory and keyed by the `golit_session`
10
+ cookie. That locality is deliberate: it's what makes recompute cost track the
11
+ *change*, not the program. Serializing DataFrames to a shared store on every
12
+ interaction would throw that away.
13
+
14
+ The consequence: a client's requests are cheapest when they reach the worker that
15
+ already holds its session — the initial `GET /`, each `POST /node/...`, and the
16
+ long-lived `GET /events` SSE stream. That's *session affinity* ("sticky sessions").
17
+ It is the recommended default, but with Redis turned on it is no longer load-bearing
18
+ for correctness: the **session store** persists each session's *input* state, so a
19
+ request that lands on a worker without the live session **reconstructs** it from
20
+ those inputs (replay + local recompute) instead of starting from defaults. Affinity
21
+ then just keeps the in-memory session warm and avoids the same session diverging
22
+ across two workers under round-robin.
23
+
24
+ The second consequence: a server-side invalidation (a streaming source, a
25
+ background job, a shared node) originates on one worker but must reach the worker
26
+ holding each *affected* client's SSE connection. That's what **Redis pub/sub**
27
+ provides — one `publish`, delivered to every worker.
28
+
29
+ So horizontal scale is two pieces:
30
+
31
+ | Piece | Mechanism | Without it |
32
+ | --- | --- | --- |
33
+ | Keep a client on one worker (recommended) | Load balancer, hashing the `golit_session` cookie | A re-routed client reconstructs from Redis-stored inputs (a cold recompute) — or, with no session store, re-renders from defaults |
34
+ | Reach every worker on invalidation | `GOLIT_REDIS_URL` → `RedisPubSub` | SSE pushes only reach clients on the publishing worker |
35
+
36
+ ## Single node (default)
37
+
38
+ ```bash
39
+ golit run examples/sales_explorer/app.py
40
+ ```
41
+
42
+ One process, in-memory fan-out (`InMemoryPubSub`). Nothing else to configure.
43
+
44
+ ## Turning on Redis
45
+
46
+ Set one environment variable; `create_app` selects **both** Redis backends
47
+ automatically — `RedisPubSub` for invalidation fan-out and `RedisSessionStore` for
48
+ durable input state (Redis is an optional dependency — install with the `redis`
49
+ extra):
50
+
51
+ ```bash
52
+ pip install "golit[redis]"
53
+ export GOLIT_REDIS_URL=redis://localhost:6379
54
+ golit run examples/sales_explorer/app.py
55
+ ```
56
+
57
+ Programmatic override, if you'd rather not use the environment:
58
+
59
+ ```python
60
+ from golit import create_app
61
+ from golit.server import RedisPubSub, RedisSessionStore
62
+
63
+ application = create_app(
64
+ app,
65
+ pubsub=RedisPubSub("redis://redis:6379"),
66
+ session_store=RedisSessionStore("redis://redis:6379"),
67
+ )
68
+ ```
69
+
70
+ ## Horizontal scale: N instances behind a sticky load balancer
71
+
72
+ The supported HA topology is **N single-worker instances**, each on its own
73
+ port/container, behind a load balancer that pins clients by the session cookie,
74
+ all sharing one Redis.
75
+
76
+ > **Why not `uvicorn --workers N`?** uvicorn's workers share one socket with no
77
+ > affinity — the kernel hands each connection to whichever worker is free, so a
78
+ > client's `GET` and its `/events` stream can land on different workers, and
79
+ > neither holds the other's session. `golit run --workers N` exists for
80
+ > convenience and local testing and prints a warning; it is **not** the
81
+ > production path because it can't provide affinity.
82
+
83
+ ### nginx
84
+
85
+ Open-source nginx can hash on a cookie, which gives consistent per-session
86
+ routing:
87
+
88
+ ```nginx
89
+ upstream golit {
90
+ hash $cookie_golit_session consistent; # sticky by Golit's session cookie
91
+ server app1:8000;
92
+ server app2:8000;
93
+ server app3:8000;
94
+ }
95
+ ```
96
+
97
+ The SSE stream also needs buffering off and a long read timeout. The full config
98
+ is in [`deploy/nginx.conf`](deploy/nginx.conf).
99
+
100
+ ## Run the whole stack locally (podman or docker)
101
+
102
+ [`deploy/`](deploy/) has a complete example: Redis + three single-worker replicas
103
+ + the nginx sticky balancer.
104
+
105
+ ```bash
106
+ # from the repo root
107
+ podman compose -f deploy/docker-compose.yml up --build
108
+ # open http://localhost:8000
109
+ ```
110
+
111
+ - [`deploy/Dockerfile`](deploy/Dockerfile) — two-stage build: compile the Rust
112
+ kernel to an abi3 wheel, then install it (with the `redis` extra) into a slim
113
+ runtime.
114
+ - [`deploy/docker-compose.yml`](deploy/docker-compose.yml) — `redis`, `app1/2/3`
115
+ (each `GOLIT_REDIS_URL=redis://redis:6379`), and `nginx` on port 8000.
116
+ - [`deploy/nginx.conf`](deploy/nginx.conf) — the cookie-hash upstream.
117
+
118
+ To prove affinity + fan-out: open the app, move the slider (the chart/KPI/table
119
+ swap — that's the local worker), then have a background source publish an
120
+ invalidation and watch it arrive over `/events` on the *other* replicas' clients.
121
+
122
+ ## Verify the fan-out (automated)
123
+
124
+ [`deploy/verify_scaling.py`](deploy/verify_scaling.py) turns that manual check into a
125
+ one-command, self-validating proof. It runs two single-worker nodes that share one Redis (the
126
+ same fan-out path as two hosts: *publish → Redis → every worker's SSE*). Node **A** publishes a
127
+ `clock` invalidation every second; node **B** does not — yet an SSE client on **B** receives
128
+ the `node:clock` events, so they can only have crossed Redis from A. A built-in **control**
129
+ re-runs both nodes with Redis off (isolated in-memory pub/sub) and asserts B sees *nothing*,
130
+ proving Redis is load-bearing rather than some local artifact.
131
+
132
+ ```bash
133
+ # a Redis to share (podman or docker)
134
+ podman run -d --rm --name golit-redis -p 6379:6379 docker.io/library/redis:7-alpine
135
+
136
+ GOLIT_REDIS_URL=redis://localhost:6379 python deploy/verify_scaling.py
137
+ # -> with Redis -> node B saw node:clock: True
138
+ # without Redis -> node B saw node:clock: False
139
+ # PASS: cross-node fan-out works, and only because of Redis
140
+
141
+ podman rm -f golit-redis
142
+ ```
143
+
144
+ Point `GOLIT_REDIS_URL` at a Redis the two processes can both reach and the same script proves
145
+ the path across **separate hosts** unchanged. ([`deploy/scaling_demo/app.py`](deploy/scaling_demo/app.py)
146
+ is the tiny clock app it drives.)
147
+
148
+ ## Operational notes
149
+
150
+ - **Worker restart loses that worker's warm caches, not the session.** Without a
151
+ session store, clients re-render from defaults on the next `GET /`. With
152
+ `RedisSessionStore` the input state is durable, so the session reconstructs (from
153
+ inputs + a local recompute) on the next request to *any* worker. Either way, keep
154
+ `@app.source` functions cheap/idempotent — the initial render can run again.
155
+ - **Redis never holds DataFrames.** Pub/sub carries small JSON (`node_id`,
156
+ `session`); the session store holds only each session's *input* map
157
+ (`{input_id: value}`). The derived frames stay worker-local — that's the thesis.
158
+ - **Scaling Redis:** a single instance handles a large fan-out fine. Redis
159
+ pub/sub is at-most-once and not persisted — acceptable here because an
160
+ invalidation just asks a worker to recompute current state, which it can always
161
+ redo on the next interaction.
162
+ - **Sizing:** memory is dominated by live session values (Polars frames × active
163
+ sessions). Scale replicas on memory, and front them with the sticky balancer.