Scenario 3 — Grouped types in a single subpackage module¶
You're here if: you're building a collection of related filter types —
Fir, Biquad, Equalizer — and you want from my_filters.filter import Fir, Biquad rather than a separate top-level import for each.
Use this when multiple related Python types should share one .so and import
from a common subpackage path. Each type still has its own independent C
library; the module is the Python grouping unit only.
1. Scaffold the project and module together¶
--module is repeatable — --module osc --module env scaffolds two modules
in one command. Each module is an empty slot; types are added with
just-makeit object.
Alternatively, scaffold the project first and add the module separately:
2. Add types¶
just-makeit object fir \
--module filter \
--state "coeffs:float[16]" \
--state "delay:float _Complex[16]" \
--state "gain:float:1.0"
just-makeit object biquad \
--module filter \
--arg-type float \
--return-type float \
--state "b0:double:1.0" \
--state "b1:double:0.0" \
--state "b2:double:0.0" \
--state "a1:double:0.0" \
--state "a2:double:0.0" \
--state "w1:double:0.0" \
--state "w2:double:0.0"
Each just-makeit object call:
- Creates the object's C library (
_core.h,_core.c, C test, C benchmark), its binding fragmentnative/src/filter/filter_ext_<obj>.c(shared: laterapplys only add missing members), and its Python test and benchmark undersrc/my_filters/filter/tests/andbenchmarks/ - Re-renders the module's
filter_ext.caggregator andCMakeLists.txt, and splices the new class intofilter/__init__.py
After both objects:
# filter/__init__.py — re-export all types from the C extension.
import os as _os
import sys as _sys
if _sys.platform == "win32" and hasattr(_os, "add_dll_directory"):
_os.add_dll_directory(_os.path.dirname(_os.path.abspath(__file__)))
del _os, _sys
from .filter import Fir, Biquad # noqa: E402
__all__ = ["Fir", "Biquad"]
Types within a module may have different --arg-type/--return-type. Here
Fir processes float _Complex and Biquad processes float.
3. Implement¶
Edit native/inc/my_filters/fir/fir_core.h and
native/inc/my_filters/biquad/biquad_core.h to
fill in the _step stubs, exactly as in Scenarios 1 and 2.
4. Build and test¶
CMake builds one .so (filter.cpython-*.so) inside
src/my_filters/filter/, linking both fir_core and biquad_core OBJECT
libraries. CTest runs test_fir_core and test_biquad_core.
5. Install¶
The wheel contains one .so for the filter subpackage rather than one .so
per type.
6. Use from Python¶
import numpy as np
from my_filters.filter import Fir, Biquad
fir = Fir(gain=1.0)
bq = Biquad(b0=1.0)
Both types are fully independent — separate create/destroy lifecycles,
each with its own step, steps, reset, and context manager support.
7. Add a third type later¶
filter_ext.c and CMakeLists.txt are re-rendered from the complete object
list, and Iir is spliced into __init__.py. Fir and Biquad — and their
binding fragments — are unaffected.
8. Install¶
The wheel is rebuilt with iir included.
Standalone object vs module object — when to use which¶
just-makeit object (no --module) |
just-makeit module + just-makeit object --module |
|---|---|
Each type gets its own .so |
All types share one .so subpackage |
from my_pkg import Gain, Ema |
from my_pkg.filter import Fir, Biquad |
| Good for unrelated algorithms | Good for a cohesive type family |
Simpler; each type is independent at the .so level |
One import namespace for the group |
Both workflows produce a lib<project>.so C library that supports
cmake --install, pkg-config, and CMake find_package.