Skip to content

Commit

Permalink
Merge pull request #3 from messense/python-binding
Browse files Browse the repository at this point in the history
Add Python binding
  • Loading branch information
messense authored Mar 10, 2021
2 parents a5b86d8 + c34cc57 commit 6369fc5
Show file tree
Hide file tree
Showing 6 changed files with 314 additions and 0 deletions.
184 changes: 184 additions & 0 deletions .github/workflows/Python.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
name: Python

on:
push:
pull_request:

jobs:
macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: 3.6
architecture: x64
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: aarch64-apple-darwin
profile: minimal
default: true
- name: Install maturin
run: pip install maturin
- name: Build wheels - x86_64
run: |
maturin build -i python --target x86_64-apple-darwin --release --out dist -m python/Cargo.toml
pip install crfs --no-index --find-links dist --force-reinstall
python -c "import crfs"
- name: Build wheels - universal2
env:
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
MACOSX_DEPLOYMENT_TARGET: '10.9'
PYO3_CROSS_LIB_DIR: /Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions/3.8/lib
run: |
# Build wheels
maturin build -i python --release --universal2 --out dist --no-sdist -m python/Cargo.toml
pip install crfs --no-index --find-links dist --force-reinstall
python -c "import crfs"
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist

windows:
runs-on: windows-latest
strategy:
matrix:
platform: [
{ python-architecture: "x64", target: "x86_64-pc-windows-msvc" },
{ python-architecture: "x86", target: "i686-pc-windows-msvc" },
]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: 3.6
architecture: ${{ matrix.platform.python-architecture }}
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: ${{ matrix.platform.target }}
profile: minimal
default: true
- name: Install maturin
run: pip install maturin
- name: Build wheels
run: |
maturin build -i python --release --out dist --no-sdist -m python/Cargo.toml --target ${{ matrix.platform.target }}
pip install crfs --no-index --find-links dist --force-reinstall
python -c "import crfs"
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist

