sharedbox 0.1.0__tar.gz → 0.3.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 (189) hide show
  1. sharedbox-0.3.0/.clang-format +4 -0
  2. sharedbox-0.3.0/.claude/settings.json +33 -0
  3. sharedbox-0.3.0/.github/workflows/check-docs.yaml +31 -0
  4. sharedbox-0.3.0/.github/workflows/ci.yaml +282 -0
  5. sharedbox-0.3.0/.github/workflows/codspeed.yml +32 -0
  6. sharedbox-0.3.0/.github/workflows/publish-docs.yaml +25 -0
  7. {sharedbox-0.1.0 → sharedbox-0.3.0}/.gitignore +10 -1
  8. sharedbox-0.3.0/CHANGELOG.md +211 -0
  9. sharedbox-0.3.0/CLAUDE.md +294 -0
  10. sharedbox-0.3.0/CMakeLists.txt +70 -0
  11. {sharedbox-0.1.0 → sharedbox-0.3.0}/LICENSE +1 -1
  12. sharedbox-0.3.0/PKG-INFO +88 -0
  13. sharedbox-0.3.0/README.md +66 -0
  14. sharedbox-0.3.0/benchmarks/test_bench_box.py +57 -0
  15. sharedbox-0.3.0/cmake/sharedbox-config.cmake +23 -0
  16. sharedbox-0.3.0/cmake/sharedbox-require-cxx.cmake +32 -0
  17. sharedbox-0.3.0/docs/examples/change_several_fields_at_once.py +44 -0
  18. sharedbox-0.3.0/docs/examples/clean_up_segments.py +38 -0
  19. sharedbox-0.3.0/docs/examples/follow_a_whole_reference_graph.py +46 -0
  20. sharedbox-0.3.0/docs/examples/name_a_box.py +45 -0
  21. sharedbox-0.3.0/docs/examples/send_a_box_to_another_process.py +49 -0
  22. sharedbox-0.3.0/docs/examples/set_defaults_and_check_values.py +79 -0
  23. sharedbox-0.3.0/docs/examples/store_text_and_bytes.py +46 -0
  24. sharedbox-0.3.0/docs/explanation/checking-a-process-is-alive.md +63 -0
  25. sharedbox-0.3.0/docs/explanation/closing-and-lifetime.md +136 -0
  26. sharedbox-0.3.0/docs/explanation/glossary.md +106 -0
  27. sharedbox-0.3.0/docs/explanation/how-a-box-is-stored.md +146 -0
  28. sharedbox-0.3.0/docs/explanation/how-the-module-is-built.md +67 -0
  29. sharedbox-0.3.0/docs/explanation/index.md +40 -0
  30. sharedbox-0.3.0/docs/explanation/limits.md +56 -0
  31. sharedbox-0.3.0/docs/explanation/reading-and-writing.md +121 -0
  32. sharedbox-0.3.0/docs/explanation/references.md +79 -0
  33. sharedbox-0.3.0/docs/explanation/waiting-for-changes.md +184 -0
  34. sharedbox-0.3.0/docs/explanation/when-to-use-sharedbox.md +41 -0
  35. sharedbox-0.3.0/docs/how-to/accept-a-box-in-c.md +83 -0
  36. sharedbox-0.3.0/docs/how-to/accept-a-box-in-cpp.md +174 -0
  37. sharedbox-0.3.0/docs/how-to/ai-contribution-policy.md +58 -0
  38. sharedbox-0.3.0/docs/how-to/build-docs.md +80 -0
  39. sharedbox-0.3.0/docs/how-to/change-several-fields-at-once.md +59 -0
  40. sharedbox-0.3.0/docs/how-to/clean-up-segments.md +82 -0
  41. sharedbox-0.3.0/docs/how-to/commits-and-prs.md +68 -0
  42. sharedbox-0.3.0/docs/how-to/contribute.md +39 -0
  43. sharedbox-0.3.0/docs/how-to/follow-a-whole-reference-graph.md +70 -0
  44. sharedbox-0.3.0/docs/how-to/index.md +50 -0
  45. sharedbox-0.3.0/docs/how-to/install-sharedbox.md +61 -0
  46. sharedbox-0.3.0/docs/how-to/make-a-release.md +87 -0
  47. sharedbox-0.3.0/docs/how-to/name-a-box.md +76 -0
  48. sharedbox-0.3.0/docs/how-to/open-a-box-from-a-program.md +89 -0
  49. sharedbox-0.3.0/docs/how-to/run-benchmarks.md +86 -0
  50. sharedbox-0.3.0/docs/how-to/run-commit-checks.md +76 -0
  51. sharedbox-0.3.0/docs/how-to/run-tests.md +152 -0
  52. sharedbox-0.3.0/docs/how-to/send-a-box-to-another-process.md +69 -0
  53. sharedbox-0.3.0/docs/how-to/set-defaults-and-check-values.md +75 -0
  54. sharedbox-0.3.0/docs/how-to/set-up-development.md +73 -0
  55. sharedbox-0.3.0/docs/how-to/store-text-and-bytes.md +66 -0
  56. sharedbox-0.3.0/docs/how-to/write-docs.md +138 -0
  57. sharedbox-0.3.0/docs/index.md +64 -0
  58. sharedbox-0.3.0/docs/reference/api/box.md +33 -0
  59. sharedbox-0.3.0/docs/reference/api/errors.md +43 -0
  60. sharedbox-0.3.0/docs/reference/api/events.md +22 -0
  61. sharedbox-0.3.0/docs/reference/api/index.md +19 -0
  62. sharedbox-0.3.0/docs/reference/api/library-authors.md +22 -0
  63. sharedbox-0.3.0/docs/reference/api/references.md +13 -0
  64. sharedbox-0.3.0/docs/reference/changelog.md +5 -0
  65. sharedbox-0.3.0/docs/reference/cpp-and-c-api.md +36 -0
  66. sharedbox-0.3.0/docs/reference/index.md +16 -0
  67. sharedbox-0.3.0/docs/reference/segment-layout.md +772 -0
  68. sharedbox-0.3.0/docs/tutorials/index.md +16 -0
  69. sharedbox-0.3.0/docs/tutorials/motor.py +102 -0
  70. sharedbox-0.3.0/docs/tutorials/react-to-changes.md +130 -0
  71. sharedbox-0.3.0/docs/tutorials/refer-to-another-box.md +92 -0
  72. sharedbox-0.3.0/docs/tutorials/share-a-record.md +115 -0
  73. sharedbox-0.3.0/include/sharedbox/sharedbox.hpp +1979 -0
  74. sharedbox-0.3.0/include/sharedbox/sharedbox_c.cpp +87 -0
  75. sharedbox-0.3.0/include/sharedbox/sharedbox_c.h +83 -0
  76. sharedbox-0.3.0/includes/abbreviations.md +9 -0
  77. sharedbox-0.3.0/prek.toml +20 -0
  78. sharedbox-0.3.0/pyproject.toml +173 -0
  79. sharedbox-0.3.0/scripts/check_xrefs.py +108 -0
  80. sharedbox-0.3.0/scripts/vscode_setup.py +84 -0
  81. sharedbox-0.3.0/src/sharedbox/__init__.py +52 -0
  82. sharedbox-0.3.0/src/sharedbox/_box.py +1164 -0
  83. sharedbox-0.3.0/src/sharedbox/_events.py +592 -0
  84. sharedbox-0.3.0/src/sharedbox/_follow.py +437 -0
  85. sharedbox-0.3.0/src/sharedbox/_layout.py +482 -0
  86. sharedbox-0.3.0/src/sharedbox/_native/codec.cpp +194 -0
  87. sharedbox-0.3.0/src/sharedbox/_native/codec.hpp +18 -0
  88. sharedbox-0.3.0/src/sharedbox/_native/module.cpp +372 -0
  89. sharedbox-0.3.0/src/sharedbox/_native/segment.cpp +544 -0
  90. sharedbox-0.3.0/src/sharedbox/_native/segment.hpp +151 -0
  91. sharedbox-0.3.0/src/sharedbox/_native.pyi +281 -0
  92. sharedbox-0.3.0/src/sharedbox/_refs.py +185 -0
  93. sharedbox-0.3.0/src/sharedbox/_version.py +24 -0
  94. sharedbox-0.3.0/src/sharedbox/benchmarks/__init__.py +3 -0
  95. sharedbox-0.3.0/src/sharedbox/benchmarks/__main__.py +4 -0
  96. sharedbox-0.3.0/src/sharedbox/benchmarks/_app.py +183 -0
  97. sharedbox-0.3.0/src/sharedbox/benchmarks/cli.py +21 -0
  98. sharedbox-0.3.0/src/sharedbox/benchmarks/ops.py +296 -0
  99. sharedbox-0.3.0/src/sharedbox/benchmarks/roundtrip.py +314 -0
  100. sharedbox-0.3.0/src/sharedbox/benchmarks/size.py +125 -0
  101. sharedbox-0.3.0/src/sharedbox/benchmarks/size_diff.py +86 -0
  102. sharedbox-0.3.0/src/sharedbox/py.typed +0 -0
  103. sharedbox-0.3.0/stubtest-allowlist.txt +12 -0
  104. sharedbox-0.3.0/tests/conftest.py +84 -0
  105. sharedbox-0.3.0/tests/cpp/CMakeLists.txt +99 -0
  106. sharedbox-0.3.0/tests/cpp/compile_fail.cpp +24 -0
  107. sharedbox-0.3.0/tests/cpp/consumer/CMakeLists.txt +9 -0
  108. sharedbox-0.3.0/tests/cpp/consumer/consumer.c +44 -0
  109. sharedbox-0.3.0/tests/cpp/test_atomics.cpp +159 -0
  110. sharedbox-0.3.0/tests/cpp/test_c_smoke.c +47 -0
  111. sharedbox-0.3.0/tests/cpp/test_c_smoke_box.cpp +26 -0
  112. sharedbox-0.3.0/tests/cpp/test_fork.cpp +57 -0
  113. sharedbox-0.3.0/tests/cpp/test_lock.cpp +103 -0
  114. sharedbox-0.3.0/tests/cpp/test_mapping.cpp +310 -0
  115. sharedbox-0.3.0/tests/cpp/test_result.cpp +121 -0
  116. sharedbox-0.3.0/tests/cpp/test_slots.cpp +157 -0
  117. sharedbox-0.3.0/tests/cpp/test_small_shm.cpp +25 -0
  118. sharedbox-0.3.0/tests/cpp/test_wait.cpp +161 -0
  119. sharedbox-0.3.0/tests/cpp/test_windows_h.cpp +9 -0
  120. sharedbox-0.3.0/tests/cpp/unique.hpp +11 -0
  121. sharedbox-0.3.0/tests/forged_open.py +90 -0
  122. sharedbox-0.3.0/tests/refs_future.py +10 -0
  123. sharedbox-0.3.0/tests/stress/stress_helpers.py +107 -0
  124. sharedbox-0.3.0/tests/stress/test_stress_contention.py +117 -0
  125. sharedbox-0.3.0/tests/stress/test_stress_crashes.py +79 -0
  126. sharedbox-0.3.0/tests/stress/test_stress_follow.py +165 -0
  127. sharedbox-0.3.0/tests/stress/test_stress_lock_timeout.py +299 -0
  128. sharedbox-0.3.0/tests/stress/test_stress_memory.py +172 -0
  129. sharedbox-0.3.0/tests/stress/test_stress_waiters.py +315 -0
  130. sharedbox-0.3.0/tests/stress/test_stress_watchers.py +126 -0
  131. sharedbox-0.3.0/tests/test_box.py +768 -0
  132. sharedbox-0.3.0/tests/test_capsule.py +174 -0
  133. sharedbox-0.3.0/tests/test_doc_examples.py +22 -0
  134. sharedbox-0.3.0/tests/test_doc_tutorials.py +40 -0
  135. sharedbox-0.3.0/tests/test_events_async.py +193 -0
  136. sharedbox-0.3.0/tests/test_events_signals.py +171 -0
  137. sharedbox-0.3.0/tests/test_events_sync.py +218 -0
  138. sharedbox-0.3.0/tests/test_fields.py +800 -0
  139. sharedbox-0.3.0/tests/test_follow.py +1182 -0
  140. sharedbox-0.3.0/tests/test_fork.py +348 -0
  141. sharedbox-0.3.0/tests/test_layout.py +200 -0
  142. sharedbox-0.3.0/tests/test_native_codec.py +154 -0
  143. sharedbox-0.3.0/tests/test_native_liveness.py +66 -0
  144. sharedbox-0.3.0/tests/test_native_segment.py +858 -0
  145. sharedbox-0.3.0/tests/test_native_wait.py +93 -0
  146. sharedbox-0.3.0/tests/test_native_waiters.py +405 -0
  147. sharedbox-0.3.0/tests/test_properties_codec.py +148 -0
  148. sharedbox-0.3.0/tests/test_properties_forged.py +19 -0
  149. sharedbox-0.3.0/tests/test_properties_layout.py +89 -0
  150. sharedbox-0.3.0/tests/test_properties_stateful.py +306 -0
  151. sharedbox-0.3.0/tests/test_refs.py +868 -0
  152. sharedbox-0.3.0/tests/test_refs_lazy.py +19 -0
  153. sharedbox-0.3.0/tests/type_checks/box_types.py +96 -0
  154. sharedbox-0.3.0/tests/type_checks/events_types.py +23 -0
  155. sharedbox-0.3.0/uv.lock +1338 -0
  156. sharedbox-0.3.0/zensical.toml +210 -0
  157. sharedbox-0.1.0/.github/workflows/ci.yaml +0 -79
  158. sharedbox-0.1.0/CHANGELOG.md +0 -12
  159. sharedbox-0.1.0/PKG-INFO +0 -155
  160. sharedbox-0.1.0/README.md +0 -132
  161. sharedbox-0.1.0/cpp_src/shared_dict.cpp +0 -235
  162. sharedbox-0.1.0/cpp_src/shared_dict.hpp +0 -74
  163. sharedbox-0.1.0/examples/basic_comparison.py +0 -328
  164. sharedbox-0.1.0/examples/concurrent_access.py +0 -415
  165. sharedbox-0.1.0/examples/getting_started.py +0 -285
  166. sharedbox-0.1.0/examples/initialization_demo.py +0 -355
  167. sharedbox-0.1.0/examples/memory_usage.py +0 -438
  168. sharedbox-0.1.0/examples/mixed_data_benchmark.py +0 -407
  169. sharedbox-0.1.0/examples/numpy_performance_benchmark.py +0 -291
  170. sharedbox-0.1.0/examples/shared_cache_example.py +0 -457
  171. sharedbox-0.1.0/pyproject.toml +0 -54
  172. sharedbox-0.1.0/setup.cfg +0 -4
  173. sharedbox-0.1.0/setup.py +0 -106
  174. sharedbox-0.1.0/src/sharedbox/__init__.py +0 -3
  175. sharedbox-0.1.0/src/sharedbox/_shareddict.pyi +0 -32
  176. sharedbox-0.1.0/src/sharedbox/_shareddict.pyx +0 -350
  177. sharedbox-0.1.0/src/sharedbox/_version.py +0 -34
  178. sharedbox-0.1.0/src/sharedbox/utils.py +0 -293
  179. sharedbox-0.1.0/src/sharedbox.egg-info/PKG-INFO +0 -155
  180. sharedbox-0.1.0/src/sharedbox.egg-info/SOURCES.txt +0 -33
  181. sharedbox-0.1.0/src/sharedbox.egg-info/dependency_links.txt +0 -1
  182. sharedbox-0.1.0/src/sharedbox.egg-info/not-zip-safe +0 -1
  183. sharedbox-0.1.0/src/sharedbox.egg-info/requires.txt +0 -5
  184. sharedbox-0.1.0/src/sharedbox.egg-info/top_level.txt +0 -1
  185. sharedbox-0.1.0/tests/test_initialization_data.py +0 -263
  186. sharedbox-0.1.0/tests/test_mixed_data_types.py +0 -316
  187. sharedbox-0.1.0/tests/test_multiprocess_containers.py +0 -631
  188. sharedbox-0.1.0/tests/test_numpy_support.py +0 -216
  189. sharedbox-0.1.0/uv.lock +0 -328
