Skip to content

Commit 2fea585

Browse files
committed
add key-value and Redis execute support
Signed-off-by: Joel Dice <[email protected]>
1 parent 9410b21 commit 2fea585

File tree

2 files changed

+102
-6
lines changed

2 files changed

+102
-6
lines changed

crates/spin-python-cli/src/main.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ fn main() -> Result<()> {
7474

7575
wizer.map_dir("python", &options.python_home);
7676

77-
env::set_var("PYTHONPATH", &format!("/python:{python_path}"));
77+
env::set_var("PYTHONPATH", format!("/python:{python_path}"));
7878
env::set_var("PYTHONHOME", "/python");
7979

8080
fs::write(
@@ -110,8 +110,7 @@ fn main() -> Result<()> {
110110
.env("SPIN_PYTHON_WIZEN", "1")
111111
.arg(&options.app_name)
112112
.arg(
113-
&temp
114-
.path()
113+
temp.path()
115114
.to_str()
116115
.context("non-UTF-8 temporary directory name")?,
117116
)

crates/spin-python-engine/src/lib.rs

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ use {
88
pyo3::{
99
exceptions::PyAssertionError,
1010
types::{PyBytes, PyMapping, PyModule},
11-
Py, PyErr, PyObject, PyResult, Python,
11+
Py, PyAny, PyErr, PyObject, PyResult, Python, ToPyObject,
1212
},
1313
spin_sdk::{
1414
config,
1515
http::{Request, Response},
16-
outbound_http, redis,
16+
key_value, outbound_http,
17+
redis::{self, RedisParameter, RedisResult},
1718
},
1819
std::{env, ops::Deref, str},
1920
};
@@ -87,6 +88,40 @@ impl HttpResponse {
8788
}
8889
}
8990

91+
#[derive(Clone)]
92+
#[pyo3::pyclass]
93+
#[pyo3(name = "Store")]
94+
struct Store {
95+
inner: key_value::Store,
96+
}
97+
98+
#[pyo3::pymethods]
99+
impl Store {
100+
fn get(&self, py: Python<'_>, key: String) -> PyResult<PyObject> {
101+
match self.inner.get(key) {
102+
Ok(v) => bytes(py, &v).map(PyObject::from),
103+
Err(key_value::Error::NoSuchKey) => Ok(py.None()),
104+
Err(e) => Err(PyErr::from(Anyhow::from(e))),
105+
}
106+
}
107+
108+
fn set(&self, key: String, value: &PyBytes) -> Result<(), Anyhow> {
109+
self.inner.set(key, value.as_bytes()).map_err(Anyhow::from)
110+
}
111+
112+
fn delete(&self, key: String) -> Result<(), Anyhow> {
113+
self.inner.delete(key).map_err(Anyhow::from)
114+
}
115+
116+
fn exists(&self, key: String) -> Result<bool, Anyhow> {
117+
self.inner.exists(key).map_err(Anyhow::from)
118+
}
119+
120+
fn get_keys(&self) -> Result<Vec<String>, Anyhow> {
121+
self.inner.get_keys().map_err(Anyhow::from)
122+
}
123+
}
124+
90125
struct Anyhow(Error);
91126

92127
impl From<Anyhow> for PyErr {
@@ -213,6 +248,45 @@ fn redis_srem(address: String, key: String, values: Vec<String>) -> PyResult<i64
213248
.map_err(|_| PyErr::from(Anyhow(anyhow!("Error executing Redis set command"))))
214249
}
215250

251+
#[pyo3::pyfunction]
252+
#[pyo3(pass_module)]
253+
fn redis_execute(
254+
module: &PyModule,
255+
address: String,
256+
command: String,
257+
arguments: Vec<&PyAny>,
258+
) -> PyResult<Vec<PyObject>> {
259+
let arguments = arguments
260+
.iter()
261+
.map(|v| {
262+
if let Ok(v) = v.extract::<i64>() {
263+
Ok(RedisParameter::Int64(v))
264+
} else if let Ok(v) = v.downcast::<PyBytes>() {
265+
Ok(RedisParameter::Binary(v.as_bytes()))
266+
} else {
267+
Err(PyErr::from(Anyhow(anyhow!(
268+
"Unable to use {v:?} as a Redis `execute` argument \
269+
-- expected `int` or `bytes`"
270+
))))
271+
}
272+
})
273+
.collect::<PyResult<Vec<_>>>()?;
274+
275+
redis::execute(&address, &command, &arguments)
276+
.map_err(|_| PyErr::from(Anyhow(anyhow!("Error executing Redis set command"))))
277+
.and_then(|results| {
278+
results
279+
.into_iter()
280+
.map(|v| match v {
281+
RedisResult::Nil => Ok(module.py().None()),
282+
RedisResult::Status(v) => Ok(v.to_object(module.py())),
283+
RedisResult::Int64(v) => Ok(v.to_object(module.py())),
284+
RedisResult::Binary(v) => bytes(module.py(), &v).map(PyObject::from),
285+
})
286+
.collect::<PyResult<_>>()
287+
})
288+
}
289+
216290
#[pyo3::pymodule]
217291
#[pyo3(name = "spin_redis")]
218292
fn spin_redis_module(_py: Python<'_>, module: &PyModule) -> PyResult<()> {
@@ -223,7 +297,29 @@ fn spin_redis_module(_py: Python<'_>, module: &PyModule) -> PyResult<()> {
223297
module.add_function(pyo3::wrap_pyfunction!(redis_sadd, module)?)?;
224298
module.add_function(pyo3::wrap_pyfunction!(redis_set, module)?)?;
225299
module.add_function(pyo3::wrap_pyfunction!(redis_smembers, module)?)?;
226-
module.add_function(pyo3::wrap_pyfunction!(redis_srem, module)?)
300+
module.add_function(pyo3::wrap_pyfunction!(redis_srem, module)?)?;
301+
module.add_function(pyo3::wrap_pyfunction!(redis_execute, module)?)
302+
}
303+
304+
#[pyo3::pyfunction]
305+
fn kv_open(name: String) -> Result<Store, Anyhow> {
306+
Ok(Store {
307+
inner: key_value::Store::open(name).map_err(Anyhow::from)?,
308+
})
309+
}
310+
311+
#[pyo3::pyfunction]
312+
fn kv_open_default() -> Result<Store, Anyhow> {
313+
Ok(Store {
314+
inner: key_value::Store::open_default().map_err(Anyhow::from)?,
315+
})
316+
}
317+
318+
#[pyo3::pymodule]
319+
#[pyo3(name = "spin_key_value")]
320+
fn spin_key_value_module(_py: Python<'_>, module: &PyModule) -> PyResult<()> {
321+
module.add_function(pyo3::wrap_pyfunction!(kv_open, module)?)?;
322+
module.add_function(pyo3::wrap_pyfunction!(kv_open_default, module)?)
227323
}
228324

229325
#[pyo3::pyfunction]
@@ -241,6 +337,7 @@ fn do_init() -> Result<()> {
241337
pyo3::append_to_inittab!(spin_http_module);
242338
pyo3::append_to_inittab!(spin_redis_module);
243339
pyo3::append_to_inittab!(spin_config_module);
340+
pyo3::append_to_inittab!(spin_key_value_module);
244341

245342
pyo3::prepare_freethreaded_python();
246343

0 commit comments

Comments
 (0)