Skip to content

Commit

Permalink
Add macros for function dispatch (#45)
Browse files Browse the repository at this point in the history
* Have dispatch with 1 arg working

* Finished up dispatch with time funcs

* Working on converting all functions to dispatch

* Finished converting to new dispatch

* Update bytes and bytecode to wrapped types

* Fix pyo3 interface

* Update pyo3 to 23

* revert resolver version

* Update workflow to publish readme

* Add rscel-macros to publish list

---------

Co-authored-by: matt <[email protected]>
  • Loading branch information
1BADragon and CedarMatt authored Jan 12, 2025
1 parent ed01300 commit 791751f
Show file tree
Hide file tree
Showing 63 changed files with 2,037 additions and 704 deletions.
42 changes: 23 additions & 19 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Rust

on:
push:
branches: [ "main" ]
branches: ["main"]
pull_request:
branches: [ "main", "develop" ]
branches: ["main", "develop"]

env:
CARGO_TERM_COLOR: always
Expand All @@ -18,29 +18,29 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v3

- uses: Swatinem/rust-cache@v2

- uses: Swatinem/rust-cache@v2

- name: Build
run: cargo build --workspace
- name: Build
run: cargo build --workspace

test-all:
runs-on: ubuntu-latest

steps:
- name: Install Protoc
uses: arduino/setup-protoc@v3
- name: Install Protoc
uses: arduino/setup-protoc@v3

- uses: actions/checkout@v3
- uses: actions/checkout@v3

- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@v2

- name: Install wasm-pack
run: cargo install wasm-pack
- name: Install wasm-pack
run: cargo install wasm-pack

- name: Run tests
run: make run-all-tests
- name: Run tests
run: make run-all-tests

deploy:
runs-on: ubuntu-latest
Expand All @@ -53,10 +53,14 @@ jobs:

- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
toolchain: stable
override: true

- name: publish
run: cargo publish -p rscel --token ${{ secrets.CRATES_IO_API_KEY }}
- name: Copy Readme
run: cp README.md rscel/README.md

- name: publish rscel-macro
run: cargo publish -p rscel-macro --token ${{ secrets.CRATES_IO_API_KEY }}

- name: publish rscel
run: cargo publish -p rscel --token ${{ secrets.CRATES_IO_API_KEY }}
11 changes: 3 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
[workspace]
members = [
"rscel",
"python",
"wasm"
]
members = ["rscel", "python", "wasm", "rscel-macro"]
default-members = ["rscel"]
resolver="2"
resolver = "2"

[workspace.package]
version = "1.0.3"
version = "1.0.4"
edition = "2021"
description = "Cel interpreter in rust"
license = "MIT"
Expand All @@ -24,4 +20,3 @@ lto = true
[workspace.dependencies]
chrono = { version = "0.4.38", features = ["serde"] }
serde_json = { version = "1.0.121", features = ["raw_value"] }

2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2024 Matt B
Copyright (c) 2025 Matt B

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
10 changes: 7 additions & 3 deletions python/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@ crate-type = ["cdylib"]

[dependencies]
rscel = { path = "../rscel" }
pyo3 = { version = "0.21.2", features = ["extension-module", "chrono"] }
pyo3 = { version = "0.23", features = [
"py-clone",
"extension-module",
"chrono",
] }
chrono = { workspace = true }
serde_json = { workspace = true }
serde_json = { workspace = true }
bincode = "1.3.3"

[build-dependencies]
pyo3-build-config = "0.21.2"
pyo3-build-config = "0.23"
52 changes: 33 additions & 19 deletions python/src/celpycallable.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::py_cel_value::{PyCelValue, PyCelValueRef};
use pyo3::{types::PyTuple, Py, PyAny, PyObject, Python, ToPyObject};
use pyo3::{types::PyTuple, Bound, IntoPyObject, Py, PyAny, PyErr, Python};
use rscel::{CelError, CelValue};

pub struct CelPyCallable {
Expand All @@ -17,21 +17,23 @@ impl FnOnce<(CelValue, Vec<CelValue>)> for CelPyCallable {

extern "rust-call" fn call_once(self, args: (CelValue, Vec<CelValue>)) -> Self::Output {
Python::with_gil(|py| {
match self.func.call_bound(
match self.func.call(
py,
PyTuple::new_bound(
PyTuple::new(
py,
&[args.0]
.iter()
.filter(|x| !x.is_null())
.map(|x| PyCelValueRef::new(x).to_object(py))
.map(|x| PyCelValueRef::new(x).into_pyobject(py))
.chain(
args.1
.into_iter()
.map(|x| PyCelValueRef::new(&x).to_object(py)),
.map(|x| PyCelValueRef::new(&x).into_pyobject(py)),
)
.collect::<Vec<PyObject>>(),
),
.collect::<Result<Vec<Bound<'_, PyAny>>, PyErr>>()
.expect("argument collection failed"),
)
.expect("pytuple construction failed"),
None,
) {
Ok(val) => val.extract::<PyCelValue>(py).unwrap().into_inner(),
Expand All @@ -44,21 +46,27 @@ impl FnOnce<(CelValue, Vec<CelValue>)> for CelPyCallable {
impl FnMut<(CelValue, Vec<CelValue>)> for CelPyCallable {
extern "rust-call" fn call_mut(&mut self, args: (CelValue, Vec<CelValue>)) -> Self::Output {
Python::with_gil(|py| {
match self.func.call_bound(
match self.func.call(
py,
PyTuple::new_bound(
PyTuple::new(
py,
&[args.0]
.iter()
.filter(|x| !x.is_null())
.map(|x| PyCelValueRef::new(x).to_object(py))
.map(|x| {
PyCelValueRef::new(x)
.into_pyobject(py)
.map(|o| o.into_any())
})
.chain(
args.1
.into_iter()
.map(|x| PyCelValueRef::new(&x).to_object(py)),
.map(|x| PyCelValueRef::new(&x).into_pyobject(py)),
)
.collect::<Vec<PyObject>>(),
),
.collect::<Result<Vec<Bound<'_, PyAny>>, PyErr>>()
.expect("argument collection failed"),
)
.expect("pytuple construction failed"),
None,
) {
Ok(val) => val.extract::<PyCelValue>(py).unwrap().into_inner(),
Expand All @@ -71,21 +79,27 @@ impl FnMut<(CelValue, Vec<CelValue>)> for CelPyCallable {
impl Fn<(CelValue, Vec<CelValue>)> for CelPyCallable {
extern "rust-call" fn call(&self, args: (CelValue, Vec<CelValue>)) -> Self::Output {
Python::with_gil(|py| {
match self.func.call_bound(
match self.func.call(
py,
PyTuple::new_bound(
PyTuple::new(
py,
&[args.0]
.iter()
.filter(|x| !x.is_null())
.map(|x| PyCelValueRef::new(x).to_object(py))
.map(|x| {
PyCelValueRef::new(x)
.into_pyobject(py)
.map(|o| o.into_any())
})
.chain(
args.1
.into_iter()
.map(|x| PyCelValueRef::new(&x).to_object(py)),
.map(|x| PyCelValueRef::new(&x).into_pyobject(py)),
)
.collect::<Vec<PyObject>>(),
),
.collect::<Result<Vec<Bound<'_, PyAny>>, PyErr>>()
.expect("argument collection failed"),
)
.expect("pytuple construction failed"),
None,
) {
Ok(val) => val.extract::<PyCelValue>(py).unwrap().into_inner(),
Expand Down
66 changes: 36 additions & 30 deletions python/src/frompyobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ use pyo3::{
conversion::FromPyObject,
exceptions::PyValueError,
types::{
timezone_utc_bound, PyBool, PyBytes, PyDateTime, PyDelta, PyDict, PyFloat, PyInt, PyList,
PyString, PyTuple,
timezone_utc, PyAnyMethods, PyBool, PyBytes, PyDateTime, PyDelta, PyDict, PyDictMethods,
PyFloat, PyInt, PyList, PyString, PyStringMethods, PyTuple, PyTypeMethods,
},
PyAny, PyErr, PyResult, PyTypeCheck, Python,
Bound, PyAny, PyErr, PyResult, PyTypeCheck,
};

use rscel::CelValue;
Expand Down Expand Up @@ -37,12 +37,12 @@ trait WrappedExtract<'a> {

macro_rules! wrapped_extract {
($type_name:ident) => {
impl<'a> WrappedExtract<'a> for $type_name {
impl<'a> WrappedExtract<'a> for &Bound<'_, $type_name> {
fn wrapped_extract<D>(&'a self, path: &[&str]) -> Result<D, WrappedError>
where
D: FromPyObject<'a>,
{
match self.extract::<D>() {
match D::extract_bound(self) {
Ok(val) => Ok(val),
Err(err) => Err(WrappedError::new(err, path)),
}
Expand All @@ -58,29 +58,26 @@ wrapped_extract!(PyString);
wrapped_extract!(PyBytes);
wrapped_extract!(PyDateTime);
wrapped_extract!(PyDelta);
impl<'a> WrappedExtract<'a> for &PyAny {
impl<'a> WrappedExtract<'a> for Bound<'_, PyAny> {
fn wrapped_extract<D>(&'a self, path: &[&str]) -> Result<D, WrappedError>
where
D: FromPyObject<'a>,
{
match self.extract() {
match D::extract_bound(self) {
Ok(val) => Ok(val),
Err(err) => Err(WrappedError::new(err, path)),
}
}
}

trait WrappedDowncast {
fn wrapped_downcast<D>(&self, path: &[&str]) -> Result<&D, WrappedError>
fn wrapped_downcast<D>(&self, path: &[&str]) -> Result<&Bound<'_, D>, WrappedError>
where
D: PyTypeCheck<AsRefTarget = D>;
D: PyTypeCheck;
}

impl WrappedDowncast for &PyAny {
fn wrapped_downcast<D: PyTypeCheck<AsRefTarget = D>>(
&self,
path: &[&str],
) -> Result<&D, WrappedError>
impl WrappedDowncast for &Bound<'_, PyAny> {
fn wrapped_downcast<D>(&self, path: &[&str]) -> Result<&Bound<'_, D>, WrappedError>
where
D: PyTypeCheck,
{
Expand All @@ -95,11 +92,11 @@ impl WrappedDowncast for &PyAny {
}

fn extract_celval_recurse<'source>(
ob: &PyAny,
ob: &Bound<'source, PyAny>,
current_path: &'source [&'source str],
) -> Result<PyCelValue, WrappedError> {
match ob.get_type().name() {
Ok(type_name) => match type_name.as_ref() {
Ok(type_name) => match type_name.to_str().expect("to get python type as str") {
"int" => Ok(PyCelValue::new(
ob.wrapped_downcast::<PyInt>(current_path)?
.wrapped_extract::<i64>(current_path)?
Expand Down Expand Up @@ -132,9 +129,11 @@ fn extract_celval_recurse<'source>(

for (i, val) in ob
.wrapped_downcast::<PyList>(current_path)?
.iter()
.try_iter()
.expect("list to iterate")
.enumerate()
{
let val = val.expect("val to exist");
next_path.push(format!("{}", i));
vec.push(
val.wrapped_extract::<PyCelValue>(
Expand All @@ -153,7 +152,8 @@ fn extract_celval_recurse<'source>(
let mut map: HashMap<String, CelValue> = HashMap::new();

let mapobj = ob.wrapped_downcast::<PyDict>(current_path)?;
for keyobj in mapobj.keys().iter() {
for keyobj in mapobj.keys().try_iter().expect("keys to iterate") {
let keyobj = keyobj.expect("keyobj to exist");
let key = match keyobj.downcast::<PyString>() {
Ok(val) => val.to_string(),
Err(_) => {
Expand Down Expand Up @@ -186,33 +186,39 @@ fn extract_celval_recurse<'source>(

Ok(PyCelValue::new(map.into()))
}
"datetime.datetime" => {
let py_utc_dt = match Python::with_gil(|py| {
let utc = timezone_utc_bound(py);
let py_astimezone = ob.getattr("astimezone")?;
"datetime.datetime" | "datetime" => {
let py = ob.py();

let args = PyTuple::new_bound(py, [utc]);
let utc = timezone_utc(py);
let py_astimezone = match ob.getattr("astimezone") {
Ok(v) => v,
Err(e) => return Err(WrappedError::new(e, current_path)),
};

let args = match PyTuple::new(py, [utc]) {
Ok(v) => v,
Err(e) => return Err(WrappedError::new(e, current_path)),
};

py_astimezone.call1(args)
}) {
let py_utc_dt = match py_astimezone.call1(args) {
Ok(val) => val,
Err(err) => return Err(WrappedError::new(err, current_path)),
};

let dt = py_utc_dt
let dt = (&py_utc_dt)
.wrapped_downcast::<PyDateTime>(current_path)?
.wrapped_extract::<DateTime<Utc>>(current_path)?;

Ok(PyCelValue::new(dt.with_timezone(&Utc).into()))
Ok(PyCelValue::new(dt.into()))
}
"datetime.timedelta" => Ok(PyCelValue::new(
"datetime.timedelta" | "timedelta" => Ok(PyCelValue::new(
ob.wrapped_downcast::<PyDelta>(current_path)?
.wrapped_extract::<Duration>(current_path)?
.into(),
)),
"NoneType" => Ok(PyCelValue::new(CelValue::from_null())),
_ => Ok(PyCelValue::new(CelValue::Dyn(Arc::<CelPyObject>::new(
CelPyObject::new(ob.into()),
CelPyObject::new(ob.clone().unbind()),
)))),
},
Err(_) => Err(WrappedError::new(
Expand All @@ -223,7 +229,7 @@ fn extract_celval_recurse<'source>(
}

impl<'source> FromPyObject<'source> for PyCelValue {
fn extract(ob: &PyAny) -> PyResult<Self> {
fn extract_bound(ob: &pyo3::Bound<'source, PyAny>) -> PyResult<Self> {
match extract_celval_recurse(ob, &[]) {
Ok(val) => Ok(val),
Err(WrappedError { err, path }) => Err(PyValueError::new_err(format!(
Expand Down
Loading

0 comments on commit 791751f

Please sign in to comment.