Just-makeit

Latest version: v0.106.0

Safety actively analyzes 1035937 Python packages for vulnerabilities to keep your Python projects secure.

Scan your dependencies

Page 1 of 56

0.106.0

Added

- **Every C benchmark row states what it counts** (gh-2282).
`jm_bench_add_unit(&_bench, name, times, rounds, iters, unit)` records a
row of `iters` units per round. The unit is a word: `jm_bench.h` defines
the ones jm's generated benchmarks use (`JM_BENCH_SAMPLE`,
`JM_BENCH_CALL`, `JM_BENCH_FRAME`), and a project passes its own
(`"bin"`, `"tap"`) without editing the vendored header. A unit the JSON
could not carry as written stops the run, naming the row and quoting
the unit (`unit "in-sample" must be [a-z0-9_]`). The
five-argument `jm_bench_add` still compiles and records calls, which is
what `iters` was documented to count. The JSON keeps every field it had
and adds `extra_info.unit` and `rate_unit` (`Sa/s` for a sample,
`<unit>s/s` otherwise, from one `jm_bench_rate_label`), `MSa_s` on a
sample row, and `stats.<stat>_per_unit` beside each per-round `min`,
`max`, `mean`, `stddev` and `median`. Before, `extra_info` was a literal
`{}`, the header called `ops` samples per second while documenting
`iters` as calls, and a reader taking `mean` for a time per call was off
by a factor of `iters`. `jm bench`'s table now has a `Per` column and
shows each row's throughput in the unit its JSON states (`MSa/s`,
`Mcalls/s`). It shows a pytest-benchmark row per call, and a row from an
older `jm_bench.h` per round in `ops/s`, where it used to label every
row `Sa/s`. **Adopting the header changes what an unchanged call
means:** a five-argument `jm_bench_add` row reads per round in `ops/s`
against an older `jm_bench.h` and per call against this one, so move
every row that counts samples (or bins, taps) to `jm_bench_add_unit` in
the same change as the re-vendor. Generated benchmarks name the unit
they time: `step()`, `steps()` and a method over an input are samples, a
method with no input is calls. A no-input records method, timed one call
a round, had claimed `BENCH_N` of them and reported a rate `BENCH_N`
times too high. A benchmark jm generates now calls `jm_bench_add_unit`,
so an existing project adopts the new `jm_bench.h` (`jm status` reports
it OUTDATED) before it builds one.

Fixed

- **A composer module documents each member once, on both faces**
(gh-2283). A source, segment, timeline or composer type's runtime
`help()` was one fixed sentence, while its `.pyi` listed every field
as `Parameters`; the factories and the composer's own methods carried
a signature line at runtime and a different sentence in the stub. Each
docstring is now defined once, and the binding's text is derived from
the stub's. A source or segment type's summary is its struct's header
`brief`, and a factory names what its discriminant value means when
the `[[enum]]` declares `enumerators`.

- **A composer with a `realtime` clock no longer crashes when its
`stream()` iterator is freed** (gh-2292). This was a regression in
0.105.1 and 0.105.2. The iterator was allocated without zeroing,
and gh-2272's in-flight lock was garbage, so dropping the iterator
aborted the process. Every iterator jm generates is now allocated
zeroed, and a gate refuses any generator that allocates without
zeroing. Projects on 0.105.1 or 0.105.2 with a composer
`[module.X.composer.realtime]` table should upgrade.

0.105.2

Fixed

- **An `int32_t` argument out of range says the same thing on every
platform** (gh-2269). It parsed through `l`, a C `long`, which is 32
bits on Windows, so there a value past `int32_t` was refused by
CPython's "Python int too large to convert to C long" before jm's range
check could say `out of range for int32_t [lo, hi]`. It now parses
through `L` into a `long long`, as `uint32_t` already did, and every
value past `int32_t` but inside a `long long` gets jm's message
everywhere.

- **A `strict` refusal names the argument the caller passed** (gh-2271).
A `strict` input is refused rather than converted, and the refusal
named the binding's C local rather than the keyword: `prod(x[::2])`
raised "in must be a 1-D C-contiguous array". It now says
`x must be ...`, as the converting path and the `.pyi` already did,
for a scalar-element input and a record input alike.

0.105.1

Fixed

