Skip to content

Commit 25f149a

Browse files
authored
feat(schema): Support passing arguments for @context_func in jinja templates (#6882)
1 parent eb9510b commit 25f149a

File tree

8 files changed

+88
-16
lines changed

8 files changed

+88
-16
lines changed

packages/cubejs-backend-native/Cargo.lock

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cubejs-backend-native/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ libc = "0.2"
2828
findshlibs = "0.10.2"
2929
convert_case = "0.6.0"
3030
# python
31-
minijinja = { git = "https://github.com/mitsuhiko/minijinja.git", rev = "efdb9bee8b893b423157035631cfe3ae694c45cd", features = ["json", "loader"], optional = true }
31+
minijinja = { version = "1", features = ["json", "loader"], optional = true }
3232
pyo3 = { version = "0.18.3", features = [], optional = true }
3333
pyo3-asyncio = { version = "0.18.0", features = ["tokio-runtime", "attributes"], optional = true }
3434

packages/cubejs-backend-native/src/python/cross.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use pyo3::exceptions::{PyNotImplementedError, PyTypeError};
77
use pyo3::types::{PyBool, PyDict, PyFloat, PyFunction, PyInt, PyList, PyString};
88
use pyo3::{Py, PyAny, PyErr, PyObject, Python, ToPyObject};
99
use std::cell::RefCell;
10-
use std::collections::hash_map::{IntoIter, Iter};
10+
use std::collections::hash_map::{IntoIter, Iter, Keys};
1111
use std::collections::HashMap;
1212
use std::sync::Arc;
1313

@@ -34,6 +34,10 @@ impl CLReprObject {
3434
pub fn iter(&self) -> Iter<String, CLRepr> {
3535
self.0.iter()
3636
}
37+
38+
pub fn keys(&self) -> Keys<'_, String, CLRepr> {
39+
self.0.keys()
40+
}
3741
}
3842

