sharedbox 0.2.4__tar.gz → 0.3.0rc0__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 (79) hide show
  1. sharedbox-0.3.0rc0/.clang-format +4 -0
  2. sharedbox-0.3.0rc0/.github/workflows/ci.yaml +176 -0
  3. sharedbox-0.3.0rc0/.github/workflows/codspeed.yml +39 -0
  4. {sharedbox-0.2.4 → sharedbox-0.3.0rc0}/.gitignore +0 -2
  5. sharedbox-0.3.0rc0/CHANGELOG.md +55 -0
  6. sharedbox-0.3.0rc0/CLAUDE.md +193 -0
  7. sharedbox-0.3.0rc0/CMakeLists.txt +41 -0
  8. {sharedbox-0.2.4 → sharedbox-0.3.0rc0}/LICENSE +1 -1
  9. sharedbox-0.3.0rc0/PKG-INFO +222 -0
  10. sharedbox-0.3.0rc0/README.md +200 -0
  11. sharedbox-0.3.0rc0/benchmarks/test_bench_box.py +58 -0
  12. sharedbox-0.3.0rc0/docs/api.md +489 -0
  13. sharedbox-0.3.0rc0/docs/design/native-segment.md +546 -0
  14. sharedbox-0.3.0rc0/prek.toml +20 -0
  15. sharedbox-0.3.0rc0/pyproject.toml +164 -0
  16. sharedbox-0.3.0rc0/scripts/vscode_setup.py +96 -0
  17. sharedbox-0.3.0rc0/src/sharedbox/__init__.py +21 -0
  18. sharedbox-0.3.0rc0/src/sharedbox/_box.py +362 -0
  19. sharedbox-0.3.0rc0/src/sharedbox/_events.py +361 -0
  20. sharedbox-0.3.0rc0/src/sharedbox/_layout.py +192 -0
  21. sharedbox-0.3.0rc0/src/sharedbox/_native/module.cpp +86 -0
  22. sharedbox-0.3.0rc0/src/sharedbox/_native/notifier.cpp +80 -0
  23. sharedbox-0.3.0rc0/src/sharedbox/_native/notifier.hpp +31 -0
  24. sharedbox-0.3.0rc0/src/sharedbox/_native/segment.cpp +570 -0
  25. sharedbox-0.3.0rc0/src/sharedbox/_native/segment.hpp +76 -0
  26. sharedbox-0.3.0rc0/src/sharedbox/_native.pyi +88 -0
  27. sharedbox-0.3.0rc0/src/sharedbox/_version.py +24 -0
  28. sharedbox-0.3.0rc0/src/sharedbox/benchmarks/__init__.py +1 -0
  29. sharedbox-0.3.0rc0/src/sharedbox/benchmarks/__main__.py +4 -0
  30. sharedbox-0.3.0rc0/src/sharedbox/benchmarks/_app.py +183 -0
  31. sharedbox-0.3.0rc0/src/sharedbox/benchmarks/cli.py +14 -0
  32. sharedbox-0.3.0rc0/src/sharedbox/benchmarks/ops.py +278 -0
  33. sharedbox-0.3.0rc0/src/sharedbox/benchmarks/roundtrip.py +308 -0
  34. sharedbox-0.3.0rc0/src/sharedbox/benchmarks/size.py +125 -0
  35. sharedbox-0.3.0rc0/src/sharedbox/benchmarks/size_diff.py +86 -0
  36. sharedbox-0.3.0rc0/stubtest-allowlist.txt +8 -0
  37. sharedbox-0.3.0rc0/tests/conftest.py +16 -0
  38. sharedbox-0.3.0rc0/tests/test_box.py +393 -0
  39. sharedbox-0.3.0rc0/tests/test_events_async.py +177 -0
  40. sharedbox-0.3.0rc0/tests/test_events_signals.py +150 -0
  41. sharedbox-0.3.0rc0/tests/test_events_sync.py +140 -0
  42. sharedbox-0.3.0rc0/tests/test_fork.py +126 -0
  43. sharedbox-0.3.0rc0/tests/test_layout.py +136 -0
  44. sharedbox-0.3.0rc0/tests/test_native_segment.py +309 -0
  45. sharedbox-0.3.0rc0/tests/test_native_wait.py +85 -0
  46. sharedbox-0.3.0rc0/tests/type_checks/box_types.py +32 -0
  47. sharedbox-0.3.0rc0/uv.lock +763 -0
  48. sharedbox-0.3.0rc0/vcpkg.json +8 -0
  49. sharedbox-0.2.4/.github/workflows/ci.yaml +0 -119
  50. sharedbox-0.2.4/CHANGELOG.md +0 -37
  51. sharedbox-0.2.4/CMakeLists.txt +0 -102
  52. sharedbox-0.2.4/PKG-INFO +0 -175
  53. sharedbox-0.2.4/README.md +0 -158
  54. sharedbox-0.2.4/docs/readme.md +0 -234
  55. sharedbox-0.2.4/examples/basic_comparison.py +0 -328
  56. sharedbox-0.2.4/examples/concurrent_access.py +0 -415
  57. sharedbox-0.2.4/examples/getting_started.py +0 -285
  58. sharedbox-0.2.4/examples/initialization_demo.py +0 -355
  59. sharedbox-0.2.4/examples/memory_usage.py +0 -438
  60. sharedbox-0.2.4/examples/mixed_data_benchmark.py +0 -407
  61. sharedbox-0.2.4/examples/numpy_performance_benchmark.py +0 -291
  62. sharedbox-0.2.4/examples/shared_cache_example.py +0 -457
  63. sharedbox-0.2.4/install-vcpkg.sh +0 -57
  64. sharedbox-0.2.4/noxfile.py +0 -20
  65. sharedbox-0.2.4/pyproject.toml +0 -83
  66. sharedbox-0.2.4/src/sharedbox/__init__.py +0 -3
  67. sharedbox-0.2.4/src/sharedbox/_core/sharedmemory.cpp +0 -235
  68. sharedbox-0.2.4/src/sharedbox/_core/sharedmemory.hpp +0 -74
  69. sharedbox-0.2.4/src/sharedbox/_shareddict.pyi +0 -40
  70. sharedbox-0.2.4/src/sharedbox/_version.py +0 -34
  71. sharedbox-0.2.4/src/sharedbox/shareddict.cpp +0 -465
  72. sharedbox-0.2.4/src/sharedbox/shareddict.hpp +0 -86
  73. sharedbox-0.2.4/src/sharedbox/utils.py +0 -293
  74. sharedbox-0.2.4/tests/test_initialization_data.py +0 -263
  75. sharedbox-0.2.4/tests/test_mixed_data_types.py +0 -315
  76. sharedbox-0.2.4/tests/test_multiprocess_containers.py +0 -632
  77. sharedbox-0.2.4/tests/test_numpy_support.py +0 -216
  78. sharedbox-0.2.4/uv.lock +0 -328
  79. {sharedbox-0.2.4 → sharedbox-0.3.0rc0}/src/sharedbox/py.typed +0 -0
