Skip to content

Commit fb01582

Browse files
authored
refactor(native): Jinja - separate injection of Python filters (#7377)
1 parent cd1019c commit fb01582

File tree

3 files changed

+91
-74
lines changed

3 files changed

+91
-74
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use crate::cross::*;
2+
use crate::template::mj_value::{from_minijinja_value, to_minijinja_value};
3+
use minijinja as mj;
4+
use neon::prelude::*;
5+
use pyo3::prelude::*;
6+
use pyo3::{exceptions::PyNotImplementedError, types::PyTuple, AsPyPointer};
7+
8+
pub fn mj_inject_python_extension(
9+
cx: &mut FunctionContext,
10+
options: Handle<JsObject>,
11+
engine: &mut mj::Environment,
12+
) -> NeonResult<()> {
13+
let filters = options
14+
.get_value(cx, "filters")?
15+
.downcast_or_throw::<JsObject, _>(cx)?;
16+
17+
let filter_names = filters.get_own_property_names(cx)?;
18+
for i in 0..filter_names.len(cx) {
19+
let filter_name: Handle<JsString> = filter_names.get(cx, i)?;
20+
let filter_fun = CLRepr::from_js_ref(filters.get_value(cx, filter_name)?, cx)?;
21+
22+
let py_fun = match filter_fun {
23+
CLRepr::PythonRef(py_ref) => match py_ref {
24+
PythonRef::PyFunction(py_fun_ref) | PythonRef::PyExternalFunction(py_fun_ref) => {
25+
py_fun_ref
26+
}
27+
other => {
28+
return cx.throw_error(format!(
29+
"minijinja::filter must be a function, actual: CLRepr::PythonRef({:?})",
30+
other
31+
))
32+
}
33+
},
34+
other => {
35+
return cx.throw_error(format!(
36+
"minijinja::filter must be a function, actual: {:?}",
37+
other.kind()
38+
))
39+
}
40+
};
41+
42+
engine.add_filter(
43+
filter_name.value(cx),
44+
move |_state: &mj::State,
45+
args: &[mj::value::Value]|
46+
-> Result<mj::value::Value, mj::Error> {
47+
let mut arguments = Vec::with_capacity(args.len());
48+
49+
for arg in args {
50+
arguments.push(from_minijinja_value(arg)?);
51+
}
52+
53+
let python_call_res = Python::with_gil(|py| {
54+
let mut args_tuple = Vec::with_capacity(args.len());
55+
56+
for arg in arguments {
57+
args_tuple.push(arg.into_py(py)?);
58+
}
59+
60+
let tuple = PyTuple::new(py, args_tuple);
61+
62+
let call_res = py_fun.call1(py, tuple)?;
63+
64+
let is_coroutine =
65+
unsafe { pyo3::ffi::PyCoro_CheckExact(call_res.as_ptr()) == 1 };
66+
if is_coroutine {
67+
Err(PyErr::new::<PyNotImplementedError, _>(
68+
"Calling async is not supported",
69+
))
70+
} else {
71+
CLRepr::from_python_ref(call_res.as_ref(py))
72+
}
73+
});
74+
match python_call_res {
75+
Ok(r) => Ok(to_minijinja_value(r)),
76+
Err(err) => Err(mj::Error::new(
77+
minijinja::ErrorKind::InvalidOperation,
78+
format!("Error while calling filter: {}", err),
79+
)),
80+
}
81+
},
82+
);
83+
}
84+
85+
Ok(())
86+
}

packages/cubejs-backend-native/src/template/entry.rs

Lines changed: 3 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ use minijinja as mj;
88
use neon::prelude::*;
99
use std::cell::RefCell;
1010

11-
use crate::template::workers::{JinjaEngineWorkerJob, JinjaEngineWorkerPool};
1211
#[cfg(feature = "python")]
13-
use pyo3::{exceptions::PyNotImplementedError, prelude::*, types::PyTuple, AsPyPointer};
12+
use crate::template::engine_python;
13+
use crate::template::workers::{JinjaEngineWorkerJob, JinjaEngineWorkerPool};
1414

1515
struct JinjaEngine {
1616
inner: mj::Environment<'static>,
@@ -53,78 +53,7 @@ impl JinjaEngine {
5353
engine.set_auto_escape_callback(|_name: &str| mj::AutoEscape::Json);
5454

5555
#[cfg(feature = "python")]
56-
{
57-
let filters = options
58-
.get_value(cx, "filters")?
59-
.downcast_or_throw::<JsObject, _>(cx)?;
60-
61-
let filter_names = filters.get_own_property_names(cx)?;
62-
for i in 0..filter_names.len(cx) {
63-
let filter_name: Handle<JsString> = filter_names.get(cx, i)?;
64-
let filter_fun = CLRepr::from_js_ref(filters.get_value(cx, filter_name)?, cx)?;
65-
66-
let py_fun = match filter_fun {
67-
CLRepr::PythonRef(py_ref) => match py_ref {
68-
PythonRef::PyFunction(py_fun_ref)
69-
| PythonRef::PyExternalFunction(py_fun_ref) => py_fun_ref,
70-
other => {
71-
return cx.throw_error(format!(
72-
"minijinja::filter must be a function, actual: CLRepr::PythonRef({:?})",
73-
other
74-
))
75-
}
76-
},
77-
other => {
78-
return cx.throw_error(format!(
79-
"minijinja::filter must be a function, actual: {:?}",
80-
other.kind()
81-
))
82-
}
83-
};
84-
85-
engine.add_filter(
86-
filter_name.value(cx),
87-
move |_state: &mj::State,
88-
args: &[mj::value::Value]|
89-
-> Result<mj::value::Value, mj::Error> {
90-
let mut arguments = Vec::with_capacity(args.len());
91-
92-
for arg in args {
93-
arguments.push(from_minijinja_value(arg)?);
94-
}
95-
96-
let python_call_res = Python::with_gil(|py| {
97-
let mut args_tuple = Vec::with_capacity(args.len());
98-
99-
for arg in arguments {
100-
args_tuple.push(arg.into_py(py)?);
101-
}
102-
103-
let tuple = PyTuple::new(py, args_tuple);
104-
105-
let call_res = py_fun.call1(py, tuple)?;
106-
107-
let is_coroutine =
108-
unsafe { pyo3::ffi::PyCoro_CheckExact(call_res.as_ptr()) == 1 };
109-
if is_coroutine {
110-
Err(PyErr::new::<PyNotImplementedError, _>(
111-
"Calling async is not supported",
112-
))
113-
} else {
114-
CLRepr::from_python_ref(call_res.as_ref(py))
115-
}
116-
});
117-
match python_call_res {
118-
Ok(r) => Ok(to_minijinja_value(r)),
119-
Err(err) => Err(mj::Error::new(
120-
minijinja::ErrorKind::InvalidOperation,
121-
format!("Error while calling filter: {}", err),
122-
)),
123-
}
124-
},
125-
)
126-
}
127-
}
56+
engine_python::mj_inject_python_extension(cx, options, &mut engine)?;
12857

12958
let workers_count = {
13059
let workers_count_float = options

packages/cubejs-backend-native/src/template/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#[cfg(feature = "python")]
2+
mod engine_python;
13
mod entry;
24
mod mj_value;
35
mod neon;

0 commit comments

Comments
 (0)