bend-python 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,225 @@
1
+ import Base
2
+
3
+ # Specification of the C driver's thread-state and lease protocol, not
4
+ # verification of C. Attached means a valid CPython thread state (and the GIL
5
+ # on GIL builds). Free-threaded Python still requires attachment.
6
+ #
7
+ # The native states cover bp_native between setting and clearing bp_current:
8
+ # Bend evaluation, where the C bridge makes no Python API call. A native
9
+ # failure longjmps back inside that window, so it also leaves via Leave.
10
+ type State is Data:
11
+ Attached{}
12
+ Detached{}
13
+ AttachedNative{}
14
+ DetachedNative{}
15
+
16
+ # Native runtime health: ordinary Python exceptions do not mark a context failed.
17
+ type Outcome is Data:
18
+ Succeeded{}
19
+ Failed{}
20
+
21
+ # A Python effect includes reference-count changes and exception operations.
22
+ type Operation is Data:
23
+ Save{}
24
+ Restore{}
25
+ Enter{}
26
+ Leave{outcome: Outcome}
27
+ PythonEffect{}
28
+ AcquireLease{slot: Nat}
29
+ ReturnLease{slot: Nat, outcome: Outcome, small_heap: Bool, cache_space: Bool}
30
+
31
+ def is_native(state: State) -> Bool:
32
+ match state:
33
+ case AttachedNative{}:
34
+ True{}
35
+ case DetachedNative{}:
36
+ True{}
37
+ case _:
38
+ False{}
39
+
40
+ def is_leave(operation: Operation) -> Bool:
41
+ match operation:
42
+ case Leave{_}:
43
+ True{}
44
+ case _:
45
+ False{}
46
+
47
+ # Attachment and native transitions. Only step_session handles lease operations.
48
+ def step(state: State, operation: Operation) -> Maybe<State>:
49
+ match state operation:
50
+ case Attached{} Save{}:
51
+ Some{Detached{}}
52
+ case Detached{} Restore{}:
53
+ Some{Attached{}}
54
+ case Attached{} PythonEffect{}:
55
+ Some{Attached{}}
56
+ case Attached{} Enter{}:
57
+ Some{AttachedNative{}}
58
+ case Detached{} Enter{}:
59
+ Some{DetachedNative{}}
60
+ case AttachedNative{} Leave{_}:
61
+ Some{Attached{}}
62
+ case DetachedNative{} Leave{_}:
63
+ Some{Detached{}}
64
+ case _ _:
65
+ None{}
66
+
67
+ def run(operations: List<Operation>, state: State) -> Maybe<State>:
68
+ match operations:
69
+ case []:
70
+ Some{state}
71
+ case operation <> tail:
72
+ Maybe.bind(&1, State, State, step(state, operation), next => run(tail, next))
73
+
74
+ # C detaches during native work when the export asks to, and always on
75
+ # free-threaded builds (Py_GIL_DISABLED), where staying attached only stalls
76
+ # stop-the-world pauses.
77
+ def detaches(release_gil: Bool, free_threaded: Bool) -> Bool:
78
+ release_gil || free_threaded
79
+
80
+ def native_call(detach: Bool, outcome: Outcome) -> List<Operation>:
81
+ match detach:
82
+ case True{}:
83
+ [Save{}, Enter{}, Leave{outcome}, Restore{}]
84
+ case False{}:
85
+ [Enter{}, Leave{outcome}]
86
+
87
+ # Each slot denotes one distinct runtime allocation. The C cache must preserve
88
+ # that identity and perform acquisition/return atomically; these are trust inputs.
89
+ type Context is Data:
90
+ Idle{}
91
+ Leased{}
92
+ Discarded{}
93
+
94
+ def eligible(context: Maybe<&2, Context>) -> Bool:
95
+ match context:
96
+ case Some{Idle{}}:
97
+ True{}
98
+ case _:
99
+ False{}
100
+
101
+ def available(pool: List<&2, Context>, slot: Nat) -> Bool:
102
+ eligible(List.get(&2, Context, pool, slot))
103
+
104
+ def leased(context: Maybe<&2, Context>) -> Bool:
105
+ match context:
106
+ case Some{Leased{}}:
107
+ True{}
108
+ case _:
109
+ False{}
110
+
111
+ def returnable(pool: List<&2, Context>, slot: Nat) -> Bool:
112
+ leased(List.get(&2, Context, pool, slot))
113
+
114
+ def reserve(pool: List<&2, Context>, slot: Nat) -> List<&2, Context>:
115
+ List.set(&2, Context, pool, slot, Leased{})
116
+
117
+ def acquire_if(allowed: Bool, pool: List<&2, Context>, slot: Nat) -> Maybe<List<&2, Context>>:
118
+ match allowed:
119
+ case True{}:
120
+ Some{reserve(pool, slot)}
121
+ case False{}:
122
+ None{}
123
+
124
+ def acquire(+pool: List<&2, Context>, +slot: Nat) -> Maybe<List<&2, Context>>:
125
+ acquire_if(available(pool, slot), pool, slot)
126
+
127
+ # C's bp_runtime_release: an instance is reusable when it is not poisoned (no
128
+ # native failure) and its heap stayed within 32 MiB; it is cached when it is
129
+ # reusable and fewer than eight instances are idle. Otherwise it is unmapped.
130
+ def completion(outcome: Outcome, small_heap: Bool, cache_space: Bool) -> Context:
131
+ match outcome small_heap cache_space:
132
+ case Succeeded{} True{} True{}:
133
+ Idle{}
134
+ case _ _ _:
135
+ Discarded{}
136
+
137
+ def finish(pool: List<&2, Context>, slot: Nat, outcome: Outcome, small_heap: Bool, cache_space: Bool) -> List<&2, Context>:
138
+ List.set(&2, Context, pool, slot, completion(outcome, small_heap, cache_space))
139
+
140
+ def finish_if(allowed: Bool, pool: List<&2, Context>, slot: Nat, outcome: Outcome, small_heap: Bool, cache_space: Bool) -> Maybe<List<&2, Context>>:
141
+ match allowed:
142
+ case True{}:
143
+ Some{finish(pool, slot, outcome, small_heap, cache_space)}
144
+ case False{}:
145
+ None{}
146
+
147
+ # Only a leased slot may be returned; a double return is rejected.
148
+ def release(+pool: List<&2, Context>, +slot: Nat, outcome: Outcome, small_heap: Bool, cache_space: Bool) -> Maybe<List<&2, Context>>:
149
+ finish_if(returnable(pool, slot), pool, slot, outcome, small_heap, cache_space)
150
+
151
+ # The combined model: one invocation's thread state together with the pool.
152
+ type Session is Data:
153
+ Session{state: State, pool: List<&2, Context>}
154
+
155
+ def detached_with(pool: Maybe<List<&2, Context>>) -> Maybe<Session>:
156
+ match pool:
157
+ case Some{next}:
158
+ Some{Session{Detached{}, next}}
159
+ case None{}:
160
+ None{}
161
+
162
+ def with_pool(state: Maybe<State>, pool: List<&2, Context>) -> Maybe<Session>:
163
+ match state:
164
+ case Some{next}:
165
+ Some{Session{next, pool}}
166
+ case None{}:
167
+ None{}
168
+
169
+ # The pool mutex is taken only while detached; lease operations in any other
170
+ # state are rejected.
171
+ def step_session(session: Session, operation: Operation) -> Maybe<Session>:
172
+ match session operation:
173
+ case Session{Detached{}, pool} AcquireLease{slot}:
174
+ detached_with(acquire(pool, slot))
175
+ case Session{Detached{}, pool} ReturnLease{slot, outcome, small_heap, cache_space}:
176
+ detached_with(release(pool, slot, outcome, small_heap, cache_space))
177
+ case Session{_, _} AcquireLease{_}:
178
+ None{}
179
+ case Session{_, _} ReturnLease{_, _, _, _}:
180
+ None{}
181
+ case Session{state, pool} operation:
182
+ with_pool(step(state, operation), pool)
183
+
184
+ def run_session(operations: List<Operation>, session: Session) -> Maybe<Session>:
185
+ match operations:
186
+ case []:
187
+ Some{session}
188
+ case operation <> tail:
189
+ Maybe.bind(&1, Session, Session, step_session(session, operation), next => run_session(tail, next))
190
+
191
+ # One invocation owns its context throughout native work and attached effects.
192
+ # Only cache acquire/return takes a shared lock; evaluation has no shared mutex.
193
+ def acquire_call(slot: Nat) -> List<Operation>:
194
+ [Save{}, AcquireLease{slot}, Restore{}]
195
+
196
+ def return_call(slot: Nat, outcome: Outcome, small_heap: Bool, cache_space: Bool) -> List<Operation>:
197
+ [Save{}, ReturnLease{slot, outcome, small_heap, cache_space}, Restore{}]
198
+
199
+ # A lease is healthy only if every native step succeeded; one failure poisons it.
200
+ def health(outcomes: List<&2, Outcome>) -> Outcome:
201
+ match outcomes:
202
+ case []:
203
+ Succeeded{}
204
+ case Succeeded{} <> tail:
205
+ health(tail)
206
+ case Failed{} <> _:
207
+ Failed{}
208
+
209
+ # bp_run's loop: each native step (start, resume, or dropping the continuation
210
+ # after a Python exception) is followed by attached Python work: performing the
211
+ # requested effect, or raising. C stops after the first native failure; the
212
+ # model also allows later rounds, so its laws cover that too.
213
+ def rounds(+detach: Bool, outcomes: List<&2, Outcome>) -> List<Operation>:
214
+ match outcomes:
215
+ case []:
216
+ []
217
+ case outcome <> tail:
218
+ List.append(&1, Operation, native_call(detach, outcome), PythonEffect{} <> rounds(detach, tail))
219
+
220
+ # bp_run: lease, run any number of rounds, then return the lease (marked by
221
+ # the rounds' health) before releasing the arena.
222
+ def invocation(+slot: Nat, detach: Bool, +outcomes: List<&2, Outcome>, small_heap: Bool, cache_space: Bool) -> List<Operation>:
223
+ List.append(&1, Operation, acquire_call(slot),
224
+ List.append(&1, Operation, rounds(detach, outcomes),
225
+ return_call(slot, health(outcomes), small_heap, cache_space)))
bend_python/library.py ADDED
@@ -0,0 +1,52 @@
1
+ """Find or vendor the Bend library shipped with the Python build helper."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+ import shutil
6
+
7
+
8
+ def library_path():
9
+ """Return the directory containing python.bend, python.c, and their laws."""
10
+ installed = Path(__file__).with_name("lib")
11
+ checkout = Path(__file__).resolve().parents[2] / "bend"
12
+ for candidate in (installed, checkout):
13
+ if (candidate / "python.bend").is_file() and (candidate / "python.c").is_file():
14
+ return candidate
15
+ raise RuntimeError("The bend-python installation is missing its Bend library")
16
+
17
+
18
+ def get_include():
19
+ """Return the library directory as a string, for build-tool compatibility."""
20
+ return str(library_path())
21
+
22
+
23
+ def vendor(destination="bend", *, force=False):
24
+ """Copy the library into a project for Bend's relative imports.
25
+
26
+ Existing identical files are accepted. Different files are preserved unless
27
+ ``force=True``; unrelated files in the directory are always preserved. A
28
+ symlink is never written through: it must already open the library file, or
29
+ it is a conflict that ``force=True`` replaces with a regular file.
30
+ """
31
+ source = library_path()
32
+ target = Path(destination)
33
+ files = sorted(path for path in source.iterdir() if path.suffix in {".bend", ".c"})
34
+
35
+ def current(path):
36
+ existing = target / path.name
37
+ if existing.is_symlink():
38
+ return existing.resolve() == path.resolve()
39
+ return existing.is_file() and existing.read_bytes() == path.read_bytes()
40
+
41
+ stale = [path for path in files if not current(path)]
42
+ conflicts = [target / path.name for path in stale if os.path.lexists(target / path.name)]
43
+ if conflicts and not force:
44
+ names = ", ".join(str(path) for path in conflicts)
45
+ raise FileExistsError(f"Refusing to replace modified library files: {names}; use --force")
46
+ target.mkdir(parents=True, exist_ok=True)
47
+ for path in stale:
48
+ destination_file = target / path.name
49
+ if destination_file.is_symlink():
50
+ destination_file.unlink()
51
+ shutil.copyfile(path, destination_file)
52
+ return target.resolve()
@@ -0,0 +1,262 @@
1
+ Metadata-Version: 2.4
2
+ Name: bend-python
3
+ Version: 0.1.0
4
+ Summary: Write CPython extensions in Bend 2, with laws proved at build time
5
+ Author: Lucas Wiman
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/lucaswiman/bend-python
8
+ Project-URL: Source, https://github.com/lucaswiman/bend-python
9
+ Project-URL: Issues, https://github.com/lucaswiman/bend-python/issues
10
+ Project-URL: Changelog, https://github.com/lucaswiman/bend-python/blob/main/CHANGELOG.md
11
+ Keywords: bend,extension,native,setuptools,proof,dependent types
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Framework :: Setuptools Plugin
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: C
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta
25
+ Classifier: Programming Language :: Python :: Implementation :: CPython
26
+ Classifier: Topic :: Software Development :: Build Tools
27
+ Classifier: Topic :: Software Development :: Compilers
28
+ Requires-Python: >=3.10
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENSE
31
+ Dynamic: license-file
32
+
33
+ # bend-python
34
+
35
+ Write CPython extensions in [Bend 2](https://github.com/bendlang/bend), a pure,
36
+ dependently typed language that compiles to native code, and state laws about
37
+ your code that the build **proves before it compiles**. A false law is a build
38
+ error, not a failing test.
39
+
40
+ ```bend
41
+ # fast.bend
42
+ import Base
43
+ import ./bend/python.bend as Python
44
+
45
+ def square(+x: U32) -> U32:
46
+ (x * x : U32)
47
+
48
+ def main() -> IO(Unit):
49
+ Python.export_u32(~(x => square(x)), "square", True{})
50
+ ```
51
+
52
+ ```bend
53
+ # LAWS.bend: the claim
54
+ import Base
55
+ import ./fast.bend as Fast
56
+
57
+ law square_wraps:
58
+ {Fast.square(65536) == 0 : U32}
59
+ ```
60
+
61
+ ```bend
62
+ # PROOF.bend: its proof, checked by every build
63
+ import Base
64
+ import ./LAWS.bend as Laws
65
+
66
+ def Laws.square_wraps():
67
+ {==}
68
+ ```
69
+
70
+ ```python
71
+ >>> import fast
72
+ >>> fast.square(12)
73
+ 144
74
+ >>> fast.square(2**16) # U32 arithmetic wraps, as the law says
75
+ 0
76
+ >>> fast.square(-1)
77
+ OverflowError: can't convert negative value to unsigned int
78
+ ```
79
+
80
+ `True{}` releases the GIL while Bend computes, so threads calling `square` run
81
+ in parallel. Installed wheels need neither Bend nor a compiler.
82
+
83
+ **Status: experimental.** Linux x86_64, CPython 3.10–3.14 including free-threaded
84
+ 3.14t, Bend pinned to 2.0.28. Bend 2 is young: its strings are linked lists of
85
+ characters, so text-heavy code is slow and memory-hungry. Benchmark before
86
+ relying on it.
87
+
88
+ ## Build an extension
89
+
90
+ You need Clang and the `bend-python` SDK in your build environment. The first
91
+ build downloads the pinned Bend compiler (checksum-verified, cached under
92
+ `~/.cache/bend-python`); set `BEND` to use your own, or `BEND_PYTHON_DOWNLOAD=0`
93
+ to forbid the download.
94
+
95
+ `fast.bend` imports `./bend/python.bend`. If that directory is missing, the build
96
+ copies the SDK's Bend library there; `python -m bend_python vendor bend` does
97
+ the same by hand, for editors and `bend --check-only`.
98
+
99
+ `setup.py` (setuptools is the only supported build backend):
100
+
101
+ ```python
102
+ from setuptools import setup
103
+ from bend_python import BendBuildExt, BendExtension
104
+
105
+ setup(
106
+ name="fast",
107
+ ext_modules=[BendExtension("fast", "fast.bend", proofs=["PROOF.bend"])],
108
+ cmdclass={"build_ext": BendBuildExt},
109
+ )
110
+ ```
111
+
112
+ `pyproject.toml`:
113
+
114
+ ```toml
115
+ [build-system]
116
+ requires = ["setuptools>=80", "bend-python==0.1.0"]
117
+ build-backend = "setuptools.build_meta"
118
+ ```
119
+
120
+ Then `python -m build` or `pip install .`. The build proves the library's laws
121
+ and yours, generates C, and compiles it; a false law ends the build with
122
+ `error: Bend proof check failed: PROOF.bend` after Bend's diagnostic. Dotted
123
+ names such as `mypackage._native` work. Ship your `.bend` files in the sdist
124
+ (`include *.bend` in `MANIFEST.in`).
125
+ [`examples/`](https://github.com/lucaswiman/bend-python/tree/main/examples) is a
126
+ complete project built this way.
127
+
128
+ ## The Python interface
129
+
130
+ An export receives a `Python.Call` (positional arguments and a kwargs dict) and
131
+ returns `IO(Python.Object)`. A `Python.Object` is a handle to any Python object:
132
+ passing one through Bend preserves identity, aliases, cycles and precision,
133
+ with no serialization.
134
+
135
+ ```bend
136
+ def echo(request: Python.Call) -> IO(Python.Object):
137
+ Python.unary(request) # exactly one positional argument
138
+
139
+ # in main: Python.export("echo", echo, False{})
140
+ ```
141
+
142
+ | Bend API | Does |
143
+ |---|---|
144
+ | `export(name, function, release_gil)` | Expose `Call -> IO(Object)`; `function` must not capture variables |
145
+ | `export_u32` / `export_f32` / `export_bool` / `export_string` / `export_binary_u32` | Typed exports from a template `~f`: argument checks and conversions included. Binary templates are curried: `~(x => y => add(x, y))` |
146
+ | `unary`, `binary` | Require exactly one/two positional arguments and no keywords |
147
+ | `to_u32` `from_u32` `to_f32` `from_f32` `to_bool` `from_bool` `to_string` `from_string` `from_nat` | Convert between Python objects and Bend values |
148
+ | `to_bytes`, `from_bytes` | Any contiguous buffer (`bytes`, `bytearray`, `memoryview`, ...) to `List<U32>` of bytes, and back to `bytes` |
149
+ | `truthy` | Python truthiness, as `bool(value)` |
150
+ | `get_item` `set_item` `getattr` `len` | The Python operations, raising Python's exceptions |
151
+ | `tuple` `list` `dict` `empty_dict` `none` | Build Python objects |
152
+ | `call(f, args, kwargs)`, `invoke(f, arguments)` | Call Python, including callbacks that reenter Bend |
153
+ | `builtins(name)`, `construct(name, arguments)` | Look up or call a builtin such as `"int"` or `"dict"` |
154
+ | `import_module(name)` | Import a module, such as `"operator"` |
155
+
156
+ Conversions are strict. `to_u32` accepts only `int` in `[0, 2**32)`, not `bool`;
157
+ `to_bool` only `bool` (use `truthy` for anything else). `to_f32` accepts only
158
+ `float` and rounds to single precision (overflowing to infinity). Strings convert
159
+ codepoint by codepoint, lone surrogates included; they are linked lists in Bend,
160
+ so prefer `to_bytes` for binary data.
161
+ `to_bytes` snapshots a direct `bytearray` while holding its lock on free-threaded
162
+ Python. For other mutable buffer exporters, including memoryviews of mutable
163
+ storage, callers must prevent concurrent writes while conversion runs.
164
+ Wrong types raise `TypeError`, out-of-range values `OverflowError`, and wrong
165
+ arity or unexpected keywords in typed exports `TypeError`. Python exceptions
166
+ raised inside a call propagate unchanged.
167
+
168
+ Handles are valid only during the call that created them. The bridge seals each
169
+ handle with a per-call key and rejects invalid decoded handles with `ValueError`.
170
+ This detects accidental fabrication or reuse from another call probabilistically;
171
+ it is not an absolute guarantee or a security boundary.
172
+
173
+ ## Threads and the GIL
174
+
175
+ Every call runs in its own Bend runtime instance (heap, stack and allocator),
176
+ so threads call the same extension concurrently, and callbacks can reenter it.
177
+ The third argument to `export` sets the GIL policy: `True{}` releases the GIL
178
+ during pure Bend evaluation, `False{}` holds it. Free-threaded builds always
179
+ detach during evaluation and never enable the GIL. The bridge reattaches before
180
+ every Python operation. Each call uses one CPU core; Bend's own parallel
181
+ scheduler and GPU backends are not enabled.
182
+
183
+ ## What is proved, and what is trusted
184
+
185
+ The build refuses to compile unless every law checks:
186
+
187
+ - **Argument checks.** Typed exports accept exactly the right number of
188
+ arguments and request a `TypeError` for every other count
189
+ ([`bend/LAWS.bend`](https://github.com/lucaswiman/bend-python/blob/main/bend/LAWS.bend)).
190
+ - **Thread protocol.** A model of the C driver proves that Python operations
191
+ happen only while attached and never during native evaluation, that every
192
+ call returns attached (failures included), and that free-threaded builds
193
+ detach ([`bend/THREAD_LAWS.bend`](https://github.com/lucaswiman/bend-python/blob/main/bend/THREAD_LAWS.bend)).
194
+ - **Runtime leases.** In the same model, a call with any number of evaluation
195
+ rounds owns one instance and changes no other, a lease cannot be returned
196
+ twice, a native failure in any round poisons it, and only healthy, small
197
+ instances are cached for reuse.
198
+
199
+ Each model was mutation-tested: breaking any of these rules fails a law. The
200
+ laws are about models and pure Bend code. **Nothing proves that the C bridge
201
+ implements the model**, or anything about CPython, reference counting, or the
202
+ generated runtime; those are covered by tests. The table maps each invariant to
203
+ its evidence:
204
+
205
+ | Invariant | Evidence |
206
+ |---|---|
207
+ | Python API only while attached, never during evaluation | Model proved (`native_effect_rejected`, `native_only_leaves`); C asserts attachment (and the GIL on GIL builds) at every boundary |
208
+ | Every call returns attached, including after native failures | Model proved for any slot, rounds and outcomes (`native_returns_attached`, `invocation_returns_attached`); C's `setjmp` placement trusted |
209
+ | One call per runtime instance | Model proved (`acquisition_requires_available`, `leased_invocation_rejected`, `double_return_rejected`); C's pool mutex trusted; concurrency test |
210
+ | A native failure affects only its instance | Model proved (`failure_isolated`); C unmaps poisoned instances; test |
211
+ | Failed or large instances are never reused | Model proved (`failure_poisons_lease`, `reusable_requires_*`); test for memory release |
212
+ | Wrong arity is a `TypeError` | Proved for the parsers and wrappers (`*_exact_arity`, `*_complete`, `*_rejected`); keyword rejection tested |
213
+ | Accidental forged or stale handles are detected | Sealing in C; tests. Rejection is probabilistic: 32-bit collisions remain possible. Hostile Bend code can import its own C |
214
+ | Exports cannot capture variables | Checked by C at import; test |
215
+ | Objects cross unchanged; conversions are strict | C; tests |
216
+
217
+ ## Limits
218
+
219
+ - Main interpreter only; subinterpreters are rejected.
220
+ - A native Bend failure (such as `Nat` overflow) raises `RuntimeError` and
221
+ discards that runtime instance; other calls are unaffected. Native crashes and
222
+ stack exhaustion still kill the process.
223
+ - Each active instance reserves about 10 GiB of *virtual* memory, so `ulimit -v`
224
+ or strict overcommit can make calls fail. Up to eight idle instances are
225
+ cached; an instance whose heap grew past 32 MiB is released instead.
226
+ - Bend's C runtime is private API. The build pins 2.0.28 and refuses to patch a
227
+ runtime that has changed.
228
+
229
+ ## Developing this repository
230
+
231
+ ```sh
232
+ bash scripts/bootstrap.sh # Python 3.14, uv venv, checksum-verified Bend
233
+ uv pip install --python .venv/bin/python --no-build-isolation -e .
234
+ (cd examples && CC=clang ../.venv/bin/python setup.py build_ext --inplace)
235
+ .venv/bin/python -m unittest discover -s tests
236
+ uvx prek install # ruff, file hygiene, and proof gates on commit
237
+ ```
238
+
239
+ [`docker/Dockerfile`](https://github.com/lucaswiman/bend-python/blob/main/docker/Dockerfile)
240
+ builds the pure SDK wheel once, then builds, `auditwheel`-repairs and tests the
241
+ example for CPython 3.10–3.14 and 3.14t with the image's current Clang. Select
242
+ versions with `-e PYTHON_TAGS=cp314-cp314t`.
243
+
244
+ ```sh
245
+ docker build -f docker/Dockerfile -t bend-python-manylinux .
246
+ docker run --rm --user "$(id -u):$(id -g)" \
247
+ -v "$PWD:/io:ro" -v "$PWD/wheelhouse:/wheelhouse" bend-python-manylinux
248
+ ```
249
+
250
+ [CI](https://github.com/lucaswiman/bend-python/blob/main/.github/workflows/wheels.yml)
251
+ runs that recipe per version, `prek`, and `twine check` on the SDK. Record changes
252
+ under "Unreleased" in [`CHANGELOG.md`](https://github.com/lucaswiman/bend-python/blob/main/CHANGELOG.md).
253
+ To release, move them to a dated `## [X.Y.Z] - YYYY-MM-DD` section matching the
254
+ project version and publish a GitHub release tagged `vX.Y.Z`; the
255
+ [release workflow](https://github.com/lucaswiman/bend-python/blob/main/.github/workflows/release.yml)
256
+ checks both, then publishes the SDK to PyPI by trusted publishing, with
257
+ provenance attestations. [`AGENTS.md`](https://github.com/lucaswiman/bend-python/blob/main/AGENTS.md)
258
+ has notes for contributors and coding agents.
259
+
260
+ ## License
261
+
262
+ [MIT](https://github.com/lucaswiman/bend-python/blob/main/LICENSE).
@@ -0,0 +1,16 @@
1
+ bend_python/__init__.py,sha256=OIRqMCQzAl1jR8CvixAoU2I91rVTuAJAlvuDwrTabnU,661
2
+ bend_python/__main__.py,sha256=JGA_yvOmQflnq_zvVtBX3s39I6ZZCNywIqDnbcHvKjE,924
3
+ bend_python/build.py,sha256=zcKj6WKYwPPyuGq0lgMTch98J6Yp7Z651fs-iQM4eIM,14993
4
+ bend_python/library.py,sha256=vsr45hq-1BSiUoU9wZjJzYEjbgpgwqEiTWmyEIdy1AY,2145
5
+ bend_python/lib/LAWS.bend,sha256=YOIHKsfTskVUcW8aAga52V6fC0fI3r7tvsb6Avh7IPc,3337
6
+ bend_python/lib/PROOF.bend,sha256=1p0OqP_HS6kEzEppnhM4JPnBdAc4BizIIj0jeX9ahJo,3688
7
+ bend_python/lib/THREAD_LAWS.bend,sha256=tP9I58ii2rtJjOBb5mhZDxsZBFtbtTgG9JcLd-wOl0Y,7916
8
+ bend_python/lib/THREAD_PROOF.bend,sha256=PmvHozp6Z287bZzWUfdjKm32FzMWv8lNiGsIk59X6aw,14565
9
+ bend_python/lib/python.bend,sha256=9EWUc1vZoldzKns_QMpRUQd8_N33PmRTnZ2pBvv2Jjc,6704
10
+ bend_python/lib/python.c,sha256=_e7GzGdImxtB_lf6Kz906FImlRBhRamhI6Pl8Bo31vI,29366
11
+ bend_python/lib/thread_state.bend,sha256=QjkzfQnF_Cov6STn11l26XD1wD7wWmlKKHkOYPzqidQ,7971
12
+ bend_python-0.1.0.dist-info/licenses/LICENSE,sha256=16PdXBiotwGBf2K9Cxt_kttbhz1ULtpaPMEJb-7lUO4,1068
13
+ bend_python-0.1.0.dist-info/METADATA,sha256=iZmA9aGijWRojd5YhiB7lMYtjWBZQ2a_fQNp3XDJdBg,12442
14
+ bend_python-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
15
+ bend_python-0.1.0.dist-info/top_level.txt,sha256=Ws_7WRe8A92BLNtvCFa2qeyqHqlK1erA0IRbRTKv4CU,12
16
+ bend_python-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lucas Wiman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ bend_python