From 9cb47bedb772df70c3f57616b6333769cdb22b6d Mon Sep 17 00:00:00 2001 From: Chris Arderne Date: Mon, 15 Apr 2024 22:45:11 +0100 Subject: [PATCH] add python wrapper --- Makefile | 2 +- pyproject.toml | 53 +++++++++++++++++++++++++++++++++++++- python/bean_rs/__init__.py | 7 +++++ python/bean_rs/bean_rs.pyi | 19 ++++++++++++++ python/bean_rs/py.typed | 0 python/tests/test_load.py | 6 +++++ requirements-dev.lock | 23 +++++++++++++++++ requirements.lock | 10 +++++++ src/error.rs | 7 ++--- src/ledger.rs | 1 + src/lib.rs | 5 ++-- 11 files changed, 126 insertions(+), 7 deletions(-) create mode 100644 python/bean_rs/__init__.py create mode 100644 python/bean_rs/bean_rs.pyi create mode 100644 python/bean_rs/py.typed create mode 100644 python/tests/test_load.py create mode 100644 requirements-dev.lock create mode 100644 requirements.lock diff --git a/Makefile b/Makefile index 492633c..d6fe7c1 100644 --- a/Makefile +++ b/Makefile @@ -10,4 +10,4 @@ cov: .PHONY: py py: - maturin develop + rye run maturin develop --pip-path ~/.rye/self/bin/pip diff --git a/pyproject.toml b/pyproject.toml index ec8e8f5..fb17078 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,13 @@ +[tool.rye.scripts] +check = "pyright" +test = "pytest" + [build-system] requires = ["maturin>=1.5,<2.0"] build-backend = "maturin" [project] name = "bean-rs" -requires-python = ">= 3.9" classifiers = [ "Environment :: Console", "License :: OSI Approved :: MIT License", @@ -15,5 +18,53 @@ classifiers = [ ] dynamic = ["version"] +requires-python = ">= 3.9" + +dependencies = [ +] + +[project.urls] +homepage = "https://github.com/carderne/signal-export" +repository = "https://github.com/carderne/signal-export" + +[tool.rye] +managed = true +dev-dependencies = [ + "maturin>=1.5,<2.0", + "pyright", + "pytest", +] + [tool.maturin] +python-source = "python" +module-name = "bean_rs._bean_rs" features = ["pyo3/extension-module"] + +[tool.ruff] +target-version = "py39" + +[tool.ruff.lint] +select = [ + "F", + "E", + "I", + "U", + "N", + "E", + "T100", + "A", + "Q", + "ANN", +] + +[tool.pyright] +venvPath = "." +venv = ".venv" +extraPaths = ["./python/bean_rs"] +include = ["python"] +reportMissingImports = true +reportMissingParameterType = true +reportUnnecessaryTypeIgnoreComment = true +reportDeprecated = true +pythonVersion = "3.12" +pythonPlatform = "Linux" diff --git a/python/bean_rs/__init__.py b/python/bean_rs/__init__.py new file mode 100644 index 0000000..c83b6a5 --- /dev/null +++ b/python/bean_rs/__init__.py @@ -0,0 +1,7 @@ +"""Python binds to a beancount clone in Rust.""" + +# The Rust bindings are in `_bean_rs` and we try to mediate all access +# through this file to make typing a bit easier +from bean_rs._bean_rs import load + +__all__ = ["load"] diff --git a/python/bean_rs/bean_rs.pyi b/python/bean_rs/bean_rs.pyi new file mode 100644 index 0000000..3231d7a --- /dev/null +++ b/python/bean_rs/bean_rs.pyi @@ -0,0 +1,19 @@ +import datetime + +class Options: + title: str + operating_currency: str + +class BeanError(Exception): + pass + +class Directive: + date: datetime.date + +class Ledger: + dirs: list[Directive] + errs: list[BeanError] + opts: Options + +def load(path: str) -> Ledger: + pass diff --git a/python/bean_rs/py.typed b/python/bean_rs/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/python/tests/test_load.py b/python/tests/test_load.py new file mode 100644 index 0000000..97a1bc2 --- /dev/null +++ b/python/tests/test_load.py @@ -0,0 +1,6 @@ +from bean_rs import load + + +def test_load() -> None: + ledger = load("example.bean") + assert ledger.opts.operating_currency == "GBP" diff --git a/requirements-dev.lock b/requirements-dev.lock new file mode 100644 index 0000000..5c640bd --- /dev/null +++ b/requirements-dev.lock @@ -0,0 +1,23 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false +# with-sources: false + +-e file:. +iniconfig==2.0.0 + # via pytest +maturin==1.5.1 +nodeenv==1.8.0 + # via pyright +packaging==24.0 + # via pytest +pluggy==1.4.0 + # via pytest +pyright==1.1.358 +pytest==8.1.1 +setuptools==69.5.1 + # via nodeenv diff --git a/requirements.lock b/requirements.lock new file mode 100644 index 0000000..8f23096 --- /dev/null +++ b/requirements.lock @@ -0,0 +1,10 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false +# with-sources: false + +-e file:. diff --git a/src/error.rs b/src/error.rs index 7465078..f959940 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,4 @@ +use pyo3::pyclass; use std::fmt; use crate::data::{DebugLine, Directive}; @@ -16,10 +17,10 @@ pub enum ErrorType { InvalidCcy, } -#[allow(dead_code)] -#[derive(Debug)] +#[pyclass] +#[derive(Clone, Debug)] pub struct BeanError { - pub ty: ErrorType, // not yet used anywhere + pub ty: ErrorType, pub debug: DebugLine, pub msg: String, } diff --git a/src/ledger.rs b/src/ledger.rs index b184a25..c0e1a22 100644 --- a/src/ledger.rs +++ b/src/ledger.rs @@ -6,6 +6,7 @@ use crate::error::BeanError; #[pyclass] pub struct Ledger { pub dirs: Vec, + #[pyo3(get)] pub errs: Vec, #[pyo3(get)] pub opts: Options, diff --git a/src/lib.rs b/src/lib.rs index 01da8e1..d3131b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,14 +45,15 @@ pub fn balance(path: &str) -> (AccBal, Vec) { /// Load the ledger from Python #[pyfunction] +#[pyo3(name = "load")] fn py_load(path: &str) -> Ledger { let text = std::fs::read_to_string(path).expect("cannot read file"); load(text) } -/// `bean_rs` importable from Python +/// `_bean_rs` importable from Python #[pymodule] -fn bean_rs(m: &Bound<'_, PyModule>) -> PyResult<()> { +fn _bean_rs(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(py_load, m)?)?; Ok(()) }