- **A `close()` on another thread no longer frees the state under a
running `nogil` call** (gh-2272). Since 0.105.0 a `nogil` method really
releases the GIL, and the `destroy()` / `close()` / `__exit__` the same
binding generates freed the state without asking whether a released call
was still running on it: one thread recording into a `Writer` while
another closed it crashed with SIGSEGV every time. Every released call
is now counted on the object, and every explicit teardown first refuses
new calls (`RuntimeError`), then waits with the GIL released for the
running ones to return, then frees, so the call returns its result and
`close()` returns once the state is gone. The same holds for every kind
that releases the GIL: an object, a handle module (and its
re-`__init__`), a capsule module's `destroy`, and a composer's `close()`
and its source's re-`__init__`; each now releases it through one
emitter (gh-2267). A module object that gains a `nogil` method after its
fragment was written has its teardown re-rendered by the verb. A
fragment `apply` does not re-render -- one rendered by 0.105.0 -- is
named by `apply` and `status` ("does not count the released call");
delete it and re-run `apply` to pick the guard up.

0.105.0

Added

- **`consumed_fn`: a list-of-records method re-offers the input its kernel
left** (gh-2204). A kernel whose output cannot be bounded before the call
-- a detector's hit count depends on the data -- stops when its `result`
buffer is full, and the binding called it once: `push(np.ones(100))`
into a kernel that stops at 64 returned 64 records, and elements 64..99
were never seen by the C. Declare `consumed_fn = "<fn>"` (or
`jm method --consumed-fn <fn>`), naming the author's
`size_t <fn>(const <state> *)` -- the input the last call took -- and
the binding calls the kernel, appends its records, and offers it the
rest of the input block until none is left, returning every record in
one list, in call order, as before. Params ride along unchanged on every
call. With a header-declared `_max_out()` as well (gh-2184), each call
is sized from it for the input still left. A call that takes none of
the input left raises `RuntimeError` naming the method rather than
spinning, and so does one claiming more than it was given. The name is
the author's and spelled as written under a `c_prefix`. The key is
refused at load, and by `jm method` before it writes, on a method with
no array input or one that does not return a list of records. A module
object's fragment rendered before the key is declared is reported by
`apply`; delete it and re-run `apply` to pick the loop up.

- **`short_out`: a backpressure kernel takes an `out=` smaller than
`max_out()`** (gh-2230). A `variable_output` method's `out=` had to hold at
least what the binding would have allocated, so a short buffer raised
`out has N elements, need >= M` -- right for a kernel that merely clamps,
and fatal for one whose contract is backpressure: it writes the rows that
fit, takes only the input that produced them and reports how much, and no
caller could ever present it a short buffer. `short_out = true`
(`jm method --short-out`) declares that contract: the buffer is then held
to its shape alone -- any length, `0` included; under `out_cols` any number
of rows of that width -- the kernel is handed its true size, and the result
is shaped to what it wrote. How much input was taken stays the object's
own property, so the caller resumes with
`rows = s.push(x, out=buf); x = x[s.consumed:]`. It requires
`pass_capacity` and is refused at load without it (and without
`variable_output`); a method that does not declare it keeps the minimum,
word for word. Both `.pyi` faces and the runtime `__doc__` say the buffer
has no minimum.

- **`no_count`: a void-input `variable_output` method can be `f()`**
(gh-2233). A `variable_output` method with `arg_type = "void"` and no
params always got a synthesized Python `count` and a `size_t n` in its
kernel, and no key removed them, so a kernel that writes at most one
result sized by the object alone -- a spectrogram's `flush`, a PSD
readout -- carried a keyword its callers had to pass for nothing and a
slot its C had to `(void)`. `no_count = true` (`jm method --no-count`)
drops both: the kernel is `fn(state, out[, max_out])`, its
`<fn>_max_out(state)` takes the state alone and sizes every call, and
the binding, both `.pyi` writers and the runtime `__doc__` offer no
`count` -- only the optional `out=`, checked against that bound as
before; the generated element round trip (gh-1404) calls such a reader
`()`. `pass_capacity` keeps its exact allocation there (the bound
cannot miss a count the call does not have), and the key composes with
`out_cols`, `none_on_empty`, `count_type` with `error_negative`,
`exact_max_out` and `multi_output`. It is refused at load where there is
no synthesized count to drop -- without `variable_output`, with an
input, with params, beside `count_default` or `count_name` -- and by
`apply` when the header's `_max_out` still takes a count. A method that
does not declare it is untouched.

- **A composer's generating source can expose its generator's methods**
(gh-2259). `[module.X.source.generates] methods = ["prod"]` binds each
named method of the generator object on the source type, so
`Synth(...).prod(x)` works without building the generator by hand. The
method runs against the generator `steps()` uses. The first call builds
it through `bridge_fn` and refuses as `steps()` does. Later calls, from
either method, continue the same state. The binding, its runtime
docstring and its `.pyi` member are the generator's, written by the same
emitter. Their `Raises` also lists what that first build can raise, as
`steps()`'s does, and an error the binding raises names the source
(`Synth.prod: ...`). Only the listed names are delegated. A name is
refused when the manifest loads if the generator does not declare it or
the source already has that member (a field, `fs`, a computed property,
`steps`, `step`, `reset`). It is also refused if the source cannot carry
the method: a built-in member of the generator, a `varargs` or
`manual_stub` method, a borrow or its release, a `single` record, or a
`codec` method.