3943
impl std::fmt::Debug for CLReprObject {

packages/cubejs-backend-native/src/python/template/mj_value.rs

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ use crate::python::cross::{CLRepr, CLReprKind, CLReprObject};
22
use crate::python::runtime::py_runtime;
33
use crate::tokio_runtime;
44
use minijinja as mj;
5-
use minijinja::value::{Object, ObjectKind, SeqObject, StructObject, Value};
5+
use minijinja::value::{Object, ObjectKind, SeqObject, StructObject, Value, ValueKind};
66
use pyo3::types::PyFunction;
77
use pyo3::Py;
8+
use std::convert::TryInto;
89
use std::sync::Arc;
910

1011
struct JinjaPythonFunction {
@@ -41,16 +42,15 @@ impl Object for JinjaPythonFunction {
4142
}
4243

4344
fn call(&self, _state: &mj::State, args: &[Value]) -> Result<Value, mj::Error> {
44-
if args.len() > 0 {
45-
return Err(mj::Error::new(
46-
mj::ErrorKind::EvalBlock,
47-
"Passing argument to python functions is not supported".to_string(),
48-
));
45+
let mut arguments = Vec::with_capacity(args.len());
46+
47+
for arg in args {
48+
arguments.push(from_minijinja_value(arg)?);
4949
}
5050

5151
let py_runtime = py_runtime()
5252
.map_err(|err| mj::Error::new(mj::ErrorKind::EvalBlock, format!("Error: {}", err)))?;
53-
let call_future = py_runtime.call_async(self.inner.clone(), vec![]);
53+
let call_future = py_runtime.call_async(self.inner.clone(), arguments);
5454

5555
let tokio = tokio_runtime()
5656
.map_err(|err| mj::Error::new(mj::ErrorKind::EvalBlock, format!("Error: {}", err)))?;
@@ -91,11 +91,8 @@ impl StructObject for JinjaDynamicObject {
9191
self.inner.get(name).map(|v| to_minijinja_value(v.clone()))
9292
}
9393

94-
fn fields(&self) -> Vec<Arc<String>> {
95-
self.inner
96-
.iter()
97-
.map(|(k, _)| Arc::new(k.clone()))
98-
.collect()
94+
fn fields(&self) -> Vec<Arc<str>> {
95+
self.inner.keys().map(|x| x.to_string().into()).collect()
9996
}
10097

10198
fn field_count(&self) -> usize {
@@ -180,6 +177,37 @@ impl Object for JinjaSequenceObject {
180177
}
181178
}
182179

180+
pub fn from_minijinja_value(from: &mj::value::Value) -> Result<CLRepr, mj::Error> {
181+
match from.kind() {
182+
ValueKind::Undefined | ValueKind::None => Ok(CLRepr::Null),
183+
ValueKind::Bool => Ok(CLRepr::Bool(from.is_true())),
184+
ValueKind::Number => {
185+
if let Ok(rv) = TryInto::<i64>::try_into(from.clone()) {
186+
Ok(CLRepr::Int(rv))
187+
} else if let Ok(rv) = TryInto::<f64>::try_into(from.clone()) {
188+
Ok(CLRepr::Float(rv))
189+
} else {
190+
Err(mj::Error::new(
191+
mj::ErrorKind::InvalidOperation,
192+
format!("Converting from {:?} to python is not supported", from),
193+
))
194+
}
195+
}
196+
ValueKind::String => {
197+
if from.is_safe() {
198+
// TODO: Danger?
199+
Ok(CLRepr::String(from.as_str().unwrap().to_string()))
200+
} else {
201+
Ok(CLRepr::String(from.as_str().unwrap().to_string()))
202+
}
203+
}
204+
other => Err(mj::Error::new(
205+
mj::ErrorKind::InvalidOperation,
206+
format!("Converting from {:?} to python is not supported", other),
207+
)),
208+
}
209+
}
210+
183211
pub fn to_minijinja_value(from: CLRepr) -> Value {
184212
match from {
185213
CLRepr::Array(inner) => Value::from_seq_object(JinjaSequenceObject { inner }),

packages/cubejs-backend-native/test/__snapshots__/jinja.test.ts.snap

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,16 @@ exports[`Jinja render 08.yml.jinja: 08.yml.jinja 1`] = `
174174
data_source: postgres }"
175175
`;
176176

177+
exports[`Jinja render arguments-test.yml.jinja: arguments-test.yml.jinja 1`] = `
178+
"test:
179+
arg_sum_integers_int_int: 2
180+
arg_sum_integers_int_float: 4.140000000000001
181+
arg_bool_true: 1
182+
arg_bool_false: 0
183+
arg_str: hello world
184+
arg_null: none"
185+
`;
186+
177187
exports[`Jinja render data-model.yml.jinja: data-model.yml.jinja 1`] = `
178188
"cubes:
179189

packages/cubejs-backend-native/test/jinja.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ suite('Python models', () => {
5656
expect(pythonModule).toEqual({
5757
load_data: expect.any(Object),
5858
load_data_sync: expect.any(Object),
59+
arg_bool: expect.any(Object),
60+
arg_sum_integers: expect.any(Object),
61+
arg_str: expect.any(Object),
62+
arg_null: expect.any(Object),
5963
});
6064
});
6165
});
@@ -69,6 +73,7 @@ suite('Jinja', () => {
6973
loadTemplateFile(jinjaEngine, '.utils.jinja');
7074
loadTemplateFile(jinjaEngine, 'dump_context.yml.jinja');
7175
loadTemplateFile(jinjaEngine, 'data-model.yml.jinja');
76+
loadTemplateFile(jinjaEngine, 'arguments-test.yml.jinja');
7277

7378
for (let i = 1; i < 9; i++) {
7479
loadTemplateFile(jinjaEngine, `0${i}.yml.jinja`);
@@ -90,6 +95,7 @@ suite('Jinja', () => {
9095
}
9196
});
9297
testTemplateWithPythonCtxBySnapshot(jinjaEngine, 'data-model.yml.jinja', {});
98+
testTemplateWithPythonCtxBySnapshot(jinjaEngine, 'arguments-test.yml.jinja', {});
9399

94100
testLoadBrokenTemplateBySnapshot(jinjaEngine, 'template_error.jinja');
95101

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
test:
2+
arg_sum_integers_int_int: {{ arg_sum_integers(1, 1) }}
3+
arg_sum_integers_int_float: {{ arg_sum_integers(1, 3.14) }}
4+
arg_bool_true: {{ arg_bool(true) }}
5+
arg_bool_false: {{ arg_bool(false) }}
6+
arg_str: {{ arg_str("hello world") }}
7+
arg_null: {{ arg_null(null) }}

packages/cubejs-backend-native/test/templates/utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
from cube import context_func
22

3+
@context_func
4+
def arg_sum_integers(a, b):
5+
return a + b
6+
7+
@context_func
8+
def arg_bool(a):
9+
return a + 0
10+
11+
@context_func
12+
def arg_str(a):
13+
return a
14+
15+
@context_func
16+
def arg_null(a):
17+
return a
18+
319
@context_func
420
def load_data_sync():
521
client = MyApiClient("google.com")

0 commit comments

Comments
 (0)