Skip to content

Commit

Permalink
Merge pull request #23 from maebli/feature/python-bindings
Browse files Browse the repository at this point in the history
Feature/python bindings
  • Loading branch information
maebli authored Sep 17, 2024
2 parents df37980 + 97c7333 commit cb02790
Show file tree
Hide file tree
Showing 10 changed files with 302 additions and 4 deletions.
185 changes: 185 additions & 0 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
name: Python Bindings CI

on:
push:
tags:
- 'python-v*'
workflow_dispatch:

permissions:
contents: read

jobs:
linux:
runs-on: ${{ matrix.platform.runner }}
defaults:
run:
working-directory: ./python
strategy:
matrix:
platform:
- runner: ubuntu-latest
target: x86_64
- runner: ubuntu-latest
target: x86
- runner: ubuntu-latest
target: aarch64
- runner: ubuntu-latest
target: armv7
- runner: ubuntu-latest
target: s390x
- runner: ubuntu-latest
target: ppc64le
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --find-interpreter
sccache: 'true'
manylinux: auto
working-directory: ./python
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-linux-${{ matrix.platform.target }}
path: ./python/dist

musllinux:
runs-on: ${{ matrix.platform.runner }}
defaults:
run:
working-directory: ./python
strategy:
matrix:
platform:
- runner: ubuntu-latest
target: x86_64
- runner: ubuntu-latest
target: x86
- runner: ubuntu-latest
target: aarch64
- runner: ubuntu-latest
target: armv7
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --find-interpreter
sccache: 'true'
manylinux: musllinux_1_2
working-directory: ./python
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-musllinux-${{ matrix.platform.target }}
path: ./python/dist

windows:
runs-on: ${{ matrix.platform.runner }}
defaults:
run:
working-directory: ./python
strategy:
matrix:
platform:
- runner: windows-latest
target: x64
- runner: windows-latest
target: x86
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
architecture: ${{ matrix.platform.target }}
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --find-interpreter
sccache: 'true'
working-directory: ./python
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-windows-${{ matrix.platform.target }}
path: ./python/dist

macos:
runs-on: ${{ matrix.platform.runner }}
defaults:
run:
working-directory: ./python
strategy:
matrix:
platform:
- runner: macos-12
target: x86_64
- runner: macos-14
target: aarch64
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --find-interpreter
sccache: 'true'
working-directory: ./python
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-macos-${{ matrix.platform.target }}
path: ./python/dist

sdist:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./python
steps:
- uses: actions/checkout@v4
- name: Build sdist
uses: PyO3/maturin-action@v1
with:
command: sdist
args: --out dist
working-directory: ./python
- name: Upload sdist
uses: actions/upload-artifact@v4
with:
name: wheels-sdist
path: ./python/dist

release:
name: Release
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./python
needs: [linux, musllinux, windows, macos, sdist]
steps:
- uses: actions/download-artifact@v4
with:
path: ./python
- name: Publish to PyPI
uses: PyO3/maturin-action@v1
env:
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
with:
command: upload
args: --non-interactive --skip-existing wheels-*/*
working-directory: ./python
3 changes: 1 addition & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
- 'wasm-v*'
pull_request:
branches: [main]
workflow_dispatch:

jobs:
build-library:
Expand Down Expand Up @@ -122,8 +123,6 @@ jobs:
run: |
cd cli/src
cargo publish --token ${{ secrets.CRATESTOKEN }}
publish-wasm:
needs: build-wasm
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ serde = { version = "1.0", features = ["derive"], optional = true }
bitflags = "2.4.2"
arrayvec = { version = "0.7.4", default-features = false }
[workspace]
members = ["cli", "wasm"]
members = ["cli", "wasm","python"]
exclude = ["examples/cortex-m"]

[[bench]]
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[![Discord](https://img.shields.io/badge/Discord-Join%20Now-blue?style=flat&logo=Discord)](https://discord.gg/FfmecQ4wua)
[![Crates.io](https://img.shields.io/crates/v/m-bus-parser.svg)](https://crates.io/crates/m-bus-parser) [![Downloads](https://img.shields.io/crates/d/m-bus-parser.svg)](https://crates.io/crates/m-bus-parser) [![License](https://img.shields.io/crates/l/m-bus-parser.svg)](https://crates.io/crates/m-bus-parser) [![Documentation](https://docs.rs/m-bus-parser/badge.svg)](https://docs.rs/m-bus-parser) [![Build Status](https://github.com/maebli/m-bus-parser/actions/workflows/rust.yml/badge.svg)](https://github.com/maebli/m-bus-parser/actions/workflows/rust.yml)


### Introduction

*For contributing see [CONTRIBUTING.md](./CONTRIBUTING.md)*
Expand Down Expand Up @@ -35,6 +36,10 @@ The source is in the wasm folder in this repos

There is a cli, the source is in the sub folder "cli" and is published on crates.io [https://crates.io/crates/m-bus-parser-cli](https://crates.io/crates/m-bus-parser-cli).

### Python bindings
[![PyPI version](https://badge.fury.io/py/pymbusparser.png)](https://badge.fury.io/py/pymbusparser)

The are some python bindings, the source is in the sub folder "python" and is published on pypi [https://pypi.org/project/pymbusparser/](https://pypi.org/project/pymbusparser/).

### Visualization of Library Function

Expand Down
1 change: 0 additions & 1 deletion examples/example_parsing_user_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use m_bus_parser::user_data::DataRecords;

fn main() {
/* Data block 1: unit 0, storage No 0, no tariff, instantaneous volume, 12565 l (24 bit integer) */
/* Data block 2: unit 0, storage No 0, no tariff, instantaneous volume, 12565 l (24 bit integer) */
let data = vec![0x03, 0x13, 0x15, 0x31, 0x00, 0x03, 0x13, 0x15, 0x31, 0x00];
let result = DataRecords::try_from(data.as_slice());
assert!(result.is_ok());
Expand Down
1 change: 1 addition & 0 deletions python/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target
15 changes: 15 additions & 0 deletions python/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "pymbusparser"
version = "0.0.1"
edition = "2021"
homepage = "https://maebli.github.io/"
repository = "https://github.com/maebli/m-bus-parser"