Fixed

- **A `variable_output` method with params and no input parses them as
every other method does** (gh-2023). That shape parsed its params with
its own copy of the method parse, which knew scalars and plain arrays
only. A `path` param did not compile, and an `enum` param was parsed as
an `int` while both stubs said `str`, so the choice string the stub
advertised was refused. A `bytes` or `capsule` param, a record-row
array and `strict` never reached it either. The params now go through
the method parse, with `out=` as its trailing argument, as the shape
with an input has since gh-1960.

- **A composer CLI reads an enum segment field as a choice, not a
number** (gh-2140). The generated CLI's segment-field loop had no enum
branch, so doppler's `gap_noise` shape (`type = "int"`,
`enum = "gap_noise"`, `default = "auto"`) rendered
`int gap_noise = (int)auto;`, which does not compile, and with a
default that did compile, `--gap_noise off` read as the number `0`
whatever choice was named. A source and a segment enum field now share
one treatment: the flag starts at the default's C value and is looked
up in the `[[enum]]` table, and an unknown choice prints
`<prog>: --gap_noise: invalid choice 'x' (choices: auto, off)` and
exits 2. An enum that binds `enumerators` now reaches C as its
constant on the source field too, where the CLI had stored the
choice's position.

- **The `get_state` / `set_state` docstrings no longer say construction
parameters never travel** (gh-2185, gh-2234). Every `serializable`
object's `get_state` said "Construction parameters are not included",
and its `set_state` said "the object keeps the parameters it was
constructed with". That was on the runtime `__doc__` and both `.pyi`
writers. Both claims are false for a core whose blob carries a setter's
value as state, which `set_state` then replaces, and for one that
writes configuration as reject keys and refuses a blob built
differently. jm renders one text for every object and cannot tell
which kind a core is, so the text now says what holds for all of them.
It captures the state that evolves as the object runs, including any
value a setter changed after construction. Construction parameters are
not restored, so restore into an object built the same way. A core may
check some of them and refuse a blob built differently. `apply`
refreshes the text in an existing project, a module object's binding
included. `tests/test_gh2185_state_triplet_prose.py` walks a generated
project for every face of the two methods and refuses the old claims on
each.

- **A list-of-records module function, and the generated benchmark of a
list-of-records method, size the result from a declared capacity**
(gh-2205). gh-2184 left two faces handing the C a fixed `max_results`
(64): a `jm function --result-field` binding mallocked 64 records even
when `out_size` was declared, so a call due more returned the first 64,
and the benchmark timed a kernel that stops at a full buffer over 64
records rather than its whole call. A records function now sizes its
buffer from `out_size` -- the capacity key a `variable_output` function
already declares, a C expression over its params -- and hands the C
that count; `jm function --out-size` is accepted with `--result-field`.
Undeclared, `max_results` stands. `out_size` beside
`max_results_param`, two capacities for one buffer, is refused. An
empty input then asks for 0 records, and `malloc(0)` may return NULL,
a `MemoryError` for an empty call: the function face's buffers (the
records and the `str` output) now come from `PyMem_Malloc` /
`PyMem_Free`, as the method face's do, and an empty call returns `[]`. The
benchmark asks the method's header-declared `_max_out()` when the header
declares it at render time and allocates that many records; it stays
create-only, so delete it and re-run `apply` to pick a later
declaration up.

- **A `uint64_t` or `size_t` argument outside its range raises
`OverflowError` instead of wrapping into C, and takes a numpy integer**
(gh-2220). Both parsed through PyArg's `K`, which masks, so `-1`
arrived as `2**64 - 1` and `2**64` as `0` before any range check could
see them: a `size_t` count given `-1` became `SIZE_MAX`. `K` also
refused a numpy integer before Python 3.14, so `np.uint64(5)` raised
`TypeError`. Both now parse an object and convert it through
`__index__` and the checked `PyLong_AsUnsignedLongLong`, so a value
outside the range raises
`OverflowError: n: -1 is out of range for size_t [0, 18446744073709551615]`,
in the words every other integer type uses, and any integer with
`__index__` is accepted. A `float` is refused with `TypeError`, as for
every integer type. This holds on every face: constructors,
`set_<field>()`, writable properties, method and module-function params,
`step()` and its controllable overrides, composer rows, and handle and
capsule modules. The constructor, `set_<field>()`, `step()`, override
and `variable_output` parses now go through the one conversion every
other face uses instead of spelling their own. A parameter named
`<x>_raw` or `<x>_obj` beside a scalar `x` that the conversion declares
such a local for is refused when it is declared, where it used to
produce C that did not compile. A module object's fragment rendered
before this fix still parses through `K`, and `jm apply` names each
member whose format no longer matches.