@@ -0,0 +1,4 @@
1
+ BasedOnStyle: LLVM
2
+ IndentWidth: 4
3
+ ColumnLimit: 115
4
+ AccessModifierOffset: -4
@@ -0,0 +1,33 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(git status:*)",
5
+ "Bash(git diff:*)",
6
+ "Bash(git log:*)",
7
+ "Bash(git show:*)",
8
+ "Bash(git rev-parse:*)",
9
+ "Bash(git branch:*)",
10
+ "Bash(git merge-base:*)",
11
+ "Bash(git ls-files:*)",
12
+ "Bash(git fetch:*)",
13
+ "Bash(gh run list:*)",
14
+ "Bash(gh run view:*)",
15
+ "Bash(gh run watch:*)",
16
+ "Bash(gh pr view:*)",
17
+ "Bash(gh pr checks:*)",
18
+ "Bash(ls:*)",
19
+ "Bash(cat:*)",
20
+ "Bash(grep:*)",
21
+ "Bash(sed -n:*)",
22
+ "Bash(head:*)",
23
+ "Bash(tail:*)",
24
+ "Bash(wc:*)"
25
+ ],
26
+ "deny": [
27
+ "Bash(git push:*)",
28
+ "Bash(git reset --hard:*)",
29
+ "Bash(rm -rf:*)",
30
+ "Bash(gh pr merge:*)"
31
+ ]
32
+ }
33
+ }
@@ -0,0 +1,31 @@
1
+ name: Check documentation
2
+
3
+ on:
4
+ workflow_call:
5
+
6
+ jobs:
7
+ check-docs:
8
+ name: Build and check cross-references
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v5
12
+
13
+ - name: Install uv
14
+ uses: astral-sh/setup-uv@v7
15
+ with:
16
+ enable-cache: true
17
+ cache-dependency-glob: "uv.lock"
18
+ python-version: "3.11"
19
+
20
+ - name: Build docs and check cross-references
21
+ run: uv run --locked --only-group tox tox -e docs
22
+
23
+ # the publish job deploys this artifact rather than building again,
24
+ # so what reaches Pages is what was checked
25
+ - name: Store the built site
26
+ uses: actions/upload-artifact@v4
27
+ with:
28
+ name: docs-site
29
+ path: site/
30
+ if-no-files-found: error
31
+ retention-days: 7
@@ -0,0 +1,282 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+ release:
9
+ types: [ published ]
10
+ workflow_dispatch:
11
+
12
+ jobs:
13
+ lint:
14
+ name: Lint
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v5
18
+
19
+ # local hooks in prek.toml call `uv run --locked --only-group lint ...`
20
+ - uses: astral-sh/setup-uv@v7
21
+
22
+ - uses: j178/prek-action@4e14d07f9231acabce116ccfca13b13dd9755ece # v3.0.0
23
+ with:
24
+ prek-version: "0.5.3"
25
+
26
+ docs:
27
+ name: Check documentation
28
+ uses: ./.github/workflows/check-docs.yaml
29
+
30
+ publish_docs:
31
+ name: Publish documentation
32
+ needs: [lint, docs]
33
+ # main and final releases only: a pre-release publishes a package and
34
+ # leaves the site describing the last final release
35
+ if: >-
36
+ (github.event_name == 'push' && github.ref == 'refs/heads/main') ||
37
+ (github.event_name == 'release' && github.event.action == 'published' &&
38
+ !github.event.release.prerelease &&
39
+ !contains(github.event.release.tag_name, 'rc'))
40
+ uses: ./.github/workflows/publish-docs.yaml
41
+ permissions:
42
+ contents: write
43
+ concurrency:
44
+ group: pages
45
+ cancel-in-progress: false
46
+
47
+ cpp_header:
48
+ name: C++ header on ${{ matrix.os }} (${{ matrix.build }})
49
+ runs-on: ${{ matrix.os }}
50
+ timeout-minutes: 10
51
+ strategy:
52
+ fail-fast: false
53
+ matrix:
54
+ include:
55
+ - { os: ubuntu-latest, build: Release, flags: "" }
56
+ - { os: windows-latest, build: Release, flags: "" }
57
+ # No ThreadSanitizer: the sequence lock's reader copies the record while a writer may change it, by design.
58
+ - os: ubuntu-latest
59
+ build: RelWithDebInfo
60
+ flags: -fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all -DSHAREDBOX_TEST_ROUNDS=20000
61
+ env:
62
+ ASAN_OPTIONS: detect_leaks=1
63
+ UBSAN_OPTIONS: print_stacktrace=1
64
+ steps:
65
+ - uses: actions/checkout@v5
66
+ with:
67
+ fetch-depth: 0
68
+
69
+ - name: Build and run the C++ tests
70
+ shell: bash
71
+ run: |
72
+ cmake -S tests/cpp -B build-cpp -DCMAKE_BUILD_TYPE=${{ matrix.build }} \
73
+ -DCMAKE_C_FLAGS_INIT="${{ matrix.flags }}" -DCMAKE_CXX_FLAGS_INIT="${{ matrix.flags }}"
74
+ cmake --build build-cpp --config ${{ matrix.build }}
75
+ ctest --test-dir build-cpp -C ${{ matrix.build }} --output-on-failure
76
+
77
+ - uses: astral-sh/setup-uv@v7
78
+
79
+ - name: Build the extension and run the C consumer
80
+ if: matrix.build == 'Release'
81
+ shell: bash
82
+ env:
83
+ SHAREDBOX_REQUIRE_C_CONSUMER: "1"
84
+ run: |
85
+ uv sync --dev
86
+ uv run --no-sync pytest tests/test_capsule.py -q
87
+
88
+ - name: Create a box larger than a 16 MiB /dev/shm
89
+ if: runner.os == 'Linux' && matrix.build == 'Release'
90
+ run: |
91
+ docker run --rm --shm-size=16m -v "$PWD":/src -w /src ubuntu:24.04 build-cpp/test_small_shm
92
+
93
+ build_wheels:
94
+ name: Build wheel for ${{ matrix.python }}-${{ matrix.platform_id }}
95
+ runs-on: ${{ matrix.os }}
96
+ strategy:
97
+ fail-fast: false
98
+ matrix:
99
+ include:
100
+ - { os: windows-latest, python: cp311, platform_id: win_amd64 }
101
+ - { os: windows-latest, python: cp312, platform_id: win_amd64 }
102
+ - { os: windows-latest, python: cp314t, platform_id: win_amd64 }
103
+ - { os: ubuntu-latest, python: cp311, platform_id: manylinux_x86_64 }
104
+ - { os: ubuntu-latest, python: cp312, platform_id: manylinux_x86_64 }
105
+ - { os: ubuntu-latest, python: cp314t, platform_id: manylinux_x86_64 }
106
+ - { os: ubuntu-latest, python: cp311, platform_id: musllinux_x86_64 }
107
+ - { os: ubuntu-latest, python: cp312, platform_id: musllinux_x86_64 }
108
+ - { os: ubuntu-latest, python: cp314t, platform_id: musllinux_x86_64 }
109
+
110
+ steps:
111
+ - uses: actions/checkout@v5
112
+ with:
113
+ fetch-depth: 0
114
+ ref: ${{ github.event_name == 'release' && github.event.release.tag_name || '' }}
115
+
116
+ - name: Build wheels
117
+ uses: pypa/cibuildwheel@v3.2.0
118
+ env:
119
+ CIBW_BUILD: ${{ matrix.python }}-${{ matrix.platform_id }}
120
+ CIBW_CONTAINER_ENGINE: "docker; create_args: --shm-size=1g"
121
+ CIBW_TEST_REQUIRES: pytest hypothesis
122
+ CIBW_TEST_COMMAND: pytest {project}/tests
123
+ with:
124
+ package-dir: .
125
+ output-dir: wheelhouse
126
+
127
+ - name: Upload wheel artifacts
128
+ uses: actions/upload-artifact@v4
129
+ with:
130
+ name: cibw-wheels-${{ matrix.python }}-${{ matrix.platform_id }}
131
+ path: ./wheelhouse/*.whl
132
+
133
+ - name: Measure wheel and extension size
134
+ shell: bash
135
+ run: |
136
+ echo "### Wheel size: ${{ matrix.python }}-${{ matrix.platform_id }}" >> "$GITHUB_STEP_SUMMARY"
137
+ # Run by path, not with -m: importing the sharedbox package loads its
138
+ # compiled extension and psygnal, which this runner does not have installed.
139
+ python src/sharedbox/benchmarks/size.py wheelhouse/*.whl --json wheelhouse/sizes.json --markdown >> "$GITHUB_STEP_SUMMARY"
140
+
141
+ - name: Upload size artifact
142
+ uses: actions/upload-artifact@v4
143
+ with:
144
+ name: wheel-sizes-${{ matrix.python }}-${{ matrix.platform_id }}
145
+ path: wheelhouse/sizes.json
146
+
147
+ sizes:
148
+ name: Report wheel size changes
149
+ runs-on: ubuntu-latest
150
+ needs: [build_wheels]
151
+ permissions:
152
+ contents: read
153
+ actions: read
154
+ continue-on-error: true
155
+ steps:
156
+ - uses: actions/checkout@v5
157
+
158
+ - name: Download this run's size artifacts
159
+ uses: actions/download-artifact@v5
160
+ with:
161
+ pattern: wheel-sizes-*
162
+ path: sizes/this
163
+
164
+ - name: Find the latest successful run on main
165
+ id: baseline
166
+ env:
167
+ GH_TOKEN: ${{ github.token }}
168
+ run: |
169
+ run_id=$(gh run list --repo "${{ github.repository }}" --workflow ci.yaml \
170
+ --branch main --status success --limit 1 --json databaseId --jq '.[0].databaseId // empty')
171
+ echo "run_id=$run_id" >> "$GITHUB_OUTPUT"
172
+
173
+ - name: Download the baseline's size artifacts
174
+ if: steps.baseline.outputs.run_id != ''
175
+ continue-on-error: true
176
+ uses: actions/download-artifact@v5
177
+ with:
178
+ pattern: wheel-sizes-*
179
+ path: sizes/main
180
+ run-id: ${{ steps.baseline.outputs.run_id }}
181
+ github-token: ${{ github.token }}
182
+
183
+ - name: Report size differences
184
+ shell: bash
185
+ # Run by path, not with -m: importing the sharedbox package loads its
186
+ # compiled extension and psygnal, which this runner does not have installed.
187
+ run: python src/sharedbox/benchmarks/size_diff.py sizes/this sizes/main >> "$GITHUB_STEP_SUMMARY"
188
+
189
+ stress:
190
+ name: Stress tests on ${{ matrix.os }}
191
+ if: github.event_name == 'workflow_dispatch'
192
+ needs: [build_wheels]
193
+ runs-on: ${{ matrix.os }}
194
+ timeout-minutes: 30
195
+ strategy:
196
+ fail-fast: false
197
+ matrix:
198
+ include:
199
+ - { os: ubuntu-latest, wheel: cibw-wheels-cp312-manylinux_x86_64 }
200
+ - { os: windows-latest, wheel: cibw-wheels-cp312-win_amd64 }
201
+ steps:
202
+ - uses: actions/checkout@v5
203
+
204
+ - uses: astral-sh/setup-uv@v7
205
+
206
+ - uses: actions/download-artifact@v5
207
+ with:
208
+ name: ${{ matrix.wheel }}
209
+ path: wheelhouse
210
+
211
+ - name: Run the stress tests against the built wheel
212
+ shell: bash
213
+ run: |
214
+ uv venv --python 3.12 .stress
215
+ uv pip install --python .stress wheelhouse/*.whl pytest hypothesis
216
+ python=.stress/bin/python
217
+ [ -x "$python" ] || python=.stress/Scripts/python.exe
218
+ "$python" -m pytest -m stress -q -p no:cacheprovider
219
+ "$python" -m pytest tests/test_properties_codec.py tests/test_properties_layout.py tests/test_properties_forged.py tests/test_properties_stateful.py -q -p no:cacheprovider --hypothesis-profile=thorough
220
+
221
+ - uses: actions/upload-artifact@v4
222
+ if: always()
223
+ with:
224
+ name: stress-${{ matrix.os }}
225
+ path: build/stress/*.json
226
+
227
+ make_sdist:
228
+ name: Make SDist
229
+ runs-on: ubuntu-latest
230
+ needs: [build_wheels]
231
+ if: github.event_name == 'release' && github.event.action == 'published'
232
+ steps:
233
+ - name: Check the release tag and pre-release flag
234
+ env:
235
+ TAG: ${{ github.event.release.tag_name }}
236
+ PRERELEASE: ${{ github.event.release.prerelease }}
237
+ run: |
238
+ if ! [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(rc[0-9]+)?$ ]]; then
239
+ echo "::error::tag $TAG is not vX.Y.Z or vX.Y.ZrcN"; exit 1
240
+ fi
241
+ case "$TAG" in
242
+ *rc*) [ "$PRERELEASE" = "true" ] || { echo "::error::$TAG must be marked pre-release"; exit 1; } ;;
243
+ *) [ "$PRERELEASE" = "false" ] || { echo "::error::$TAG must not be marked pre-release"; exit 1; } ;;
244
+ esac
245
+
246
+ - uses: actions/checkout@v5
247
+ with:
248
+ fetch-depth: 0
249
+ ref: ${{ github.event_name == 'release' && github.event.release.tag_name || '' }}
250
+
251
+ - name: Build SDist
252
+ run: pipx run build --sdist
253
+
254
+ - name: Check the sdist version matches the tag
255
+ env:
256
+ TAG: ${{ github.event.release.tag_name }}
257
+ run: test -f "dist/sharedbox-${TAG#v}.tar.gz"
258
+
259
+ - uses: actions/upload-artifact@v4
260
+ with:
261
+ name: cibw-sdist
262
+ path: dist/*.tar.gz
263
+
264
+ upload_pypi:
265
+ name: Upload to PyPI
266
+ needs: [lint, docs, cpp_header, build_wheels, make_sdist]
267
+ runs-on: ubuntu-latest
268
+ environment:
269
+ name: pypi
270
+ url: https://pypi.org/p/sharedbox
271
+ permissions:
272
+ id-token: write
273
+ if: github.event_name == 'release' && github.event.action == 'published'
274
+ steps:
275
+ - uses: actions/download-artifact@v5
276
+ with:
277
+ # unpacks all CIBW artifacts into dist/
278
+ pattern: cibw-*
279
+ path: dist
280
+ merge-multiple: true
281
+
282
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,32 @@
1
+ name: CodSpeed
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+ id-token: write
13
+
14
+ jobs:
15
+ benchmarks:
16
+ name: Run benchmarks
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v5
20
+ with:
21
+ fetch-depth: 0
22
+
23
+ - uses: astral-sh/setup-uv@v7
24
+
25
+ - name: Install the project
26
+ run: uv sync --dev --frozen --python 3.12
27
+
28
+ - name: Run benchmarks
29
+ uses: CodSpeedHQ/action@v5
30
+ with:
31
+ mode: simulation
32
+ run: uv run --no-sync pytest benchmarks --codspeed
@@ -0,0 +1,25 @@
1
+ name: Publish documentation
2
+
3
+ on:
4
+ workflow_call:
5
+
6
+ jobs:
7
+ publish-docs:
8
+ name: Publish on pages
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ contents: write
12
+ steps:
13
+ - uses: actions/checkout@v5
14
+
15
+ - name: Download the built site
16
+ uses: actions/download-artifact@v5
17
+ with:
18
+ name: docs-site
19
+ path: site/
20
+
21
+ - name: Deploy to GitHub Pages
22
+ uses: peaceiris/actions-gh-pages@v4
23
+ with:
24
+ github_token: ${{ secrets.GITHUB_TOKEN }}
25
+ publish_dir: site
@@ -16,4 +16,13 @@ wheels/
16
16
 
17
17
  src/sharedbox/_version.py
18
18
 
19
- wheelhouse/
19
+ wheelhouse/
20
+
21
+ graphify-out/
22
+
23
+ build-cpp/
24
+
25
+ .hypothesis/
26
+ .claude/settings.local.json
27
+
28
+ site/
@@ -0,0 +1,211 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ Dates are marked as `DD-MM-YYYY`
9
+
10
+ ## [0.3.0] - 01-10-2026
11
+
12
+ ### Added
13
+
14
+ - `SharedBox`: base class whose annotated fields live in a shared-memory segment.
15
+ - `Capacity`: byte capacity for `str` and `bytes` fields.
16
+ - `FieldWatch`: `for` and `async for` over new values of a field.
17
+ - `SharedBox.events`: psygnal `SignalGroup` with one `(new, old)` signal per
18
+ field.
19
+ - `BoxClosedError`, `LockTimeoutError`, `SchemaMismatchError`,
20
+ `SegmentExistsError`, `SegmentNotFoundError`.
21
+ - `benchbox`: command with `ops`, `roundtrip`, `size` and `all`
22
+ benchmarks, also run as `python -m sharedbox.benchmarks`.
23
+ - `benchmarks` extra: installs Typer and pyperf for `benchbox`.
24
+ - `SharedBox`: `identity` class keyword; it enters the schema hash and
25
+ names the box.
26
+ - `SharedBox`: `max_waiters` class keyword, 1 to 4096 box handles that
27
+ watch at once, default 64.
28
+ - `SharedBox.__sharedbox_box__()`: returns a `"sharedbox_box"` PyCapsule
29
+ for other extensions.
30
+ - `SupportsSharedBox`: protocol for functions that accept a box.
31
+ - `get_include()`: folder holding `sharedbox/sharedbox.hpp`,
32
+ `sharedbox/sharedbox_c.h` and `sharedbox/sharedbox_c.cpp`.
33
+ - `sharedbox.hpp`: header-only C++20 implementation of the segment layout,
34
+ installed in the wheel with a CMake config (`sharedbox::headers`), which
35
+ links `bcrypt` on Windows and `rt` and `Threads::Threads` on Linux.
36
+ - `sharedbox-config-version.cmake`: installed next to
37
+ `sharedbox-config.cmake`; `find_package(sharedbox 0.3)` accepts 0.3
38
+ versions only.
39
+ - `sharedbox_c.h`: minimal C interface (`sbx_open`, `sbx_import`,
40
+ `sbx_schema_hash`, `sbx_read`, `sbx_write`, `sbx_release`), built
41
+ through `sharedbox::c`.
42
+
43
+ ```python
44
+ class Frame(SharedBox, identity="camera/frame/1", max_waiters=16):
45
+ exposure: float = 0.01
46
+ count: int = 0
47
+ ```
48
+
49
+ - `field()`: per-field `default`, `default_factory`, `init`, `repr`,
50
+ `kw_only`, `metadata` and `doc` for `SharedBox` fields.
51
+ - `fields()`: the `Field` of each field of a `SharedBox` subclass or box.
52
+ - `Field`: read-only description of one `SharedBox` field.
53
+ - `SharedBox`: `dataclasses.InitVar` annotations, passed to
54
+ `__post_init__` and not stored.
55
+ - `SharedBox.__post_init__()`: runs after a box is created and before
56
+ other processes can attach to it.
57
+ - `SharedBox`: `inspect.signature()` of a subclass gives its constructor
58
+ parameters.
59
+ - `sharedbox.hpp`: `handle::create_unpublished()` and
60
+ `handle::publish()`.
61
+ - `SharedBox`: reference fields, annotated with a `SharedBox` subclass `X`
62
+ or `X | None`; reading one attaches the box it refers to, with that
63
+ box's own class.
64
+ - `BoxRef`: frozen dataclass (`name`, `schema_hash`, `create_id`, and the
65
+ `box_class` property) that `snapshot()`, `events` and `watch()` report
66
+ for a reference field.
67
+ - `BrokenReferenceError`: raised when the box a reference field refers to
68
+ was removed or created again since it was assigned.
69
+ - `UnknownBoxClassError`: raised when the box a reference field refers to
70
+ has a class this process has not defined.
71
+ - `SharedBox.snapshot()`: `follow` keyword; `follow=True` replaces each
72
+ reference with the snapshot of the box it refers to.
73
+ - `sharedbox.hpp`: `kind_ref` and `box_ref`, the kind code and stored value
74
+ of a reference field.
75
+
76
+ ```python
77
+ class Motor(SharedBox):
78
+ position: int
79
+ limit: int = field(default=100, kw_only=True, metadata={"unit": "mm"})
80
+ offset: InitVar[int] = 0
81
+
82
+ def __post_init__(self, offset: int) -> None:
83
+ self.position += offset
84
+
85
+
86
+ class Stage(SharedBox):
87
+ motor: Motor | None = None
88
+ ```
89
+
90
+ - `BoxEvents`: psygnal `SignalGroup` subclass that `SharedBox.events`
91
+ returns.
92
+ - `BoxEvents.follow()`: given a reference field, returns a group whose
93
+ signals are emitted for changes inside the box the field refers to,
94
+ whichever box that is; given no field, emits on `nested` every change
95
+ inside the boxes the reference fields reach.
96
+ - `BoxEvents.unfollow()`: stops forwarding started with `follow()`.
97
+ - `BoxEvents.nested`: `(path, new, old)` signal of a class with reference
98
+ fields.
99
+
100
+ ```python
101
+ motor_events = stage.events.follow("motor")
102
+ motor_events.position.connect(lambda new, old: print(new))
103
+ stage.events.follow()
104
+ stage.events.nested.connect(lambda path, new, old: print(path, new))
105
+ ```
106
+
107
+ - Documentation site at <https://jacopoabramo.github.io/sharedbox>:
108
+ tutorial, how-to guides, explanations and the API reference.
109
+
110
+ ### Changed
111
+
112
+ - Building the extension requires nanobind 3.1.0 or newer and a C++20
113
+ compiler.
114
+ - Wheels per platform: `cp311-cp311`, `cp312-abi3` for CPython 3.12 and
115
+ newer, and `cp314-cp314t` for free-threaded CPython 3.14.
116
+ - `SharedBox` fields are converted in the native module and packed by
117
+ alignment; segments use layout 1.0, a plain named mapping.
118
+ - `SharedBox`: the default box name is 16 hex digits of SHA-256 over the
119
+ identity, with no `sharedbox-` prefix. A box created by an earlier
120
+ release under `sharedbox-<hash>` is not found under the new default name.
121
+ - Shared-memory objects are named `sharedbox.<name>`
122
+ (`/dev/shm/sharedbox.<name>`, `Local\sharedbox.<name>`); boxes of
123
+ earlier releases are not found under them.
124
+ - `SegmentExistsError`: says whether the box's creator is still running,
125
+ runs in another pid namespace, or has exited, or that the name holds no
126
+ published box.
127
+ - A pickled `SharedBox` carries its class's schema hash and its box's
128
+ create id; unpickling with a different class, or after the box was
129
+ created again, raises `SchemaMismatchError`.
130
+ - `SharedBox.attach()`: shared memory that does not become a box within
131
+ 1 s raises `SegmentNotFoundError`.
132
+ - `SharedBox.attach()`: a segment of another layout major version raises
133
+ `SchemaMismatchError` naming the version.
134
+ - `SharedBox` fields: reading or writing one field runs no Python code;
135
+ deleting one raises `AttributeError`.
136
+ - `SharedBox`: a subclass whose `__slots__` names `_segment` raises
137
+ `TypeError`.
138
+ - `SharedBox`: an error about a field names the field as `Class.field`.
139
+ - `SharedBox`: a read or write that waits for another writer's lock lets
140
+ other threads run.
141
+ - `SharedBox.update()`: takes about half the time; field names are checked
142
+ only when one is unknown.
143
+ - `SharedBox.snapshot()`: builds its dict in the native module.
144
+ - `SharedBox`: the `kw_only` class keyword and a `KW_ONLY` annotation
145
+ apply only to the fields of the class that declares them; a subclass
146
+ keeps each inherited field's keyword-only setting.
147
+ - `SharedBox`: a subclass that sets an unannotated class attribute on the
148
+ name of an inherited field raises `TypeError`.
149
+ - `SegmentExistsError`: for a box whose creator is still running
150
+ `__post_init__`, says the box is being created by that pid.
151
+ - `SharedBox.__sharedbox_box__()`: raises `BufferError` before the box
152
+ is published.
153
+ - `sharedbox.hpp`: `handle::open` and `handle::from_capsule` accept a field
154
+ whose kind code they do not know and treat its bytes as opaque.
155
+ - `sharedbox.hpp`: `handle::write` returns `status::range` for a field whose
156
+ kind code it does not know, and `sbx_write` returns `SBX_E_RANGE`.
157
+ - `SharedBox.attach()`: a segment with a field of a kind this version
158
+ cannot read raises `SchemaMismatchError` naming the kind.
159
+ - `SharedBox`: an annotation naming an undefined class raises `TypeError`.
160
+ - `SharedBox`: a field named `follow`, `unfollow` or `nested` raises
161
+ `TypeError`.
162
+ - `SharedBox.close()`: called from an event callback, on a watcher thread,
163
+ does not wait for the watcher threads of the boxes it closes, and drops
164
+ the writes they had not delivered.
165
+
166
+ ### Fixed
167
+
168
+ - `SharedBox.watch()`: no longer yields the same value twice.
169
+ - `SharedBox.attach()`: no longer fails when it runs while another process
170
+ is still creating the box.
171
+ - `SharedBox`: a process that attaches while the box is being created sees
172
+ the initial values, never a zeroed record.
173
+ - `SharedBox.close()`: no longer deadlocks while another thread's read or
174
+ write waits for the write lock.
175
+ - `SharedBox.close()`: returns without waiting for the watcher thread's
176
+ 0.1 s poll.
177
+ - `SharedBox.watch()` and `SharedBox.events`: on Windows, a write no
178
+ longer reaches one of several waiting threads up to 50 ms late.
179
+ - `SharedBox.watch()` and `SharedBox.events`: keep delivering when every
180
+ waiter slot is taken.
181
+ - `SharedBox`: on Linux, a box larger than the free space of `/dev/shm`
182
+ raises `OSError` at creation instead of the process receiving `SIGBUS`.
183
+
184
+ ### Removed
185
+
186
+ - Python 3.10 support.
187
+ - `SharedDict` and `sharedbox.utils`.
188
+ - Boost and vcpkg: the build no longer needs `VCPKG_ROOT`.
189
+ - `docs/api.md`, `docs/library-authors.md` and `docs/design/`: their
190
+ content is on the documentation site.
191
+
192
+ ## [0.2.4] - 05-10-2025
193
+
194
+ ### Changed
195
+
196
+ - Rewrite codebase in nanobind
197
+
198
+ ### Fixed
199
+
200
+ - Parallelize CI so that each wheel is built with the correct version
201
+ - Also faster builds
202
+
203
+ ## [0.1.0] - 29-09-2025
204
+
205
+ ### Added
206
+
207
+ - Initial release
208
+
209
+ [0.3.0]: https://github.com/jacopoabramo/sharedbox/compare/0.2.4...v0.3.0
210
+ [0.2.4]: https://github.com/jacopoabramo/sharedbox/compare/0.1.0...0.2.4
211
+ [0.1.0]: https://github.com/jacopoabramo/sharedbox/commits/0.1.0