-
-
Notifications
You must be signed in to change notification settings - Fork 139
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add nested dict support #208
Changes from 5 commits
b117e5e
a2ebf1c
b9bc4a7
35dffcf
8623bbb
e2c5faa
12b21d0
642b375
04b1bcd
0684e26
56f6dac
3c321ca
b21907f
31edf83
56d50a6
33020ef
b241664
7a69ee8
0e07ff5
c3ca11a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,10 +48,11 @@ cdef object exc_info | |
from sys import exc_info | ||
|
||
cdef object Mapping | ||
cdef object Sequence | ||
try: | ||
from collections.abc import Mapping | ||
from collections.abc import Mapping, Sequence | ||
except ImportError: | ||
from collections import Mapping # Py2 | ||
from collections import Mapping, Sequence # Py2 | ||
|
||
cdef object wraps | ||
from functools import wraps | ||
|
@@ -156,6 +157,10 @@ def lua_type(obj): | |
lua.lua_settop(L, old_top) | ||
unlock_runtime(lua_object._runtime) | ||
|
||
cdef inline int _len_as_int(Py_ssize_t obj) except -1: | ||
if obj > <Py_ssize_t>LONG_MAX: | ||
raise OverflowError | ||
return <int>obj | ||
|
||
@cython.no_gc_clear | ||
cdef class LuaRuntime: | ||
|
@@ -408,15 +413,15 @@ cdef class LuaRuntime: | |
""" | ||
return self.table_from(items, kwargs) | ||
|
||
def table_from(self, *args): | ||
def table_from(self, *args, bint recursive=False): | ||
"""Create a new table from Python mapping or iterable. | ||
|
||
table_from() accepts either a dict/mapping or an iterable with items. | ||
Items from dicts are set as key-value pairs; items from iterables | ||
are placed in the table in order. | ||
|
||
Nested mappings / iterables are passed to Lua as userdata | ||
(wrapped Python objects); they are not converted to Lua tables. | ||
(wrapped Python objects) if recursive is False; they are not converted to Lua tables. | ||
""" | ||
assert self._state is not NULL | ||
cdef lua_State *L = self._state | ||
|
@@ -426,12 +431,12 @@ cdef class LuaRuntime: | |
try: | ||
check_lua_stack(L, 5) | ||
lua.lua_newtable(L) | ||
# FIXME: how to check for failure? | ||
# FIXME: how to check for failure? and nested dict | ||
for obj in args: | ||
if isinstance(obj, dict): | ||
for key, value in obj.iteritems(): | ||
py_to_lua(self, L, key) | ||
py_to_lua(self, L, value) | ||
py_to_lua(self, L, key, wrap_none=False, recursive=recursive) | ||
py_to_lua(self, L, value, wrap_none=False, recursive=recursive) | ||
lua.lua_rawset(L, -3) | ||
|
||
elif isinstance(obj, _LuaTable): | ||
|
@@ -447,12 +452,12 @@ cdef class LuaRuntime: | |
elif isinstance(obj, Mapping): | ||
for key in obj: | ||
value = obj[key] | ||
py_to_lua(self, L, key) | ||
py_to_lua(self, L, value) | ||
py_to_lua(self, L, key, wrap_none=False, recursive=recursive) | ||
py_to_lua(self, L, value, wrap_none=False, recursive=recursive) | ||
lua.lua_rawset(L, -3) | ||
else: | ||
for arg in obj: | ||
py_to_lua(self, L, arg) | ||
py_to_lua(self, L, arg, wrap_none=False, recursive=recursive) | ||
lua.lua_rawseti(L, -2, i) | ||
i += 1 | ||
return py_from_lua(self, L, -1) | ||
|
@@ -1135,7 +1140,7 @@ cdef object resume_lua_thread(_LuaThread thread, tuple args): | |
# already terminated | ||
raise StopIteration | ||
if args: | ||
nargs = len(args) | ||
nargs = _len_as_int(len(args)) | ||
push_lua_arguments(thread._runtime, co, args) | ||
with nogil: | ||
status = lua.lua_resume(co, L, nargs, &nres) | ||
|
@@ -1381,7 +1386,7 @@ cdef py_object* unpack_userdata(lua_State *L, int n) nogil: | |
cdef int py_function_result_to_lua(LuaRuntime runtime, lua_State *L, object o) except -1: | ||
if runtime._unpack_returned_tuples and isinstance(o, tuple): | ||
push_lua_arguments(runtime, L, <tuple>o) | ||
return len(<tuple>o) | ||
return _len_as_int(len(<tuple>o)) | ||
check_lua_stack(L, 1) | ||
return py_to_lua(runtime, L, o) | ||
|
||
|
@@ -1410,7 +1415,7 @@ cdef int py_to_lua_handle_overflow(LuaRuntime runtime, lua_State *L, object o) e | |
lua.lua_settop(L, old_top) | ||
raise | ||
|
||
cdef int py_to_lua(LuaRuntime runtime, lua_State *L, object o, bint wrap_none=False) except -1: | ||
cdef int py_to_lua(LuaRuntime runtime, lua_State *L, object o, bint wrap_none=False, bint recursive=False) except -1: | ||
"""Converts Python object to Lua | ||
Preconditions: | ||
1 extra slot in the Lua stack | ||
|
@@ -1462,13 +1467,26 @@ cdef int py_to_lua(LuaRuntime runtime, lua_State *L, object o, bint wrap_none=Fa | |
elif isinstance(o, float): | ||
lua.lua_pushnumber(L, <lua.lua_Number><double>o) | ||
pushed_values_count = 1 | ||
elif isinstance(o, _PyProtocolWrapper): | ||
type_flags = (<_PyProtocolWrapper> o)._type_flags | ||
o = (<_PyProtocolWrapper> o)._obj | ||
pushed_values_count = py_to_lua_custom(runtime, L, o, type_flags) | ||
elif recursive and isinstance(o, Sequence): | ||
synodriver marked this conversation as resolved.
Show resolved
Hide resolved
|
||
lua.lua_createtable(L, _len_as_int(len(o)), 0) # create a table at the top of stack, with narr already known | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here's probably an error to handle if the allocation fails. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is that used to prevent from over nesting? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I think this applies to a couple of other conversions as well. Lua has an error handler that uses longjmp, that probably applies here, too. That is quite annoying, because Lupa does not handle this currently (and it's really not trivial to handle, especially in Cython). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry for the late reply. I think detect reference cycles in the data structures is not easy, and probably we should just limit the maximum depth of recursion |
||
for i, v in enumerate(o): | ||
py_to_lua(runtime, L, v, wrap_none, recursive) | ||
lua.lua_rawseti(L, -2, i+1) | ||
synodriver marked this conversation as resolved.
Show resolved
Hide resolved
|
||
pushed_values_count = 1 | ||
elif recursive and isinstance(o, Mapping): | ||
synodriver marked this conversation as resolved.
Show resolved
Hide resolved
|
||
lua.lua_createtable(L, 0, _len_as_int(len(o))) # create a table at the top of stack, with nrec already known | ||
for key, value in o.items(): | ||
py_to_lua(runtime, L, key, wrap_none, recursive) | ||
py_to_lua(runtime, L, value, wrap_none, recursive) | ||
lua.lua_rawset(L, -3) | ||
pushed_values_count = 1 | ||
else: | ||
if isinstance(o, _PyProtocolWrapper): | ||
type_flags = (<_PyProtocolWrapper>o)._type_flags | ||
o = (<_PyProtocolWrapper>o)._obj | ||
else: | ||
# prefer __getitem__ over __getattr__ by default | ||
type_flags = OBJ_AS_INDEX if hasattr(o, '__getitem__') else 0 | ||
# prefer __getitem__ over __getattr__ by default | ||
type_flags = OBJ_AS_INDEX if hasattr(o, '__getitem__') else 0 | ||
pushed_values_count = py_to_lua_custom(runtime, L, o, type_flags) | ||
return pushed_values_count | ||
|
||
|
@@ -1776,7 +1794,7 @@ cdef int py_object_gc_with_gil(py_object *py_obj, lua_State* L) with gil: | |
return 0 | ||
finally: | ||
py_obj.obj = NULL | ||
|
||
cdef int py_object_gc(lua_State* L) nogil: | ||
if not lua.lua_isuserdata(L, 1): | ||
return 0 | ||
|
@@ -1802,7 +1820,7 @@ cdef bint call_python(LuaRuntime runtime, lua_State *L, py_object* py_obj) excep | |
else: | ||
args = () | ||
kwargs = {} | ||
|
||
for i in range(nargs): | ||
arg = py_from_lua(runtime, L, i+2) | ||
if isinstance(arg, _PyArguments): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -601,6 +601,74 @@ def test_table_from_table_iter_indirect(self): | |
self.assertEqual(list(table2.keys()), [1, 2, 3]) | ||
self.assertEqual(set(table2.values()), set([1, 2, "foo"])) | ||
|
||
def test_table_from_nested_dict(self): | ||
data = {"a": {"a": "foo"}, "b": {"b": "bar"}} | ||
table = self.lua.table_from(data, recursive=True) | ||
self.assertEqual(table["a"]["a"], "foo") | ||
self.assertEqual(table["b"]["b"], "bar") | ||
self.lua.globals()["data"] = table | ||
self.lua.eval("assert(data.a.a=='foo', 'failed')") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a test where we make sure that this raises an exception if the condition fails, so that the test actually fails? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I would add it later There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think you've added such a test. self.assertEqual(self.lua.eval("data.a.a"), 'foo') Feel free to add a test helper method to make this shorter: def assertLuaResult(lua_expression, result):
self.assertEqual(self.lua.eval(lua_expression), result) |
||
self.lua.eval("assert(data.b.b=='bar', 'failed')") | ||
self.lua.eval("assert(type(data.a)=='table', 'failed, expect table, got '..type(data.a))") | ||
self.lua.eval("assert(type(data.b)=='table', 'failed, expect table, got '..type(data.b))") | ||
self.lua.execute("""function itertable(table) | ||
for k,v in pairs(table) do | ||
print(k) | ||
if type(v) == "table" then | ||
itertable(v) | ||
else | ||
print(v) | ||
end | ||
end | ||
end | ||
print('\\n') | ||
itertable(data) | ||
""") | ||
scoder marked this conversation as resolved.
Show resolved
Hide resolved
|
||
del self.lua.globals()["data"] | ||
scoder marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def test_table_from_nested_list(self): | ||
data = {"a": {"a": "foo"}, "b": [1, 2, 3]} | ||
table = self.lua.table_from(data, recursive=True) | ||
self.assertEqual(table["a"]["a"], "foo") | ||
self.assertEqual(table["b"][1], 1) | ||
self.assertEqual(table["b"][2], 2) | ||
self.assertEqual(table["b"][3], 3) | ||
self.lua.globals()["data"] = table | ||
self.lua.eval("assert(data.a.a=='foo', 'failed')") | ||
self.lua.eval("assert(#data.b==3, 'failed')") | ||
self.lua.eval("assert(type(data.a)=='table', 'failed, expect table, got '..type(data.a))") | ||
self.lua.eval("assert(type(data.b)=='table', 'failed, expect table, got '..type(data.b))") | ||
self.lua.execute("""function itertable(table) | ||
for k,v in pairs(table) do | ||
print(k) | ||
if type(v) == "table" then | ||
itertable(v) | ||
else | ||
print(v) | ||
end | ||
end | ||
end | ||
print('\\n') | ||
itertable(data) | ||
""") | ||
del self.lua.globals()["data"] | ||
|
||
def test_table_from_nested_list_bad(self): | ||
data = {"a": {"a": "foo"}, "b": [1, 2, 3]} | ||
table = self.lua.table_from(data, recursive=False) # in this case, lua will get userdata instead of table | ||
self.assertEqual(table["a"]["a"], "foo") | ||
self.assertEqual(table["b"][0], 1) | ||
self.assertEqual(table["b"][1], 2) | ||
self.assertEqual(table["b"][2], 3) | ||
self.lua.globals()["data"] = table | ||
|
||
def test(): | ||
self.lua.eval("assert(type(data.a)=='table', 'failed, expect table, got '..type(data.a))") | ||
self.lua.eval("assert(type(data.b)=='table', 'failed, expect table, got '..type(data.b))") | ||
|
||
self.assertRaises(lupa.LuaError, test) | ||
del self.lua.globals()["data"] | ||
|
||
# FIXME: it segfaults | ||
# def test_table_from_generator_calling_lua_functions(self): | ||
# func = self.lua.eval("function (obj) return obj end") | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could probably still get a C compiler warning here if
sizeof(long) == sizeof(Py_ssize_t)
, because it would detect that the condition is always false and, thus, useless.BTW, why do you compare to
LONG_MAX
and notINT_MAX
if you want to return anint
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a better way to deal with it or we should direct cast it?