- **A composer's ranged field crosses the Python face at its own type**
(gh-2224). A ranged source or segment field -- a scalar or a
`(lo, hi)` pair -- parsed each bound as a `double` and cast it, and read
the pair back through `"(dd)"` on a source and `Py_ssize_t` on a
segment. So a `uint64_t`, `int64_t`, `size_t` or `ptrdiff_t` bound past
2^53 changed when set and again when read, an integer source pair read
back as two floats, a `double` segment pair read back truncated to
integers, and an `int8_t` given 300 wrapped to 44, all with no error.
Each bound now parses through the field's own `_CTYPE_META` row
(`scalar_parse_c`, the parse a plain field of that type takes) and the
pair is built from its `to_py`, on the constructor, the attribute and
`Segment.sum`. So every value round-trips exactly, a bound the type
cannot hold is refused with an `OverflowError` naming the field
(gh-2144), and an integer field's bound is an `int`, as its `.pyi`
always said: a `float` there is now a `TypeError`, as it already was for
a plain integer field -- and, until gh-2220, so is a numpy integer for a
`uint64_t` or `size_t` field on Python before 3.14, as for a plain one.
`_jm_parse_range` now only splits the value into its bound objects.

- **A generated C command line refuses a bad numeric flag instead of
running with a wrong value** (gh-2225). A composer's c-face CLI read
each numeric flag with `strtoull` (or `strtod`) and a cast, and a
`jm app` C executable with `strtol` and a cast. Neither checked the end
pointer, `errno` or the declared type's range, so `--count abc` ran
with `0`, an overflowing count with `ULLONG_MAX`, `--gain 300` on an
`int8_t` with `44` and `--n -1` on a `uint8_t` with `255`, all with
exit status 0 and no message. Both faces now read every numeric flag
through one generated parser: the whole argument at its declared type,
held to the type's `<stdint.h>` / `<limits.h>` range (a `float` read at
its own precision), or the tool prints
`<prog>: --gain: '300' is out of range for int8_t [-128, 127]` and
exits 2. A valid argument reads exactly as before, in the same grammar
(decimal for an app, `0x` hex and `0` octal too for a composer). The
parser is emitted only into a file whose flags call it.

- **A property getter's binding no longer carries an `<<IMPLEMENT>>`
marker** (gh-2231). A property backed by a C getter call rendered
`/* <<IMPLEMENT: return the computed or stored value>> */` in the
generated binding, directly above the finished
`return ...(<comp>_get_<prop>(self->handle));`. That was in a standalone
object's `_ext.c`, a module object's `_ext_<obj>.c` fragment and a
view's fragment. The marker tells the author which body to write, and
there it pointed at nothing: the stub the author fills in is the C
getter in `_core.c`, which keeps its own. `apply` drops the line from an
`_ext.c`. A fragment is not rewritten wholesale, so `status` reports one
rendered before this as UNEXPLAINED with every unit identical until it
is regenerated. `tests/test_gh2231_no_implement_marker_in_glue.py`
builds the broad fixture tree and refuses the marker in every file jm's
ownership table calls jm's content.

- **A method that declares a way to refuse claims no result in its runtime
example** (gh-2232). The doctest jm writes into a method's `__doc__`
printed the return type's zero as the call's output, and a
`variable_output` result's dtype after it. Both are true of the
scaffold's stub and a guess about any real kernel. For a method that
declares `error_negative`, `error_sentinel` or `error_on_empty`, that
guess is exactly what the declaration says may not happen: doppler's
`flush(np.zeros(4))` showed `0` over a kernel that refuses a zero row.
Under `error_on_empty` the scaffold's own stub refused it too. gh-869
already stopped the guess for `status_return`. Every refusal
`declared_raise` reads now gets that treatment: the example shows the
call and expects no output, so `>>> y = obj.flush(...)` with no line
after it. A value is bound to `y` rather than echoed, so the line still
passes whenever the call does. A method that cannot refuse keeps its
result. The `.pyi` synthesizes no method example, so the two faces
agree. `tests/test_gh2232_refusing_example.py` reads the refusal keys
from `declared_raise` and refuses a claimed result in each shape one
can be declared on.

