Skip to content

Commit

Permalink
Convert from eval to callExprString API
Browse files Browse the repository at this point in the history
  • Loading branch information
infinisil authored and yorickvP committed Apr 14, 2023
1 parent 8f6eb73 commit 51a003b
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 59 deletions.
24 changes: 13 additions & 11 deletions python/src/eval.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,16 @@ const char * currentExceptionTypeName()
return res ? res : "(null)";
}

static PyObject * _eval(const std::string & expression, PyObject * vars)
static PyObject * _eval(const std::string & expression, PyObject * argument)
{
nix::Strings storePath;
nix::EvalState state(storePath, nix::openStore());

nix::Env * env = nullptr;
auto staticEnv = pythonToNixEnv(state, vars, &env);
if (!staticEnv) {
auto nixArgument = pythonToNixValue(state, argument);
if (!nixArgument) {
return nullptr;
}
auto staticEnvPointer = std::make_shared<nix::StaticEnv>(*staticEnv);
nix::Value fun;
nix::Value v;


Expand All @@ -44,25 +43,28 @@ static PyObject * _eval(const std::string & expression, PyObject * vars)
});

// TODO: Should the "." be something else here?
auto e = state.parseExprFromString(expression, ".", staticEnvPointer);
e->eval(state, *env, v);
auto e = state.parseExprFromString(expression, ".");
state.eval(e, fun);
// TODO: Add position
state.callFunction(fun, *nixArgument, v, noPos);
state.forceValueDeep(v);
}

nix::PathSet context;
return nixToPythonObject(state, v, context);
}

// TODO: Rename this function to callExprString, matching the Python name
PyObject * eval(PyObject * self, PyObject * args, PyObject * keywds)
{
PyObject * expressionObject;
PyObject * vars = nullptr;
PyObject * argument = nullptr;

const char * kwlist[] = {"expression", "vars", nullptr};
const char * kwlist[] = {"expression", "arg", nullptr};

// See https://docs.python.org/3/c-api/arg.html for the magic string
if (!PyArg_ParseTupleAndKeywords(
args, keywds, "U|O!", const_cast<char **>(kwlist), &expressionObject, &PyDict_Type, &vars)) {
args, keywds, "UO", const_cast<char **>(kwlist), &expressionObject, &argument)) {
return nullptr;
}

Expand All @@ -75,7 +77,7 @@ PyObject * eval(PyObject * self, PyObject * args, PyObject * keywds)
std::string expression(expressionBase, expressionSize);

try {
return _eval(expression, vars);
return _eval(expression, argument);
} catch (nix::ThrownError & e) {
return PyErr_Format(ThrownNixError, "%s", e.message().c_str());
} catch (nix::Error & e) {
Expand Down
1 change: 0 additions & 1 deletion python/src/internal/python-to-nix.hh
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,4 @@ namespace nix::python {

nix::Value * pythonToNixValue(nix::EvalState & state, PyObject * obj);

std::optional<nix::StaticEnv> pythonToNixEnv(nix::EvalState & state, PyObject * vars, nix::Env ** env);
} // namespace nix::python
2 changes: 1 addition & 1 deletion python/src/python-module.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ PyObject * ThrownNixError = nullptr;
PyObject * NixError = nullptr;

static PyMethodDef NixMethods[] = {
{"eval", (PyCFunction) eval, METH_VARARGS | METH_KEYWORDS, "Eval nix expression"}, {NULL, NULL, 0, NULL}};
{"callExprString", (PyCFunction) eval, METH_VARARGS | METH_KEYWORDS, "Eval nix expression"}, {NULL, NULL, 0, NULL}};

static struct PyModuleDef nixmodule = {
PyModuleDef_HEAD_INIT, "nix", "Nix expression bindings",
Expand Down
32 changes: 0 additions & 32 deletions python/src/python-to-nix.cc
Original file line number Diff line number Diff line change
Expand Up @@ -130,36 +130,4 @@ nix::Value * pythonToNixValue(nix::EvalState & state, PyObject * obj)
}
return v;
}

