Skip to content

Latest commit

 

History

History
141 lines (103 loc) · 5.21 KB

dev.md

File metadata and controls

141 lines (103 loc) · 5.21 KB

Python binding development

For a usage overview, see README.md.

Bindings call the public Rust facade. PyO3 supplies types; the generator handles declared overloads, indexing and NumPy annotations. See the binding contract for implementation rules and examples.

Run all commands from the repository root.

Test published prereleases

Dev and rc builds are for development testing:

pip install --upgrade --pre cosmolkit

--pre allows both dev and rc versions. To test a specific build, use pip install cosmolkit==<published-version>.

Prepare

Install development tools and the extractor matching PyO3:

uv sync --group dev
cargo install pyo3-introspection --version 0.30.1 --locked --root target/pyo3-tools

Build stubs

.venv/bin/python python/build_stubs.py \
    --extractor target/pyo3-tools/bin/pyo3-introspection

This command compiles the Rust extension, extracts types and generates the Python package and .pyi. It does not run tests or build a wheel.

The package is written to python/artifacts/smoke/package/cosmolkit/.

Check an existing build

Run runtime samples and static type checks without rebuilding:

.venv/bin/python python/check_stubs.py

This check uses the development dependencies, including RDKit for its interop type annotations. Coverage and local pre-commit checks run it; publishing does not. After changing bindings or generation rules, run build_stubs.py again.

Check registration

.venv/bin/python python/registry_check.py

Compares the canonical registry with the built package and generated types, including configuration fields and errors. Writes python/artifacts/registry.json.

Build and install a wheel

After build_stubs.py, package the extension, __init__.py, __init__.pyi and py.typed:

.venv/bin/maturin build --release --manifest-path python/Cargo.toml \
    --target-dir target/typed_facade_projection --features extension \
    --interpreter .venv/bin/python \
    --out python/artifacts/wheels
uv pip install --python .venv/bin/python --reinstall \
    python/artifacts/wheels/cosmolkit-*.whl
.venv/bin/python -c 'import cosmolkit as ck; print(ck.mol_from_smiles("CCO").num_atoms)'

The final command prints 3. Omit --release only for faster local debug builds; use optimized wheels for applications and performance measurements. Distribution name: cosmolkit; import name: cosmolkit.

Release wheels use release-abi3-py39 for Python 3.9 and newer, alongside release-cpython builds for Python 3.11, 3.12 and 3.13. Type declarations are generated separately with the current Python/PyO3 environment and packaged unchanged with those wheels.

Molecules support pickle.dumps(mol) and pickle.loads(data) through the stable cosmolkit._molecule_from_binary reconstruction function and native binary format.

Extending bindings

Explicit 3D coordinate inputs use Rust Vec<[f64; 3]>. Lists and tuples use a shared input adapter; NumPy arrays use an array view rather than per-element Python extraction. Shape is checked before numeric conversion, and Rust checks finite values. The official stub does not encode the fixed row length of 3.

Configurations declare fields only in their Rust owner, using #[derive(cosmolkit_macros::Configuration)]. Re-export its generated hidden metadata alongside the native type; the facade also re-exports its __ck_configuration_TypeName macro. The Python declaration names only that type:

ck::project_configuration!(ck::MaccsFingerprintParams);

The shared projection generates typed construction and writable properties over the actual Rust configuration. Getters return tuples, readonly mappings or nested configuration views. Child edits update the parent; containers are replaced through whole-field assignment. Conversion failures leave the Rust field unchanged, including fixed-array length errors. No editable Python container mirror is retained.

p = ck.MorganCallParams(from_atoms=[0])
p.from_atoms = [0, 2]          # Assigns the actual Rust field.
assert p.from_atoms == (0, 2)
# p.from_atoms = ["wrong"]   # TypeError; the field stays (0, 2).

Native annotations specify construction and composition: required keeps mandatory inputs, none_default accepts None as the native default, flatten exposes explicitly composed options, and factory forwards the registered new() method. Every ordinary parameter wrapper retains its native record.

Callbacks, random sources and private execution state use an explicit adapter:

ck::project_configuration!(ck::SubstructMatchParams, using match_parameters);

Their native declarations mark state and adapter fields; ordinary fields still come from native metadata and assign the actual native fields. Adapters retain actual native ownership, initialization and invocation errors. Unmarked private fields, generic records and unsupported field types fail compilation, rather than being inferred or downgraded.

Ordinary new APIs must not require generator, runner or consumer-sample changes. tests/runtime.py and tests/positive.py contain only a few fixed framework examples; they do not enumerate business APIs. New projection protocols require an explicit design decision.