- **A composer's JSON reader refuses a value its field's C type cannot
hold** (gh-2236). The exact reader 2221 added for a 64-bit field
(gh-2139) parses into an `unsigned long long` / `long long` and then
cast that to the field's type. A `size_t` or `ptrdiff_t` parses through
a `long long` on every target and is 32 bits on a 32-bit one, so there
`"4294967296"` -- or the number 4294967296 -- passed the reader and the
cast dropped its high bits, with no error. The value is now converted
back and compared before it is stored, and one the cast would change is
refused with the reader's own `ValueError`, naming the field and saying
it is out of range. Nothing changes on a 64-bit target, where the cast
loses nothing.

- **A composer's JSON reader refuses a number its integer field cannot
hold** (gh-2247). `from_json` / `from_file` read every field a double
holds through `_json_num` and cast it, so an integer field took `1.5`
as `1`, and a value past its type -- `4294967296`, `-1` or `1e30` into
a `uint32_t` -- arrived wrapped (`0`, `4294967295`, `0`), a cast that
is undefined behaviour in C. A `bytes` field's elements were cast into a
byte the same way, so `256` arrived as `0`. Nothing raised. Every
integer field narrower than 64 bits, plain or ranged, and every `bytes`
element now reads only a whole number within its type's range
(`_types.int_limits`, from the type row) and refuses anything else with
the reader's `ValueError`, naming the field and saying the value is out
of range. The factory keeps that exception for every composer, not only
one with a 64-bit field.

- **A `size_t` argument above `SIZE_MAX` raises `OverflowError` on a
target where `size_t` is 32 bits** (gh-2258). The checked parse gh-2220
added converts a `uint64_t` / `size_t` through
`PyLong_AsUnsignedLongLong`, which refuses only what an
`unsigned long long` cannot hold, so on such a target a value between
`SIZE_MAX` and `2**64` passed it and wrapped on the cast with no error.
The converted value is now compared with the type's maximum before the
cast and refused in the same words. Nothing changes where `size_t` is
64 bits: the comparison is always false there, and the compiler drops
it. The maximum is read from the type's one range declaration, which
the generated command line and a composer's JSON reader read too.

- **A `batch` method has a `.pyi` member and an array-typed `__doc__`, and
`nogil` releases the GIL on every method whose binding jm writes**
(gh-2259). A `batch` method (`jm method --batch`) is jm's elementwise
block method: an array in, an array of the same length out, an optional
`out=`. The standalone `.pyi` had no member for it, the module one typed
it `prod(self, x: complex) -> complex`, and the runtime `__doc__` said
`x : complex` and `Returns complex`. Both stubs and the `__doc__` now
read `prod(x, out=None) -> NDArray[...]`, or `fill(count=1, out=None)`
with no input, and agree. Their sections come from the same builder.
`nogil = true` was accepted and ignored on a `batch` method, and on
every other shape that spelled its kernel call inline: a scalar,
`void`, `status_return`, `error_negative`, `multi_output` or `out_type`
result, a codec-pack method's sink, and a capsule module's bare
`(state)` method. doppler had nine such methods that it believed
released the GIL. Every call jm writes now goes through one emitter, so
the GIL is released on both of a batch method's paths, with the arrays
acquired and the result allocated first. A method without `nogil`
generates the same code as before. A `varargs` or `manual_stub` method
has no generated call, so jm refuses `nogil` there and asks you to
release the GIL in your own binding. A module object's fragment
rendered before this fix keeps its old call, and `jm apply` and
`jm status` name each `nogil` member in it that holds the GIL.

0.104.0

Added

- **`out=` and `<m>_max_out` for a `variable_output` method with an input
and params** (gh-2028). `run(x, gain)` -- an `arg_type` input with params
beside it -- was the one single-output shape offered no `out=` buffer:
its parse dropped the params until gh-1960, and after that `out` joining
the Python arguments broke the header's `param in` -> `x` match in the
doc. Both are generated now, in the binding and both `.pyi` faces: `out`
is the parse's trailing optional argument (`run(x, gain, out=None)`,
positional or keyword, after a defaulted param's group too), sized from
the input exactly as the same method without params is, and
`<m>_max_out(len(x))` sizes it. An undersized buffer is refused before
the kernel runs. A `manual_stub` entry for that `<m>_max_out` is now
refused, with the instruction to drop it: the stub it declared is jm's
own. `multi_output` is the one `variable_output` shape left without
`out=`.

Fixed