std::optional<nix::StaticEnv> pythonToNixEnv(nix::EvalState & state, PyObject * vars, nix::Env ** env)
{
Py_ssize_t pos = 0;
PyObject *key = nullptr, *val = nullptr;

*env = &state.allocEnv(vars ? PyDict_Size(vars) : 0);
(*env)->up = &state.baseEnv;

nix::StaticEnv staticEnv(false, state.staticBaseEnv.get());

if (!vars) {
return staticEnv;
}

auto displ = 0;
while (PyDict_Next(vars, &pos, &key, &val)) {
auto name = checkAttrKey(key);
if (!name) {
return {};
}

auto attrVal = pythonToNixValue(state, val);
if (!attrVal) {
return {};
}
staticEnv.vars.emplace_back(state.symbols.create(name), displ);
(*env)->values[displ++] = attrVal;
}

return staticEnv;
}
} // namespace nix::python
29 changes: 15 additions & 14 deletions python/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,64 +5,65 @@
class TestPythonNix(unittest.TestCase):
def test_dict(self):
val = dict(a=1)
self.assertEqual(nix.eval("a", vars=dict(a=val)), val)
self.assertEqual(nix.callExprString("{ a }: a", arg=dict(a=val)), val)

def test_string(self):
self.assertEqual(nix.eval("a", vars=dict(a="foo")), "foo")
self.assertEqual(nix.callExprString("{ a }: a", arg=dict(a="foo")), "foo")

def test_bool(self):
self.assertEqual(nix.eval("a", vars=dict(a=True)), True)
self.assertEqual(nix.callExprString("{ a }: a", arg=dict(a=True)), True)

def test_none(self):
self.assertEqual(nix.eval("a", vars=dict(a=None)), None)
self.assertEqual(nix.callExprString("{ a }: a", arg=dict(a=None)), None)

def test_ifd(self):
expression = """
{}:
builtins.readFile (derivation {
name = "test";
args = [ "-c" "printf \\"%s\\" test > $out" ];
builder = "/bin/sh";
system = builtins.currentSystem;
})
"""
self.assertEqual(nix.eval(expression, vars=dict()), "test")
self.assertEqual(nix.callExprString(expression, arg={}), "test")

def test_throw(self):
errorString = "hello hi there\ntest"
with self.assertRaises(nix.ThrownNixError) as cm:
nix.eval("throw str", vars=dict(str=errorString))
nix.callExprString("{ str }: throw str", arg=dict(str=errorString))
self.assertEqual(cm.exception.args[0], errorString)

def test_syntax_error(self):
with self.assertRaises(nix.NixError) as cm:
nix.eval("{")
nix.callExprString("{", arg={})

def test_GIL_case(self):
with self.assertRaises(nix.ThrownNixError) as cm:
nix.eval("{ a = throw \"nope\"; }")
nix.callExprString("{}: { a = throw \"nope\"; }", arg={})
self.assertEqual(cm.exception.args[0], "nope")

def test_infinity(self):
with self.assertRaises(nix.NixError):
nix.eval("let x = { inherit x; }; in x")
nix.callExprString("{}: let x = { inherit x; }; in x", arg={})

def test_null_expression(self):
# Null characters should be allowed in expressions, even if they aren't
# very useful really, though at least null's should be supported in
# strings in the future https://github.com/NixOS/nix/issues/1307)
self.assertEqual(nix.eval("\"ab\x00cd\""), "ab")
self.assertEqual(nix.callExprString("{}: \"ab\x00cd\"", arg={}), "ab")

def test_throw_null(self):
with self.assertRaises(nix.ThrownNixError) as cm:
nix.eval("throw \"hello\x00there\"")
nix.callExprString("{}: throw \"hello\x00there\"", arg={})
self.assertEqual(cm.exception.args[0], "hello")

def test_booleans(self):
self.assertIs(nix.eval("assert a == true; a", vars=dict(a=True)), True)
self.assertIs(nix.eval("assert a == false; a", vars=dict(a=False)), False)
self.assertIs(nix.callExprString("{ a }: assert a == true; a", arg=dict(a=True)), True)
self.assertIs(nix.callExprString("{ a }: assert a == false; a", arg=dict(a=False)), False)

def test_null(self):
self.assertIs(nix.eval("assert a == null; a", vars=dict(a=None)), None)
self.assertIs(nix.callExprString("{ a }: assert a == null; a", arg=dict(a=None)), None)

if __name__ == '__main__':
unittest.main()

0 comments on commit 51a003b

Please sign in to comment.