[dependencies]
m-bus-parser = { path = "..", version = "0.0.13", features = ["std", "serde"] }
serde_json = "1.0"
pyo3 = { version = "0.22.2", features = ["extension-module","generate-import-lib"] }
hex = "0.4.2"

[lib]
name = "pymbusparser"
35 changes: 35 additions & 0 deletions python/README_python.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Python bindings [WIP]

Rust lib aims to be accessible from a Python module. This is done by using the `PyO3` crate.

## Development

- `pipx install maturin` to install `maturin` globally.
- `maturin develop` to build the Rust lib and create a Python module in the current environment.
- Currently this creates a release in the target directory that is one hierachy up. This is not ideal and will be fixed in the future.
- after calling the maturin develop command cd one up and `pip install target/..` and then run `python` and `from pymbusparser import pymbus` to test the module.
- to test inside `REPL` run `python` and then `import p

## Publishing

- `maturin publish`
- username = __token__
- passwrod = api key from pypi

## Usage

`pip install pymbusparser`

```python
from pymbusparser import m_bus_parse,parse_application_layer

print(m_bus_parse("68 3D 3D 68 08 01 72 00 51 20 02 82 4D 02 04 00 88 00 00 04 07 00 00 00 00 0C 15 03 00 00 00 0B 2E 00 00 00 0B 3B 00 00 00 0A 5A 88 12 0A 5E 16 05 0B 61 23 77 00 02 6C 8C 11 02 27 37 0D 0F 60 00 67 16","table"))

print(m_bus_parse("68 3D 3D 68 08 01 72 00 51 20 02 82 4D 02 04 00 88 00 00 04 07 00 00 00 00 0C 15 03 00 00 00 0B 2E 00 00 00 0B 3B 00 00 00 0A 5A 88 12 0A 5E 16 05 0B 61 23 77 00 02 6C 8C 11 02 27 37 0D 0F 60 00 67 16","json"))

print(m_bus_parse("68 3D 3D 68 08 01 72 00 51 20 02 82 4D 02 04 00 88 00 00 04 07 00 00 00 00 0C 15 03 00 00 00 0B 2E 00 00 00 0B 3B 00 00 00 0A 5A 88 12 0A 5E )16 05 0B 61 23 77 00 02 6C 8C 11 02 27 37 0D 0F 60 00 67 16","yml"))

# note this is not as pretty as the function before, still TODO, currently it just outputs structs of RUST in string
print(parse_application_layer("2f2f0413fce0f5052f2f2f2f2f2f2f2f"))

```
13 changes: 13 additions & 0 deletions python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[project]
name = "pymbusparser"
version = "0.0.1"
description = "Python bindings for m-bus-parser written in Rust"
homepage = "https://maebli.github.io/"
authors = [{name = "Michael Aebli", email = ""}]
license = { text = "MIT" }
readme = "README_python.md"


[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"
46 changes: 46 additions & 0 deletions python/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use hex;
use m_bus_parser::serialize_mbus_data;
use m_bus_parser::user_data::DataRecords;
use pyo3::prelude::*;
use serde_json;

#[pyfunction]
fn parse_application_layer(data_record: &str) -> PyResult<String> {
// Decode the hex string into bytes
match hex::decode(data_record) {
Ok(bytes) => {
// Try to parse the bytes into DataRecords
match DataRecords::try_from(bytes.as_slice()) {
Ok(records) => {
// Serialize the records to JSON using Serde
match serde_json::to_string(&records) {
Ok(json) => Ok(json),
Err(e) => Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
"Failed to serialize records to JSON: {}",
e
))),
}
}
Err(_) => Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
"Failed to parse data record",
)),
}
}
Err(e) => Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(format!(
"Failed to decode hex: {}",
e
))),
}
}

#[pyfunction]
pub fn m_bus_parse(data: &str, format: &str) -> String {
serialize_mbus_data(data, format)
}

#[pymodule]
fn pymbusparser(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(parse_application_layer, m)?)?;
m.add_function(wrap_pyfunction!(m_bus_parse, m)?)?;
Ok(())
}

0 comments on commit cb02790

Please sign in to comment.