- **No generated binding parses a scalar into a variable narrower than PyArg
writes** (gh-1952). Five faces parsed a scalar straight into its
declared C type: every `kind = "handle"` shape (create args, factory
params, method args, writable properties), a `kind = "capsule"` create
param and writable property, a controllable `step()` / `steps()`
override, and a codec method's fixed param. PyArg writes the width of
its format char, so an `int8_t` or `bool` took the four bytes `i` / `p`
write and a `float _Complex` took a 16-byte `Py_complex`. Each was a
write past a local on the stack. A handle's `out_len_fn` methods parsed
into a wider local, but seeded a complex one with `= 0`, which does not
compile. Every one of these faces now parses into the row's wider local
through the one tuple-parse slot (`scalar_arg_c` / `scalar_narrow_c`),
seeds a default the way every other face does, and range-checks the
value before narrowing it (gh-2144). A handle or capsule scalar local is
now initialised, so `double fs;` reads `double fs = 0.0;`.
`tests/test_gh1952_parse_width.py` reads every `PyArg_Parse*` call in a
render of every face. It refuses any scalar parsed into a variable of
another type than its format char writes, with the char-to-type map
derived from `_CTYPE_META`.

- **A header's `param in` documents `x` on a method that takes `out=`**
(gh-2028). The header names the block input by its C name, and the doc
matched it to the Python `x` by position among the arguments no
`param` named -- counting jm's own `count` and `out` among them. So
every input-only `variable_output` method, which has taken `out=` since
gh-219, documented `x` as `Input.` unless the header also spelled
`param out`, on both `.pyi` faces and the runtime `__doc__`. The
binding's own arguments now take no positional slot: an authored
`param out` or `param count` still documents one by name, and jm's
default text the rest. So a generator's `count` is no longer filled by
position from a `param` of another name (its C `n`); `param count`
documents it, as gh-1042 established.

- **A composer's JSON face carries a 64-bit integer field exactly**
(gh-2139). The generated `to_json` wrote every numeric source and segment
field through a `double` and `from_json` read it back through one, so a
`uint64_t` such as `0x8000000000000001` -- or an `int64_t`, `size_t` or
`ptrdiff_t` past 2^53 -- came back a different value, with no error. Such
a field is now a JSON number while its value is within ±(2^53 - 1), the
range a double holds exactly (I-JSON, RFC 7493), and a decimal string
past it, so every small value and every record within that range reads
as before. A record an older jm wrote with a wider value holds a rounded
double, whose true value is already lost, and is now refused rather than
loaded as a different number.
`from_json` / `from_file` read either form exactly, and refuse with a
`ValueError` naming the field a number outside that range or with a
fraction, a string that is not a decimal integer or overflows the type,
and a sign on an unsigned field. A composer with no 64-bit field
generates the same code as before; the double reader `_json_num` is now
defined only where some field calls it, as gh-1863 does for the other
helpers, so one whose every number is 64-bit builds under `-Werror`.

- **An integer outside its C type's range raises `OverflowError` instead
of wrapping silently into C** (gh-2144). A narrow integer parses into a
wider local (an `int8_t` into an `int`), and the binding cast it with no
range test, so `int8_t` given `300` arrived as `44`. This happened on
every face that takes a scalar: constructors, `set_<field>()`, writable
properties, method params, `step()`, module functions, composer rows,
and handle and capsule modules. `uint8_t` and `uint16_t` parsed through
PyArg's masking `I`, so even `2**32 + 5` arrived as `5`. `uint32_t`
parsed through `k`, so on Windows `-1` arrived as `4294967295`. Every
`int8_t`, `int16_t`, `int32_t`, `uint8_t`, `uint16_t` and `uint32_t`
value is now tested against its `<stdint.h>` bounds before the cast. The
unsigned ones parse through a checked format char. A value outside the
bounds raises
`OverflowError: gain: 300 is out of range for int8_t [-128, 127]`, as
numpy 2 refuses `np.int8(300)`. On the object, function and composer
faces, the code generated for every other type is unchanged. A module
object's fragment rendered before this fix has no range test, and
`jm apply` names each member that lacks one. Still open: `uint64_t` and
`size_t` parse through the masking `K`, so `-1` still arrives as
`2**64 - 1` (gh-2220).

0.103.0

Added

- **`out_cols`: a `variable_output` method can return a matrix** (gh-2115). A
kernel whose result is fixed-width rows -- a spectrogram's `nfft` bins, a
frame of `n` samples -- filled a flat buffer, and every array jm allocated
for it was 1-D, so the width had nowhere to be declared and the caller had
to reshape. `out_cols = "state->nfft"` (or an integer; `jm method --out-cols`
with the expression) now makes the binding return
`(count / out_cols, out_cols)` on both the allocating path and `out=`. A
caller-supplied `out=` must be 2-D with that many columns, and a kernel
count that is not a whole number of rows is a `RuntimeError`. The kernel's
contract is
unchanged: it still sees a flat buffer and counts in elements, and
`_max_out()` still answers in elements. Refused at load where it cannot
apply: without `variable_output`, and with `multi_output`, `record_dtype`
or `batch`. A method that does not declare it is untouched.

