diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..f648169 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,6 @@ +[target.aarch64-apple-darwin] + +rustflags = [ + "-C", "link-arg=-undefined", + "-C", "link-arg=dynamic_lookup", +] diff --git a/Cargo.toml b/Cargo.toml index 7ae510b..5728b47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,4 +19,4 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } prost-build = "0.12" [workspace] -members = ["python"] +members = ["python/krec/bindings"] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6a8d2f7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,86 @@ +[tool.black] + +line-length = 120 +target-version = ["py311"] +include = '\.pyi?$' + +[tool.pytest.ini_options] + +addopts = "-rx -rf -x -q --full-trace" +testpaths = ["tests"] + +markers = [ + "slow: Marks test as being slow", +] + +[tool.mypy] + +pretty = true +show_column_numbers = true +show_error_context = true +show_error_codes = true +show_traceback = true +disallow_untyped_defs = true +strict_equality = true +allow_redefinition = true + +warn_unused_ignores = true +warn_redundant_casts = true + +incremental = true +namespace_packages = false + +[[tool.mypy.overrides]] + +module = [ + "setuptools", + "setuptools_rust", +] +ignore_missing_imports = true + +[[tool.mypy.overrides]] + +module = ["krec.bindings"] + +disable_error_code = ["no-untyped-def"] + +[tool.isort] + +profile = "black" + +[tool.ruff] + +line-length = 120 +target-version = "py310" + +[tool.ruff.lint] + +select = ["ANN", "D", "E", "F", "I", "N", "PGH", "PLC", "PLE", "PLR", "PLW", "W"] + +ignore = [ + "ANN101", "ANN102", + "D101", "D102", "D103", "D104", "D105", "D106", "D107", + "N812", "N817", + "PLR0911", "PLR0912", "PLR0913", "PLR0915", "PLR2004", + "PLW0603", "PLW2901", +] + +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.lint.per-file-ignores] + +"__init__.py" = ["E402", "F401", "F403", "F811"] + +[tool.ruff.lint.isort] + +known-first-party = ["krec", "tests"] +combine-as-imports = true + +[tool.ruff.lint.pydocstyle] + +convention = "google" + +[build-system] + +requires = ["setuptools>=42", "wheel", "setuptools-rust>=1.5.2"] +build-backend = "setuptools.build_meta" diff --git a/python/Cargo.toml b/python/Cargo.toml deleted file mode 100644 index fee731c..0000000 --- a/python/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "krec-py" -version = "0.1.0" -edition = "2021" - -[lib] -name = "krec" -crate-type = ["cdylib"] - -[dependencies] -krec = { version = "0.1.0", path = ".." } -pyo3 = { version = "0.19", features = ["extension-module"] } -pyo3-stub-gen = ">= 0.6.0" -tracing = "0.1" diff --git a/python/krec/__init__.py b/python/krec/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/krec/bindings/Cargo.toml b/python/krec/bindings/Cargo.toml new file mode 100644 index 0000000..0aa4bfe --- /dev/null +++ b/python/krec/bindings/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "bindings" +version = "0.1.0" +edition = "2021" + +[lib] +name = "bindings" +crate-type = ["cdylib", "rlib"] + +[dependencies] +pyo3 = { version = ">= 0.21", features = ["extension-module"] } +pyo3-stub-gen = ">= 0.6" +tracing = "0.1" + +# Workspace packages. +krec = { version = "0.1.0", path = "../../.." } diff --git a/python/krec/bindings/bindings.pyi b/python/krec/bindings/bindings.pyi new file mode 100644 index 0000000..0693c4b --- /dev/null +++ b/python/krec/bindings/bindings.pyi @@ -0,0 +1,360 @@ +# This file is automatically generated by pyo3_stub_gen +# ruff: noqa: E501, F401 + +import typing + +class ActuatorCommand: + r""" + Command for an actuator + """ + actuator_id: int + position: float + velocity: float + torque: float + def __new__(cls,actuator_id,position = ...,velocity = ...,torque = ...,values = ...): ... + def __repr__(self) -> str: + ... + + def set_position(self, value:float) -> None: + ... + + def set_velocity(self, value:float) -> None: + ... + + def set_torque(self, value:float) -> None: + ... + + +class ActuatorConfig: + r""" + Configuration for an actuator + """ + actuator_id: int + kp: typing.Optional[float] + kd: typing.Optional[float] + ki: typing.Optional[float] + max_torque: typing.Optional[float] + name: typing.Optional[str] + def __new__(cls,actuator_id,kp = ...,kd = ...,ki = ...,max_torque = ...,name = ...,values = ...): ... + def __repr__(self) -> str: + ... + + def set_kp(self, value:typing.Optional[float]) -> None: + ... + + def set_kd(self, value:typing.Optional[float]) -> None: + ... + + def set_ki(self, value:typing.Optional[float]) -> None: + ... + + def set_max_torque(self, value:typing.Optional[float]) -> None: + ... + + def set_name(self, value:typing.Optional[str]) -> None: + ... + + +class ActuatorState: + r""" + State information for a single actuator + """ + actuator_id: int + online: bool + position: typing.Optional[float] + velocity: typing.Optional[float] + torque: typing.Optional[float] + temperature: typing.Optional[float] + voltage: typing.Optional[float] + current: typing.Optional[float] + def __new__(cls,actuator_id,online = ...,position = ...,velocity = ...,torque = ...,temperature = ...,voltage = ...,current = ...,values = ...): ... + def __repr__(self) -> str: + ... + + def set_online(self, value:bool) -> None: + ... + + def set_position(self, value:typing.Optional[float]) -> None: + ... + + def set_velocity(self, value:typing.Optional[float]) -> None: + ... + + def set_torque(self, value:typing.Optional[float]) -> None: + ... + + def set_temperature(self, value:typing.Optional[float]) -> None: + ... + + def set_voltage(self, value:typing.Optional[float]) -> None: + ... + + def set_current(self, value:typing.Optional[float]) -> None: + ... + + +class FrameIterator: + r""" + Iterator for frames + """ + def __iter__(self) -> FrameIterator: + ... + + def __next__(self) -> typing.Optional[KRecFrame]: + ... + + +class IMUQuaternion: + r""" + A quaternion representing 3D rotation + """ + x: float + y: float + z: float + w: float + def __new__(cls,x = ...,y = ...,z = ...,w = ...,values = ...): ... + def __repr__(self) -> str: + ... + + +class IMUValues: + r""" + IMU sensor values including acceleration, gyroscope, and orientation data + """ + accel: typing.Optional[Vec3] + gyro: typing.Optional[Vec3] + quaternion: typing.Optional[IMUQuaternion] + def __new__(cls,accel = ...,gyro = ...,mag = ...,quaternion = ...,values = ...): ... + def __repr__(self) -> str: + ... + + def set_accel(self, value:typing.Optional[Vec3]) -> None: + ... + + def set_gyro(self, value:typing.Optional[Vec3]) -> None: + ... + + def set_quaternion(self, value:typing.Optional[IMUQuaternion]) -> None: + ... + + +class KRec: + frame_count: int + header: KRecHeader + def __new__(cls,header:KRecHeader): ... + def get_frame(self, index:int) -> KRecFrame: + r""" + Get a specific frame by index + """ + ... + + def get_frames(self) -> list[KRecFrame]: + r""" + Get all frames + """ + ... + + def add_frame(self, frame:KRecFrame) -> None: + r""" + Add a frame to the recording + """ + ... + + def remove_frame(self, index:int) -> None: + r""" + Remove a frame at the specified index + """ + ... + + def clear_frames(self) -> None: + r""" + Clear all frames + """ + ... + + def __getitem__(self, index:int) -> KRecFrame: + r""" + Get a frame by index (Python [] operator) + """ + ... + + def __len__(self) -> int: + r""" + Get the length (Python len() function) + """ + ... + + def __iter__(self) -> FrameIterator: + r""" + Iterator support for frames + """ + ... + + def __repr__(self) -> str: + ... + + def __str__(self) -> str: + ... + + def display(self) -> str: + r""" + Returns a detailed string representation of the KRec contents + """ + ... + + def display_frame(self, frame_number:int) -> str: + r""" + Returns a more detailed string representation of a specific frame + """ + ... + + def save(self, path:str) -> None: + ... + + @staticmethod + def load(path:str) -> KRec: + ... + + def combine_with_video(self, video_path:str, output_path:str) -> None: + ... + + +class KRecFrame: + def new(self, video_timestamp:typing.Optional[int], frame_number:typing.Optional[int], inference_step:typing.Optional[int], values:typing.Optional[typing.Any]) -> KRecFrame: + ... + + def __repr__(self) -> str: + ... + + def get_video_timestamp(self) -> int: + ... + + def set_video_timestamp(self, value:int) -> None: + ... + + def get_frame_number(self) -> int: + ... + + def set_frame_number(self, value:int) -> None: + ... + + def get_inference_step(self) -> int: + ... + + def set_inference_step(self, value:int) -> None: + ... + + def add_actuator_state(self, state:ActuatorState) -> None: + ... + + def get_actuator_states(self) -> list[ActuatorState]: + ... + + def clear_actuator_states(self) -> None: + ... + + def set_actuator_commands(self, commands:typing.Sequence[ActuatorCommand]) -> None: + ... + + def get_actuator_commands(self) -> list[ActuatorCommand]: + ... + + def clear_actuator_commands(self) -> None: + ... + + def add_actuator_command(self, command:ActuatorCommand) -> None: + ... + + def has_actuator_commands(self) -> bool: + ... + + def actuator_command_count(self) -> int: + ... + + def set_imu_values(self, imu:typing.Optional[IMUValues]) -> None: + ... + + def get_imu_values(self) -> typing.Optional[IMUValues]: + ... + + def clear_imu_values(self) -> None: + ... + + def has_imu_values(self) -> bool: + ... + + def actuator_state_count(self) -> int: + ... + + +class KRecHeader: + def new(self, uuid:typing.Optional[str], task:typing.Optional[str], robot_platform:typing.Optional[str], robot_serial:typing.Optional[str], start_timestamp:typing.Optional[int], end_timestamp:typing.Optional[int], values:typing.Optional[typing.Any]) -> KRecHeader: + ... + + def __repr__(self) -> str: + ... + + def get_uuid(self) -> str: + ... + + def set_uuid(self, value:str) -> None: + ... + + def get_task(self) -> str: + ... + + def set_task(self, value:str) -> None: + ... + + def get_robot_platform(self) -> str: + ... + + def set_robot_platform(self, value:str) -> None: + ... + + def get_robot_serial(self) -> str: + ... + + def set_robot_serial(self, value:str) -> None: + ... + + def get_start_timestamp(self) -> int: + ... + + def set_start_timestamp(self, value:int) -> None: + ... + + def get_end_timestamp(self) -> int: + ... + + def set_end_timestamp(self, value:int) -> None: + ... + + def add_actuator_config(self, config:ActuatorConfig) -> None: + ... + + def get_actuator_configs(self) -> list[ActuatorConfig]: + ... + + def clear_actuator_configs(self) -> None: + ... + + +class Vec3: + r""" + A 3D vector with x, y, z components + """ + x: float + y: float + z: float + def __new__(cls,x = ...,y = ...,z = ...,values = ...): ... + def __repr__(self) -> str: + ... + + +def combine_with_video(video_path:str,krec_path:str,output_path:str) -> None: + ... + +def extract_from_video(video_path:str,output_path:str) -> None: + ... + diff --git a/python/pyproject.toml b/python/krec/bindings/pyproject.toml similarity index 95% rename from python/pyproject.toml rename to python/krec/bindings/pyproject.toml index 164540f..d4d3c3c 100644 --- a/python/pyproject.toml +++ b/python/krec/bindings/pyproject.toml @@ -3,7 +3,7 @@ requires = ["maturin>=1.0,<2.0"] build-backend = "maturin" [project] -name = "krec" +name = "bindings" version = "0.1.4" description = "Python bindings for KRec" authors = [{ name = "Denys Bezmenov", email = "denys@kscale.dev" }] diff --git a/python/src/bin/stub_gen.rs b/python/krec/bindings/src/bin/stub_gen.rs similarity index 100% rename from python/src/bin/stub_gen.rs rename to python/krec/bindings/src/bin/stub_gen.rs diff --git a/python/src/lib.rs b/python/krec/bindings/src/lib.rs similarity index 93% rename from python/src/lib.rs rename to python/krec/bindings/src/lib.rs index 863d46d..2937292 100644 --- a/python/src/lib.rs +++ b/python/krec/bindings/src/lib.rs @@ -1,33 +1,37 @@ -use ::krec::{ +use krec::{ ActuatorCommand, ActuatorConfig, ActuatorState, ImuQuaternion, ImuValues, KRec, KRecFrame, KRecHeader, Vec3, }; use pyo3::exceptions::{PyIndexError, PyValueError}; use pyo3::prelude::*; use pyo3::types::PyIterator; +use pyo3_stub_gen::define_stub_info_gatherer; +use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pyfunction, gen_stub_pymethods}; use tracing::{info, instrument}; /// A 3D vector with x, y, z components +#[gen_stub_pyclass] #[pyclass(name = "Vec3")] #[derive(Debug, Clone)] struct PyVec3 { inner: Vec3, } +#[gen_stub_pymethods] #[pymethods] impl PyVec3 { #[new] - #[pyo3(text_signature = "(x=0.0, y=0.0, z=0.0, /, values=None)")] + #[pyo3(signature = (x=None, y=None, z=None, values=None))] fn new( - py: Python<'_>, + _py: Python<'_>, x: Option, y: Option, z: Option, - values: Option<&PyAny>, + values: Option>, ) -> PyResult { if let Some(values) = values { // Try to convert from iterable - if let Ok(iter) = PyIterator::from_object(py, values) { + if let Ok(iter) = PyIterator::from_bound_object(&values) { let mut coords: Vec = Vec::new(); for item in iter { let value: f64 = item?.extract()?; @@ -76,26 +80,28 @@ impl PyVec3 { } /// A quaternion representing 3D rotation +#[gen_stub_pyclass] #[pyclass(name = "IMUQuaternion")] #[derive(Debug, Clone)] struct PyIMUQuaternion { inner: ImuQuaternion, } +#[gen_stub_pymethods] #[pymethods] impl PyIMUQuaternion { #[new] - #[pyo3(text_signature = "(x=0.0, y=0.0, z=0.0, w=1.0, /, values=None)")] + #[pyo3(signature = (x=None, y=None, z=None, w=None, values=None))] fn new( - py: Python<'_>, + _py: Python<'_>, x: Option, y: Option, z: Option, w: Option, - values: Option<&PyAny>, + values: Option>, ) -> PyResult { if let Some(values) = values { - if let Ok(iter) = PyIterator::from_object(py, values) { + if let Ok(iter) = PyIterator::from_bound_object(&values) { let mut coords: Vec = Vec::new(); for item in iter { let value: f64 = item?.extract()?; @@ -149,26 +155,28 @@ impl PyIMUQuaternion { } /// IMU sensor values including acceleration, gyroscope, and orientation data +#[gen_stub_pyclass] #[pyclass(name = "IMUValues")] #[derive(Debug, Clone)] struct PyIMUValues { inner: ImuValues, } +#[gen_stub_pymethods] #[pymethods] impl PyIMUValues { #[new] - #[pyo3(text_signature = "(accel=None, gyro=None, mag=None, quaternion=None, /, values=None)")] + #[pyo3(signature = (accel=None, gyro=None, mag=None, quaternion=None, values=None))] fn new( py: Python<'_>, accel: Option, gyro: Option, mag: Option, quaternion: Option, - values: Option<&PyAny>, + values: Option>, ) -> PyResult { if let Some(values) = values { - if let Ok(iter) = PyIterator::from_object(py, values) { + if let Ok(iter) = PyIterator::from_bound_object(&values) { let mut items = Vec::new(); for item in iter { let item = item?; @@ -270,18 +278,18 @@ impl PyIMUValues { } /// State information for a single actuator +#[gen_stub_pyclass] #[pyclass(name = "ActuatorState")] #[derive(Debug, Clone)] struct PyActuatorState { inner: ActuatorState, } +#[gen_stub_pymethods] #[pymethods] impl PyActuatorState { #[new] - #[pyo3( - text_signature = "(actuator_id, online=False, position=None, velocity=None, torque=None, temperature=None, voltage=None, current=None, /, values=None)" - )] + #[pyo3(signature = (actuator_id, online=None, position=None, velocity=None, torque=None, temperature=None, voltage=None, current=None, values=None))] fn new( py: Python<'_>, actuator_id: u32, @@ -292,10 +300,10 @@ impl PyActuatorState { temperature: Option, voltage: Option, current: Option, - values: Option<&PyAny>, + values: Option>, ) -> PyResult { if let Some(values) = values { - if let Ok(iter) = PyIterator::from_object(py, values) { + if let Ok(iter) = PyIterator::from_bound_object(&values) { let mut items = Vec::new(); for item in iter { let item = item?; @@ -439,18 +447,18 @@ impl PyActuatorState { } /// Configuration for an actuator +#[gen_stub_pyclass] #[pyclass(name = "ActuatorConfig")] #[derive(Debug, Clone)] struct PyActuatorConfig { inner: ActuatorConfig, } +#[gen_stub_pymethods] #[pymethods] impl PyActuatorConfig { #[new] - #[pyo3( - text_signature = "(actuator_id, kp=None, kd=None, ki=None, max_torque=None, name=None, /, values=None)" - )] + #[pyo3(signature = (actuator_id, kp=None, kd=None, ki=None, max_torque=None, name=None, values=None))] fn new( py: Python<'_>, actuator_id: u32, @@ -459,10 +467,10 @@ impl PyActuatorConfig { ki: Option, max_torque: Option, name: Option, - values: Option<&PyAny>, + values: Option>, ) -> PyResult { if let Some(values) = values { - if let Ok(iter) = PyIterator::from_object(py, values) { + if let Ok(iter) = PyIterator::from_bound_object(&values) { let mut items = Vec::new(); for item in iter { let item = item?; @@ -578,28 +586,28 @@ impl PyActuatorConfig { } /// Command for an actuator +#[gen_stub_pyclass] #[pyclass(name = "ActuatorCommand")] #[derive(Debug, Clone)] struct PyActuatorCommand { inner: ActuatorCommand, } +#[gen_stub_pymethods] #[pymethods] impl PyActuatorCommand { #[new] - #[pyo3( - text_signature = "(actuator_id, position=0.0, velocity=0.0, effort=0.0, /, values=None)" - )] + #[pyo3(signature = (actuator_id, position=None, velocity=None, torque=None, values=None))] fn new( - py: Python<'_>, + _py: Python<'_>, actuator_id: u32, position: Option, velocity: Option, torque: Option, - values: Option<&PyAny>, + values: Option>, ) -> PyResult { if let Some(values) = values { - if let Ok(iter) = PyIterator::from_object(py, values) { + if let Ok(iter) = PyIterator::from_bound_object(&values) { let mut coords: Vec = Vec::new(); for item in iter { let value: f32 = item?.extract()?; @@ -667,12 +675,14 @@ impl PyActuatorCommand { } } +#[gen_stub_pyclass] #[pyclass(name = "KRec")] #[derive(Debug, Clone)] struct PyKRec { inner: KRec, } +#[gen_stub_pymethods] #[pymethods] impl PyKRec { #[new] @@ -981,6 +991,7 @@ impl PyKRec { } } +#[gen_stub_pyclass] #[pyclass(name = "KRecHeader")] #[derive(Debug, Clone)] struct PyKRecHeader { @@ -988,11 +999,10 @@ struct PyKRecHeader { } #[pymethods] +#[gen_stub_pymethods] impl PyKRecHeader { #[new] - #[pyo3( - text_signature = "(uuid=None, task=None, robot_platform=None, robot_serial=None, start_timestamp=None, end_timestamp=None, /, values=None)" - )] + #[pyo3(signature = (uuid=None, task=None, robot_platform=None, robot_serial=None, start_timestamp=None, end_timestamp=None, values=None))] fn new( py: Python<'_>, uuid: Option, @@ -1001,10 +1011,10 @@ impl PyKRecHeader { robot_serial: Option, start_timestamp: Option, end_timestamp: Option, - values: Option<&PyAny>, + values: Option>, ) -> PyResult { if let Some(values) = values { - if let Ok(iter) = PyIterator::from_object(py, values) { + if let Ok(iter) = PyIterator::from_bound_object(&values) { let mut items = Vec::new(); for item in iter { let item = item?; @@ -1123,6 +1133,7 @@ impl PyKRecHeader { } } +#[gen_stub_pyclass] #[pyclass(name = "KRecFrame")] #[derive(Debug, Clone)] struct PyKRecFrame { @@ -1130,20 +1141,19 @@ struct PyKRecFrame { } #[pymethods] +#[gen_stub_pymethods] impl PyKRecFrame { #[new] - #[pyo3( - text_signature = "(video_timestamp=None, frame_number=None, inference_step=None, /, values=None)" - )] + #[pyo3(signature = (video_timestamp=None, frame_number=None, inference_step=None, values=None))] fn new( py: Python<'_>, video_timestamp: Option, frame_number: Option, inference_step: Option, - values: Option<&PyAny>, + values: Option>, ) -> PyResult { if let Some(values) = values { - if let Ok(iter) = PyIterator::from_object(py, values) { + if let Ok(iter) = PyIterator::from_bound_object(&values) { let mut items = Vec::new(); for item in iter { let item = item?; @@ -1258,6 +1268,7 @@ impl PyKRecFrame { } // Methods for IMU values + #[pyo3(signature = (imu=None))] fn set_imu_values(&mut self, imu: Option<&PyIMUValues>) { self.inner.imu_values = imu.map(|imu| imu.inner.clone()); } @@ -1285,12 +1296,14 @@ impl PyKRecFrame { } /// Iterator for frames +#[gen_stub_pyclass] #[pyclass] struct FrameIterator { frames: Vec, index: usize, } +#[gen_stub_pymethods] #[pymethods] impl FrameIterator { fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { @@ -1310,12 +1323,14 @@ impl FrameIterator { } } +#[gen_stub_pyfunction] #[pyfunction] fn combine_with_video(video_path: &str, krec_path: &str, output_path: &str) -> PyResult<()> { ::krec::combine_with_video(video_path, krec_path, output_path) .map_err(|e| PyErr::new::(e.to_string())) } +#[gen_stub_pyfunction] #[pyfunction] fn extract_from_video(video_path: &str, output_path: &str) -> PyResult<()> { ::krec::extract_from_video(video_path, output_path) @@ -1323,7 +1338,7 @@ fn extract_from_video(video_path: &str, output_path: &str) -> PyResult<()> { } #[pymodule] -fn krec(_py: Python, m: &PyModule) -> PyResult<()> { +fn bindings(m: &Bound) -> PyResult<()> { let _ = ::krec::init(); m.add_class::()?; m.add_class::()?; @@ -1340,3 +1355,5 @@ fn krec(_py: Python, m: &PyModule) -> PyResult<()> { Ok(()) } + +define_stub_info_gatherer!(stub_info); diff --git a/python/krec/py.typed b/python/krec/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f8f7d19 --- /dev/null +++ b/setup.py @@ -0,0 +1,63 @@ +# mypy: disable-error-code="import-untyped" +#!/usr/bin/env python +"""Setup script for the project.""" + +import os +import re +import shutil +import subprocess + +from setuptools import find_packages, setup +from setuptools.command.build_ext import build_ext +from setuptools_rust import Binding, RustExtension + +with open("README.md", "r", encoding="utf-8") as f: + long_description: str = f.read() + + +with open("Cargo.toml", "r", encoding="utf-8") as fh: + version_re = re.search(r"^version = \"([^\"]*)\"", fh.read(), re.MULTILINE) +assert version_re is not None, "Could not find version in Cargo.toml" +version: str = version_re.group(1) + + +class RustBuildExt(build_ext): + def run(self) -> None: + # Generate the stub file + subprocess.run(["cargo", "run", "--bin", "stub_gen"], check=True) + + # Move the generated stub file to parent directory + src_file = "krec/rust/rust.pyi" + dst_file = "krec/rust.pyi" + if os.path.exists(src_file) and not os.path.exists(dst_file): + shutil.move(src_file, dst_file) + if not os.path.exists(dst_file): + raise RuntimeError(f"Failed to generate {dst_file}") + if os.path.exists(src_file): + os.remove(src_file) + + super().run() + + +setup( + name="krec", + version=version, + description="Python bindings for K-Scale recordingss", + author="K-Scale Labs", + url="https://github.com/kscalelabs/krec", + rust_extensions=[ + RustExtension( + target="actuator.bindings", + path="actuator/bindings/Cargo.toml", + binding=Binding.PyO3, + ), + ], + setup_requires=["setuptools-rust"], + zip_safe=False, + long_description=long_description, + long_description_content_type="text/markdown", + python_requires=">=3.11", + include_package_data=True, + packages=find_packages(where="python", include=["python/krec"]), + cmdclass={"build_ext": RustBuildExt}, +)