@@ -0,0 +1,4 @@
1
+ BasedOnStyle: LLVM
2
+ IndentWidth: 4
3
+ ColumnLimit: 115
4
+ AccessModifierOffset: -4
@@ -0,0 +1,176 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+ release:
9
+ types: [ published ]
10
+
11
+ jobs:
12
+ lint:
13
+ name: Lint
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v5
17
+
18
+ # local hooks in prek.toml call `uv run --locked --only-group lint ...`
19
+ - uses: astral-sh/setup-uv@v7
20
+
21
+ - uses: j178/prek-action@4e14d07f9231acabce116ccfca13b13dd9755ece # v3.0.0
22
+ with:
23
+ prek-version: "0.5.3"
24
+
25
+ build_wheels:
26
+ name: Build wheel for ${{ matrix.python }}-${{ matrix.platform_id }}
27
+ runs-on: ${{ matrix.os }}
28
+ strategy:
29
+ fail-fast: false
30
+ matrix:
31
+ include:
32
+ - { os: windows-latest, python: cp311, platform_id: win_amd64 }
33
+ - { os: windows-latest, python: cp312, platform_id: win_amd64 }
34
+ - { os: windows-latest, python: cp314t, platform_id: win_amd64 }
35
+ - { os: ubuntu-latest, python: cp311, platform_id: manylinux_x86_64 }
36
+ - { os: ubuntu-latest, python: cp312, platform_id: manylinux_x86_64 }
37
+ - { os: ubuntu-latest, python: cp314t, platform_id: manylinux_x86_64 }
38
+ - { os: ubuntu-latest, python: cp311, platform_id: musllinux_x86_64 }
39
+ - { os: ubuntu-latest, python: cp312, platform_id: musllinux_x86_64 }
40
+ - { os: ubuntu-latest, python: cp314t, platform_id: musllinux_x86_64 }
41
+
42
+ steps:
43
+ - uses: actions/checkout@v5
44
+ with:
45
+ fetch-depth: 0
46
+ ref: ${{ github.event_name == 'release' && github.event.release.tag_name || '' }}
47
+
48
+ - name: Build wheels
49
+ uses: pypa/cibuildwheel@v3.2.0
50
+ env:
51
+ CIBW_BUILD: ${{ matrix.python }}-${{ matrix.platform_id }}
52
+ CIBW_CONTAINER_ENGINE: "docker; create_args: --shm-size=1g"
53
+ CIBW_TEST_REQUIRES: pytest
54
+ CIBW_TEST_COMMAND: pytest {project}/tests
55
+ with:
56
+ package-dir: .
57
+ output-dir: wheelhouse
58
+
59
+ - name: Upload wheel artifacts
60
+ uses: actions/upload-artifact@v4
61
+ with:
62
+ name: cibw-wheels-${{ matrix.python }}-${{ matrix.platform_id }}
63
+ path: ./wheelhouse/*.whl
64
+
65
+ - name: Measure wheel and extension size
66
+ shell: bash
67
+ run: |
68
+ echo "### Wheel size: ${{ matrix.python }}-${{ matrix.platform_id }}" >> "$GITHUB_STEP_SUMMARY"
69
+ # Run by path, not with -m: importing the sharedbox package loads its
70
+ # compiled extension and psygnal, which this runner does not have installed.
71
+ python src/sharedbox/benchmarks/size.py wheelhouse/*.whl --json wheelhouse/sizes.json --markdown >> "$GITHUB_STEP_SUMMARY"
72
+
73
+ - name: Upload size artifact
74
+ uses: actions/upload-artifact@v4
75
+ with:
76
+ name: wheel-sizes-${{ matrix.python }}-${{ matrix.platform_id }}
77
+ path: wheelhouse/sizes.json
78
+
79
+ sizes:
80
+ name: Report wheel size changes
81
+ runs-on: ubuntu-latest
82
+ needs: [build_wheels]
83
+ permissions:
84
+ contents: read
85
+ actions: read
86
+ continue-on-error: true
87
+ steps:
88
+ - uses: actions/checkout@v5
89
+
90
+ - name: Download this run's size artifacts
91
+ uses: actions/download-artifact@v5
92
+ with:
93
+ pattern: wheel-sizes-*
94
+ path: sizes/this
95
+
96
+ - name: Find the latest successful run on main
97
+ id: baseline
98
+ env:
99
+ GH_TOKEN: ${{ github.token }}
100
+ run: |
101
+ run_id=$(gh run list --repo "${{ github.repository }}" --workflow ci.yaml \
102
+ --branch main --status success --limit 1 --json databaseId --jq '.[0].databaseId // empty')
103
+ echo "run_id=$run_id" >> "$GITHUB_OUTPUT"
104
+
105
+ - name: Download the baseline's size artifacts
106
+ if: steps.baseline.outputs.run_id != ''
107
+ continue-on-error: true
108
+ uses: actions/download-artifact@v5
109
+ with:
110
+ pattern: wheel-sizes-*
111
+ path: sizes/main
112
+ run-id: ${{ steps.baseline.outputs.run_id }}
113
+ github-token: ${{ github.token }}
114
+
115
+ - name: Report size differences
116
+ shell: bash
117
+ # Run by path, not with -m: importing the sharedbox package loads its
118
+ # compiled extension and psygnal, which this runner does not have installed.
119
+ run: python src/sharedbox/benchmarks/size_diff.py sizes/this sizes/main >> "$GITHUB_STEP_SUMMARY"
120
+
121
+ make_sdist:
122
+ name: Make SDist
123
+ runs-on: ubuntu-latest
124
+ needs: [build_wheels]
125
+ if: github.event_name == 'release' && github.event.action == 'published'
126
+ steps:
127
+ - name: Check the release tag and pre-release flag
128
+ env:
129
+ TAG: ${{ github.event.release.tag_name }}
130
+ PRERELEASE: ${{ github.event.release.prerelease }}
131
+ run: |
132
+ if ! [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(rc[0-9]+)?$ ]]; then
133
+ echo "::error::tag $TAG is not vX.Y.Z or vX.Y.ZrcN"; exit 1
134
+ fi
135
+ case "$TAG" in
136
+ *rc*) [ "$PRERELEASE" = "true" ] || { echo "::error::$TAG must be marked pre-release"; exit 1; } ;;
137
+ *) [ "$PRERELEASE" = "false" ] || { echo "::error::$TAG must not be marked pre-release"; exit 1; } ;;
138
+ esac
139
+
140
+ - uses: actions/checkout@v5
141
+ with:
142
+ fetch-depth: 0
143
+ ref: ${{ github.event_name == 'release' && github.event.release.tag_name || '' }}
144
+
145
+ - name: Build SDist
146
+ run: pipx run build --sdist
147
+
148
+ - name: Check the sdist version matches the tag
149
+ env:
150
+ TAG: ${{ github.event.release.tag_name }}
151
+ run: test -f "dist/sharedbox-${TAG#v}.tar.gz"
152
+
153
+ - uses: actions/upload-artifact@v4
154
+ with:
155
+ name: cibw-sdist
156
+ path: dist/*.tar.gz
157
+
158
+ upload_pypi:
159
+ name: Upload to PyPI
160
+ needs: [lint, build_wheels, make_sdist]
161
+ runs-on: ubuntu-latest
162
+ environment:
163
+ name: pypi
164
+ url: https://pypi.org/p/sharedbox
165
+ permissions:
166
+ id-token: write
167
+ if: github.event_name == 'release' && github.event.action == 'published'
168
+ steps:
169
+ - uses: actions/download-artifact@v5
170
+ with:
171
+ # unpacks all CIBW artifacts into dist/
172
+ pattern: cibw-*
173
+ path: dist
174
+ merge-multiple: true
175
+
176
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,39 @@
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
+ - name: Set up vcpkg at the manifest baseline
24
+ run: |
25
+ git clone https://github.com/microsoft/vcpkg.git "${{ runner.temp }}/vcpkg"
26
+ git -C "${{ runner.temp }}/vcpkg" checkout "$(jq -r '."builtin-baseline"' vcpkg.json)"
27
+ "${{ runner.temp }}/vcpkg/bootstrap-vcpkg.sh" -disableMetrics
28
+ echo "VCPKG_ROOT=${{ runner.temp }}/vcpkg" >> "$GITHUB_ENV"
29
+
30
+ - uses: astral-sh/setup-uv@v7
31
+
32
+ - name: Install the project
33
+ run: uv sync --dev --frozen --python 3.12
34
+
35
+ - name: Run benchmarks
36
+ uses: CodSpeedHQ/action@v5
37
+ with:
38
+ mode: simulation
39
+ run: uv run --no-sync pytest benchmarks --codspeed
@@ -17,5 +17,3 @@ wheels/
17
17
  src/sharedbox/_version.py
18
18
 
19
19
  wheelhouse/
20
-
21
- .nox/
@@ -0,0 +1,55 @@
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] - Unreleased
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
+
25
+ ### Changed
26
+
27
+ - Building the extension requires nanobind 3.1.0 or newer.
28
+ - Wheels per platform: `cp311-cp311`, `cp312-abi3` for CPython 3.12 and
29
+ newer, and `cp314-cp314t` for free-threaded CPython 3.14.
30
+ - Boost is installed through a vcpkg manifest pinned to one baseline.
31
+
32
+ ### Removed
33
+
34
+ - Python 3.10 support.
35
+ - `SharedDict` and `sharedbox.utils`.
36
+
37
+ ## [0.2.4] - 05-10-2025
38
+
39
+ ### Changed
40
+
41
+ - Rewrite codebase in nanobind
42
+
43
+ ### Fixed
44
+
45
+ - Parallelize CI so that each wheel is built with the correct version
46
+ - Also faster builds
47
+
48
+ ## [0.1.0] - 29-09-2025
49
+
50
+ ### Added
51
+
52
+ - Initial release
53
+
54
+ [0.2.4]: (https://github.com/jacopoabramo/sharedbox/compare/0.1.0...0.2.4)
55
+ [0.1.0]: (https://github.com/jacopoabramo/sharedbox/commits/0.1.0)
@@ -0,0 +1,193 @@
1
+ # CLAUDE.md
2
+
3
+ `sharedbox` gives Python processes a record that lives in shared memory.
4
+ `SharedBox` is a base class: a subclass's annotated fields are stored in one
5
+ named segment that every process can open. The segment is C++
6
+ (Boost.Interprocess) exposed to Python with nanobind; the Python side decides
7
+ the layout and encodes values.
8
+
9
+ ## Repository layout
10
+
11
+ ```text
12
+ sharedbox/
13
+ |-- src/sharedbox/
14
+ | |-- __init__.py re-exports the public API
15
+ | |-- _box.py SharedBox: class keywords, fields, create/attach, update, snapshot, unlink
16
+ | |-- _layout.py Capacity, field offsets and encoding, schema hash
17
+ | |-- _events.py FieldWatch, the watcher thread, psygnal events
18
+ | |-- _native.pyi hand-written stub for the extension
19
+ | |-- py.typed
20
+ | |-- benchmarks/ the benchbox command (extra: benchmarks)
21
+ | | |-- cli.py entry point; exits with an install hint without Typer
22
+ | | |-- _app.py Typer commands: ops, roundtrip, size, all
23
+ | | |-- ops.py pyperf timings of single operations, against the stdlib
24
+ | | |-- roundtrip.py cross-process round trip percentiles
25
+ | | |-- size.py wheel and extension size (standard library only)
26
+ | | `-- size_diff.py wheel size table against main, for CI (standard library only)
27
+ | `-- _native/
28
+ | |-- module.cpp nanobind module: Segment and the error classes
29
+ | |-- segment.{hpp,cpp} the segment: header, record, sequence lock
30
+ | `-- notifier.{hpp,cpp} wakes waiters across processes (futex, named semaphore)
31
+ |-- tests/ pytest; many tests spawn processes
32
+ | `-- type_checks/ checked by mypy, never imported at run time
33
+ |-- benchmarks/ not collected by the default pytest run
34
+ | `-- test_bench_box.py pytest-codspeed benchmarks, run by codspeed.yml
35
+ |-- scripts/vscode_setup.py points VS Code's C/C++ extension at the build headers
36
+ |-- docs/api.md API reference
37
+ |-- docs/design/ design notes for the native segment
38
+ |-- .github/workflows/ci.yaml cibuildwheel wheels, tests, PyPI publish
39
+ |-- .github/workflows/codspeed.yml benchmarks on CodSpeed
40
+ |-- CMakeLists.txt extension build
41
+ |-- vcpkg.json Boost dependency and vcpkg baseline
42
+ |-- stubtest-allowlist.txt stubtest exceptions for nanobind types
43
+ |-- .clang-format clang-format style for src/sharedbox/_native/
44
+ |-- prek.toml prek hooks: ruff, clang-format, and the builtin checks
45
+ |-- pyproject.toml scikit-build-core, setuptools-scm, pytest, cibuildwheel, mypy, tox
46
+ `-- uv.lock
47
+ ```
48
+
49
+ - `_native.pyi` is written by hand. Update it whenever the bound API changes;
50
+ the tox `mypy` env runs stubtest against it.
51
+
52
+ ## Memory layout
53
+
54
+ The design and its reasons are in `docs/design/native-segment.md`.
55
+
56
+ One segment per box: `managed_windows_shared_memory` on Windows (page-file
57
+ backed), `managed_shared_memory` on Linux (`/dev/shm`, mode `0600`). The
58
+ name is the `name` class keyword, the name passed to `create()`, or by
59
+ default `sharedbox-` plus 16 hex digits of SHA-256 over the class's
60
+ `module.qualname` (`__mp_main__` counts as `__main__`). Creating always asks
61
+ for a new name and raises `SegmentExistsError` if it is taken.
62
+
63
+ The segment holds a named object `"sharedbox.header"` (64 bytes, one cache
64
+ line) and one block with the tail and the record. The tail is `field_count`
65
+ `StoredField` entries of 8 bytes (`u32 offset`, `u32 capacity_and_kind`, top
66
+ bit set for a prefixed field), then one `u64` write count per field. The
67
+ record follows, 64-byte aligned. The segment size is these plus 1024 bytes
68
+ for Boost's bookkeeping, rounded up to 4 KiB. `static_assert`s in
69
+ `segment.cpp` check every `sizeof` and `offsetof`.
70
+
71
+ `Header` fields:
72
+
73
+ - `magic`: written last on create; `attach()` waits for it.
74
+ - `abi_version`: `2`; any other value is refused. `magic` and `abi_version`
75
+ keep their offsets across versions.
76
+ - `field_count`, `record_size`.
77
+ - `schema_hash`: first 8 bytes of SHA-256 over the class identity and each
78
+ field's `name:kind:capacity`. `attach()` raises `SchemaMismatchError` if
79
+ it differs.
80
+ - `record`, `tail`: `u32` offsets of the record and the tail in the segment.
81
+ `attach()` checks both against the segment size, checks each field table
82
+ entry and keeps its own copy.
83
+ - `seq`: sequence lock. Even when free, odd while a write runs. Readers copy
84
+ and retry if `seq` moved; writers take it with a compare-and-swap and give
85
+ up after `lock_timeout` with `LockTimeoutError`.
86
+ - `generation`: counts every write. `wake_word` and `waiters` wake threads
87
+ that wait for a change.
88
+ - `writer_pid`: process holding the write lock, cleared by a normal unlock.
89
+ `force_unlock()` releases the lock and leaves `writer_pid` as it is.
90
+
91
+ ### Record encoding
92
+
93
+ Fields in declaration order, base classes first, each starting at a multiple
94
+ of 8 bytes. Everything is little-endian.
95
+
96
+ - `bool`: 1 byte, `0x00` or `0x01`.
97
+ - `int`: 8 bytes, signed.
98
+ - `float`: 8 bytes, IEEE 754 double.
99
+ - `str`, `bytes`: `u32` length, then up to `capacity` bytes (UTF-8 for `str`).
100
+
101
+ Nothing is pickled. At most 256 fields; a capacity is 1 byte to 1 MiB.
102
+
103
+ ### Lifecycle
104
+
105
+ - `close()` stops the watcher thread and detaches this box. Later reads and
106
+ writes raise `BoxClosedError`. Garbage collection closes a box too.
107
+ - `unlink()` removes the name on Linux and does nothing on Windows, where the
108
+ OS frees the segment with its last handle.
109
+ - A Linux segment that is never unlinked stays in `/dev/shm`. Most tests use the
110
+ `unique_name` fixture, which removes the file afterwards.
111
+
112
+ ## Build
113
+
114
+ scikit-build-core drives CMake (3.30 or newer), which needs:
115
+
116
+ - `VCPKG_ROOT` set; CMake stops without it.
117
+ - Boost from `vcpkg.json`, installed by CMake on the first build.
118
+ - nanobind (build dependency) and a C++17 compiler (MSVC or GCC). macOS is
119
+ not supported.
120
+
121
+ The version comes from git tags through setuptools-scm, written to
122
+ `src/sharedbox/_version.py` (ignored by git).
123
+
124
+ ```sh
125
+ uv sync --dev # creates .venv, builds and installs the extension
126
+ uv run python scripts/vscode_setup.py # once, for VS Code's C/C++ extension
127
+ ```
128
+
129
+ `[tool.uv] cache-keys` lists the C++ sources, so `uv sync` rebuilds the
130
+ extension after they change. Build folders are `build/<wheel tag>`.
131
+
132
+ Wheels per platform (Windows x64, Linux x86_64 glibc and musl): `cp311-cp311`,
133
+ `cp312-abi3` for CPython 3.12 and newer, and `cp314-cp314t` for free-threaded
134
+ CPython 3.14. One `nanobind_add_module(... STABLE_ABI FREE_THREADED ...)` line
135
+ produces all three.
136
+
137
+ ## Tests
138
+
139
+ ```sh
140
+ uv run pytest # current interpreter
141
+ uv run pytest tests/test_box.py -k pickle
142
+ uv run tox # py311 to py314, py314t, mypy
143
+ uv run tox -e py314t # one env
144
+ ```
145
+
146
+ CI (`.github/workflows/ci.yaml`) builds the wheels above with cibuildwheel,
147
+ runs pytest against each wheel, and publishes to PyPI. Publishing runs only
148
+ from a GitHub release tagged `vX.Y.Z` and marked as a release, or
149
+ `vX.Y.ZrcN` and marked as a pre-release; any other tag or mismatch between
150
+ the tag and the pre-release flag fails the build before it uploads. Docker
151
+ runs with `--shm-size=1g`, so keep test segments under that.
152
+
153
+ ## Usage
154
+
155
+ ```python
156
+ import multiprocessing as mp
157
+ from typing import Annotated
158
+
159
+ from sharedbox import Capacity, SharedBox
160
+
161
+
162
+ class Motor(SharedBox):
163
+ position: int
164
+ enabled: bool
165
+ label: Annotated[str, Capacity(32)]
166
+
167
+
168
+ def worker() -> None:
169
+ motor = Motor.attach() # finds the box by its class
170
+ motor.position = 10
171
+ motor.close()
172
+
173
+
174
+ if __name__ == "__main__":
175
+ with Motor(1, False, "x-axis") as motor:
176
+ motor.events.position.connect(lambda new, old: print(old, "->", new))
177
+ child = mp.Process(target=worker)
178
+ child.start()
179
+ child.join() # prints: 1 -> 10
180
+ Motor.unlink()
181
+ ```
182
+
183
+ Every read decodes a fresh value from the segment. `update(**values)` writes
184
+ several fields at once; `watch(field)` and `events` report changes from any
185
+ process.
186
+
187
+ ## Conventions
188
+
189
+ - Changelog: `CHANGELOG.md`, Keep a Changelog format, dates as `DD-MM-YYYY`.
190
+ - Lint and format Python with `ruff` and C++ with `clang-format`, both run
191
+ through prek: `uv run prek run --all-files`, `uv run tox -e lint`.
192
+ - Change dependencies with `uv add` / `uv remove`, never by editing
193
+ `pyproject.toml`.
@@ -0,0 +1,41 @@
1
+ cmake_minimum_required(VERSION 3.30...4.4)
2
+
3
+ if(NOT DEFINED ENV{VCPKG_ROOT})
4
+ message(FATAL_ERROR "Set VCPKG_ROOT to a vcpkg checkout.")
5
+ endif()
6
+ set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
7
+ CACHE STRING "vcpkg toolchain file")
8
+
9
+ project(${SKBUILD_PROJECT_NAME} LANGUAGES CXX)
10
+
11
+ set(CMAKE_CXX_STANDARD 17)
12
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
13
+
14
+ find_package(Python 3.11 REQUIRED
15
+ COMPONENTS Interpreter Development.Module
16
+ OPTIONAL_COMPONENTS Development.SABIModule)
17
+ execute_process(
18
+ COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
19
+ OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT)
20
+ find_package(nanobind CONFIG REQUIRED)
21
+ find_package(Boost REQUIRED CONFIG)
22
+ find_package(Threads REQUIRED)
23
+
24
+ nanobind_add_module(_native
25
+ STABLE_ABI
26
+ FREE_THREADED
27
+ LTO
28
+ NB_DOMAIN sharedbox
29
+ src/sharedbox/_native/module.cpp
30
+ src/sharedbox/_native/segment.cpp
31
+ src/sharedbox/_native/notifier.cpp)
32
+ target_link_libraries(_native PRIVATE Boost::headers)
33
+
34
+ if(WIN32)
35
+ target_compile_definitions(_native PRIVATE
36
+ BOOST_ALL_NO_LIB WIN32_LEAN_AND_MEAN NOMINMAX _WIN32_WINNT=0x0A00)
37
+ else()
38
+ target_link_libraries(_native PRIVATE rt Threads::Threads)
39
+ endif()
40
+
41
+ install(TARGETS _native LIBRARY DESTINATION sharedbox)
@@ -198,4 +198,4 @@
198
198
  distributed under the License is distributed on an "AS IS" BASIS,
199
199
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
200
  See the License for the specific language governing permissions and
201
- limitations under the License.
201
+ limitations under the License.