- **A writable property's setter can refuse a value** (gh-2182). A property
may declare `error` and `error_message`, spelled as a method's pair
(`jm property --writable --error EXC --error-message TEXT`). Its C
setter then returns `int`, and a non-zero return raises `EXC` with the
message and the return code, so `acc.alpha = -0.5` says the core refused
it. Before, the setter discarded whatever `<stem>_set_<prop>` returned:
the assignment succeeded and the property went on reading its old
value. Where jm declares the setter it declares it `int`, with an `int`
stub; the `.pyi` and `help()` document the exception in a `Raises`
section; `jm apply` and `jm script` carry both keys. `error` is refused,
naming the fix, wherever there is no C setter call to test: a read-only
property, `field = true`, and a property named for a state field (whose
accessor jm declares `void`). A property declaring no `error` renders
exactly as before.

- **A handle method with an array argument can declare its `int` a
status, so `error` applies to it** (gh-2186). jm reads such a return as
a count, samples taken, and refused `error` there (gh-1118), with no way
to say a call such as `send(iq, fs, fc)` returns 0 for a taken block and
non-zero for a refused one. `status_return = true` on the method says so:
it is the object face's key, with its meaning. A non-zero return raises
the method's `error` (`ValueError` when none is named, with
`error_message` as on any status method), the binding returns `None`,
and the `.pyi` says `-> None` with a `Raises` section. The refusal of a
bare `error` on such a method now names `status_return = true`, and the
key is refused over a non-integer `returns` or an array or `bytes`
result, whose return is the payload length.

Fixed

- **`jm dry-run` names the build its backend runs** (gh-2128). It always
printed a cmake configure, with jm's own interpreter as
`Python3_EXECUTABLE`, whatever `[project] build` said. On a `build = "make"`
project that described a build the project never runs. A make project now
shows `make PYTHON=<project python>`, and a cmake project shows its configure
with the project's `.venv` interpreter when one exists.

- **An `extra_methods` row no longer breaks the C test's symbol table, and
the core calls it makes are link-checked** (gh-2175). jm renders the
row as `(PyCFunction)(void (*) (void))<fn>`, and the gh-1361 scanner
read `void (` as a call. A header with any callback member, such as
`void (*cb)(void *)`, supplied the other half, so `apply` wrote
`(jm_any_fn)void,` into `test_<obj>_symbols.c` and the C test did not
compile. The scanner now drops every C keyword, C99 through C23, not
only the ones seen so far. The table is also read from the object's
`_extra.c` hook as well as its binding, so a core function only the
hook calls is still checked at link time.

- **A manifest `doc` that ends in a doctest no longer breaks the stub's
doctest run** (gh-2176). A text-mode `.pyi` doctest
(`pytest --doctest-glob='*.pyi'`) reads an example's expected output up
to the next blank line, and the closing quotes came straight after the
last line. The example then swallowed the quotes and the declaration
after them: a parse error for the whole file, or an example that could
never match. This is gh-691's failure on the path gh-2059 opened, where
an `extra_methods` row's numpy `doc` can end in `Examples`. Every
layout of an authored `doc` now leaves a blank line before the closing
quotes when the text ends inside an example: a member's, a handle
method's, and a module's. Every other `doc` renders exactly as before.

- **A `none_on_empty` method's stub says it can return `None`** (gh-2183). A
`variable_output` method with `none_on_empty = true` returns `None` when
the kernel writes nothing, on its allocating route and its `out=` route,
but both `.pyi` producers annotated the bare array, so a type checker
rejected `if x.value() is None`. Only the borrow route asked the key. One
predicate now decides it, the one the binding's `Py_RETURN_NONE` is
emitted from: every face (the standalone and module `.pyi`, and the
runtime synopsis) annotates `<array> | None`, a tuple result included,
and both `Returns` sections say when the result is `None`. A borrow's
`Returns` now says when too, and its runtime synopsis reads
`-> ndarray | None`. `jm apply` carries the new runtime doc into an
existing module object's sacred `_ext_<obj>.c`: the doc refresh no longer
reads a synopsis that gained `| None` as an author's text. Projects
without `none_on_empty` regenerate unchanged.