linux:
runs-on: ubuntu-latest
strategy:
matrix:
platform: [
{ manylinux: '2010', target: "x86_64-unknown-linux-gnu", arch: "x86_64" },
{ manylinux: '2010', target: "i686-unknown-linux-gnu", arch: "i686" },
]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: 3.6
architecture: x64
- name: Build Wheels
run: |
echo 'curl --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
source ~/.cargo/env
export PATH=/opt/python/cp38-cp38/bin:$PATH
pip install maturin
maturin build -i python --release --out dist --no-sdist -m python/Cargo.toml --target ${{ matrix.platform.target }} --manylinux ${{ matrix.platform.manylinux }}
pip install crfs --no-index --find-links dist --force-reinstall
python -c "import crfs"
' > build-wheel.sh
docker run --rm -v "$PWD":/io -w /io quay.io/pypa/manylinux${{ matrix.platform.manylinux }}_${{ matrix.platform.arch }} bash build-wheel.sh
- name: Auditwheel Symbols
run: |
pip install auditwheel-symbols
auditwheel-symbols dist/*.whl
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist

linux-cross:
runs-on: ubuntu-latest
strategy:
matrix:
platform: [
{ manylinux: "2014", target: "aarch64-unknown-linux-gnu", arch: "aarch64" },
{ manylinux: "2014", target: "armv7-unknown-linux-gnueabihf", arch: "armv7" },
]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: 3.6
- name: Build Wheels
run: |
echo 'curl -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
source ~/.cargo/env
rustup target add ${{ matrix.platform.target }}
maturin build -i python --release --out dist --no-sdist -m python/Cargo.toml --target ${{ matrix.platform.target }} --manylinux ${{ matrix.platform.manylinux }}
' > build-wheel.sh
docker run --rm -v "$PWD":/io -w /io messense/manylinux2014-cross:${{ matrix.platform.arch }} bash build-wheel.sh
- uses: uraimo/[email protected]
name: Install built wheel
with:
arch: ${{ matrix.platform.arch }}
distro: ubuntu18.04
githubToken: ${{ github.token }}
# Mount the dist directory as /artifacts in the container
dockerRunArgs: |
--volume "${PWD}/dist:/artifacts"
install: |
apt-get update
apt-get install -y --no-install-recommends python3 python3-pip
pip3 install -U pip
run: |
ls -lrth /artifacts
pip3 install crfs --no-index --find-links /artifacts --force-reinstall
python3 -c "import crfs"
- name: Auditwheel Symbols
run: |
pip install auditwheel-symbols
auditwheel-symbols dist/*.whl
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist


release:
name: Release
runs-on: ubuntu-latest
if: "startsWith(github.ref, 'refs/tags/')"
needs: [ macos, windows, linux, linux-cross ]
steps:
- uses: actions/download-artifact@v2
with:
name: wheels
- uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Publish to PyPi
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
pip install --upgrade wheel pip setuptools twine
twine upload --skip-existing *
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/target
/python/target
Cargo.lock
tests/output/*.cqdb
11 changes: 11 additions & 0 deletions python/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[target.x86_64-apple-darwin]
rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
]

[target.aarch64-apple-darwin]
rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
]
24 changes: 24 additions & 0 deletions python/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "py-crfs"
version = "0.1.0"
authors = ["messense <[email protected]>"]
edition = "2018"
license = "MIT"
keywords = ["crf", "crfsuite"]
readme = "README.md"
homepage = "https://github.com/messense/crfs-rs"
repository = "https://github.com/messense/crfs-rs.git"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
name = "crfs"
crate-type = ["cdylib"]

[dependencies]
crfs-rs = { package = "crfs", version = "0.1.0" }
ouroboros = "0.8.2"
pyo3 = { version = "0.13.2", features = ["abi3-py36", "extension-module"] }

[package.metadata.maturin]
name = "crfs"
Empty file added python/README.md
Empty file.
94 changes: 94 additions & 0 deletions python/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use std::fs;

use crfs_rs::{Attribute, Model};
use ouroboros::self_referencing;
use pyo3::prelude::*;

#[pyclass(module = "crfs", name = "Attribute")]
#[derive(FromPyObject)]
struct PyAttribute {
/// Attribute name
#[pyo3(get, set)]
name: String,
/// Value of the attribute
#[pyo3(get, set)]
value: f64,
}

#[pymethods]
impl PyAttribute {
#[new]
fn new(name: String, value: f64) -> Self {
Self { name, value }
}
}

#[derive(FromPyObject)]
enum PyAttributeInput {
#[pyo3(transparent)]
Attr(PyAttribute),
Dict {
/// Attribute name
#[pyo3(item("name"))]
name: String,
/// Value of the attribute
#[pyo3(item("value"))]
value: f64,
},
Tuple(String, f64),
}

impl From<PyAttributeInput> for Attribute {
fn from(attr: PyAttributeInput) -> Self {
match attr {
PyAttributeInput::Attr(PyAttribute { name, value }) => Attribute::new(name, value),
PyAttributeInput::Dict { name, value } => Attribute::new(name, value),
PyAttributeInput::Tuple(name, value) => Attribute::new(name, value),
}
}
}

#[pyclass(module = "crfs", name = "Model")]
#[self_referencing]
struct PyModel {
data: Vec<u8>,
#[borrows(data)]
#[not_covariant]
model: Model<'this>,
}

#[pymethods]
impl PyModel {
#[new]
fn new_py(data: Vec<u8>) -> PyResult<Self> {
let model = PyModelTryBuilder {
data,
model_builder: |model_data| Model::new(model_data),
}
.try_build()?;
Ok(model)
}

#[staticmethod]
fn open(path: &str) -> PyResult<Self> {
let data = fs::read(path)?;
Self::new_py(data)
}

pub fn tag(&self, xseq: Vec<Vec<PyAttributeInput>>) -> PyResult<Vec<String>> {
let mut tagger = self.with_model(|model| model.tagger())?;
let xseq: Vec<Vec<Attribute>> = xseq
.into_iter()
.map(|xs| xs.into_iter().map(Into::into).collect())
.collect();
let labels = tagger.tag(&xseq)?;
Ok(labels.iter().map(|l| l.to_string()).collect())
}
}

#[pymodule]
fn crfs(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<PyAttribute>()?;
m.add_class::<PyModel>()?;
Ok(())
}

0 comments on commit 6369fc5

Please sign in to comment.