- **A list-of-records method returns every record its C reports when the
header declares the method's `_max_out()`** (gh-2184). The binding
called the kernel with a fixed stack array, `rec_t results[64]`, and
the kernel's `(…, rec_t *result, size_t max_results) -> count` contract
has no way to say there were more, so a call due 100 records returned
64 and dropped the rest without a word. The only lever was a bigger
`max_results`, a bigger stack array on every call. Now a header that
declares `<pkg>_<comp>_<name>_max_out(state, n_in)` -- the capacity
function a `variable_output` method already has, with the same name
and arity rules (`n_in`, `<param>_len`, or state-only) -- is asked on
every call; the binding allocates that many records, hands the kernel
the same capacity as `max_results`, frees the buffer on every way out,
and returns them all. Standalone and module objects alike, for an
input, params, or neither. Without a declaration the fixed buffer is
byte-identical to before. A module object's fragment rendered before
the declaration is not re-rendered by `apply`, which now reports that
method as dropping records past `max_results`; delete the fragment and
re-run `apply` to pick the capacity up.

- **An object that hands out views no longer frees the memory under them
at `destroy()`** (gh-2187). A `borrow = true` method, a `buf_field`
property and an array state's `get_<name>_view()` each pin the object,
not its memory, so an explicit `destroy()` or `__exit__` left every
outstanding view, and a GIL-free call still running, reading freed
memory. Such an object's teardown now parks the state and every method
refuses as before; `tp_dealloc` frees it once the last view and call
have let go. `[<obj>.destroy] wake = "<method>"` names a method to call
first, so a blocking call returns. A fallible destroy on a lending
object is refused at generation: a finalizer named by `exit` reports
the status instead. A module object that starts or stops lending after
its fragment was written has the teardown jm wrote re-rendered with
it, and one the author edited is kept and named by `apply` and
`status`.

- **A C benchmark's JSON holds every row it recorded, past 32** (gh-2188).
`jm_bench.h` kept its rows in a fixed array of 32, and `jm_bench_add`
dropped every row past it without a word: the benchmark printed every
row, exited 0, and wrote a JSON missing the rest, so `jm bench` and
`bench-compare` saw fewer rows than were measured. The rows are now a heap
array that grows, so there is no cap, and `JM_BENCH_MAX_ENTRIES` is gone.
A row the header cannot hold (an allocation that fails) or one with no
time in it (fewer than 1 round, whose statistics were read out of an
empty array) now stops the run with a message on stderr and a non-zero
exit, rather than being dropped or written as garbage.
`jm_bench_write_json` now frees the rows once the file is written, so a
benchmark built with `-fsanitize=address` exits with nothing leaked. It
takes a non-const `jm_bench_t *`, and the bench is empty afterwards.
`jm_bench_free` does the same for a benchmark that returns without
writing. `jm_bench.h` is create-only: an existing project picks the fix
up by re-vendoring it (`jm status` reports it outdated).

- **`jm bench` fails when a benchmark run fails** (gh-2189). A
`bench_<comp>_core`'s exit status was never read, and a missing JSON was
skipped without a word, so a benchmark that crashed, or one that refused to
write a short set, was simply absent from a run that exited 0. A non-zero
exit, a death by signal, and an exit 0 with no JSON or with a JSON that does
not parse are each now reported as `failed bench_<comp>_core (<why>)`, with
the binary's stderr quoted beneath. A target that built but whose binary
cannot be found is reported the same way, where it was skipped. The Python
side had the same hole: pytest's exit status was never read, so a benchmark
that raised dropped out of the snapshot. A `pytest` run that exits other
than 0 or 5 (nothing collected) is now `failed pytest <file> (exit N)`. As
with a timeout, the other benchmarks still run and are saved, the snapshot
lists the failure under `"failed"`, and `jm bench` then exits 1 naming it.
`--check` fails on it too, and its JSON document lists it under `"failed"`.
A JSON a failed run did write is not read.

- **A release's explicit count of `0` reaches C as `0`, instead of releasing
the whole outstanding borrow** (gh-2191). The generated release tested
the parsed value, and `0` was both its "omitted" default and a count a
caller can mean, so `peek(64); consume(0)` released 64 samples nobody
had processed (doppler-dsp/doppler2014), and `consume(0)` with nothing
outstanding raised. Omitted is now decided by whether the argument was
passed, positionally or by keyword. A bare `consume()` still takes the
borrow's count, and still raises `RuntimeError` with nothing
outstanding.

- **A handle method's `.pyi` names its array argument as the manifest
does** (gh-2207). The stub called it `x` whatever was declared, while the
binding's keyword list and the runtime docstring used the declared name:
for `send(iq, fs, fc)` the stub said `send(self, x, fs, fc)`, so
`send(x=...)` raised `TypeError` and a type checker refused
`send(iq=...)`, the call that works. A count-in method's stub did the same
with `n`. The binding and the stub now take each parameter's name and
position from one place, `_handle.py_args`.

Page 1 of 56

Links

Releases

Has known vulnerabilities

© 2026 Safety CLI Cybersecurity Inc. All Rights Reserved.