Giter VIP home page Giter VIP logo

lupa's Introduction

Lupa

logo/logo-220x200.png

Lupa integrates the runtimes of Lua or LuaJIT2 into CPython. It is a partial rewrite of LunaticPython in Cython with some additional features such as proper coroutine support.

For questions not answered here, please contact the Lupa mailing list.

  • separate Lua runtime states through a LuaRuntime class
  • Python coroutine wrapper for Lua coroutines
  • iteration support for Python objects in Lua and Lua objects in Python
  • proper encoding and decoding of strings (configurable per runtime, UTF-8 by default)
  • frees the GIL and supports threading in separate runtimes when calling into Lua
  • tested with Python 2.7/3.6 and later
  • ships with Lua 5.1, 5.2, 5.3 and 5.4 as well as LuaJIT 2.0 and 2.1 on systems that support it.
  • easy to hack on and extend as it is written in Cython, not C

In Latin, "lupa" is a female wolf, as elegant and wild as it sounds. If you don't like this kind of straight forward allegory to an endangered species, you may also happily assume it's just an amalgamation of the phonetic sounds that start the words "Lua" and "Python", two from each to keep the balance.

It complements Python very well. Lua is a language as dynamic as Python, but LuaJIT compiles it to very fast machine code, sometimes faster than many statically compiled languages for computational code. The language runtime is very small and carefully designed for embedding. The complete binary module of Lupa, including a statically linked LuaJIT2 runtime, only weighs some 800KB on a 64 bit machine. With standard Lua 5.2, it's less than 600KB.

However, the Lua ecosystem lacks many of the batteries that Python readily includes, either directly in its standard library or as third party packages. This makes real-world Lua applications harder to write than equivalent Python applications. Lua is therefore not commonly used as primary language for large applications, but it makes for a fast, high-level and resource-friendly backup language inside of Python when raw speed is required and the edit-compile-run cycle of binary extension modules is too heavy and too static for agile development or hot-deployment.

Lupa is a very fast and thin wrapper around Lua or LuaJIT. It makes it easy to write dynamic Lua code that accompanies dynamic Python code by switching between the two languages at runtime, based on the tradeoff between simplicity and speed.

The binary wheels include different Lua versions as well as LuaJIT, if supported. By default, import lupa uses the latest Lua version, but you can choose a specific one via import:

try:
    import lupa.luajit21 as lupa
except ImportError:
    try:
        import lupa.lua54 as lupa
    except ImportError:
        try:
            import lupa.lua53 as lupa
        except ImportError:
            import lupa

print(f"Using {lupa.LuaRuntime().lua_implementation} (compiled with {lupa.LUA_VERSION})")
>>> from lupa.lua54 import LuaRuntime
>>> lua = LuaRuntime(unpack_returned_tuples=True)

>>> lua.eval('1+1')
2

>>> lua_func = lua.eval('function(f, n) return f(n) end')

>>> def py_add1(n): return n+1
>>> lua_func(py_add1, 2)
3

>>> lua.eval('python.eval(" 2 ** 2 ")') == 4
True
>>> lua.eval('python.builtins.str(4)') == '4'
True

The function lua_type(obj) can be used to find out the type of a wrapped Lua object in Python code, as provided by Lua's type() function:

>>> lupa.lua_type(lua_func)
'function'
>>> lupa.lua_type(lua.eval('{}'))
'table'

To help in distinguishing between wrapped Lua objects and normal Python objects, it returns None for the latter:

>>> lupa.lua_type(123) is None
True
>>> lupa.lua_type('abc') is None
True
>>> lupa.lua_type({}) is None
True

Note the flag unpack_returned_tuples=True that is passed to create the Lua runtime. It is new in Lupa 0.21 and changes the behaviour of tuples that get returned by Python functions. With this flag, they explode into separate Lua values:

>>> lua.execute('a,b,c = python.eval("(1,2)")')
>>> g = lua.globals()
>>> g.a
1
>>> g.b
2
>>> g.c is None
True

When set to False, functions that return a tuple pass it through to the Lua code:

>>> non_explode_lua = lupa.LuaRuntime(unpack_returned_tuples=False)
>>> non_explode_lua.execute('a,b,c = python.eval("(1,2)")')
>>> g = non_explode_lua.globals()
>>> g.a
(1, 2)
>>> g.b is None
True
>>> g.c is None
True

Since the default behaviour (to not explode tuples) might change in a later version of Lupa, it is best to always pass this flag explicitly.

Python objects are either converted when passed into Lua (e.g. numbers and strings) or passed as wrapped object references.

>>> wrapped_type = lua.globals().type     # Lua's own type() function
>>> wrapped_type(1) == 'number'
True
>>> wrapped_type('abc') == 'string'
True

Wrapped Lua objects get unwrapped when they are passed back into Lua, and arbitrary Python objects get wrapped in different ways:

>>> wrapped_type(wrapped_type) == 'function'  # unwrapped Lua function
True
>>> wrapped_type(len) == 'userdata'       # wrapped Python function
True
>>> wrapped_type([]) == 'userdata'        # wrapped Python object
True

Lua supports two main protocols on objects: calling and indexing. It does not distinguish between attribute access and item access like Python does, so the Lua operations obj[x] and obj.x both map to indexing. To decide which Python protocol to use for Lua wrapped objects, Lupa employs a simple heuristic.

Pratically all Python objects allow attribute access, so if the object also has a __getitem__ method, it is preferred when turning it into an indexable Lua object. Otherwise, it becomes a simple object that uses attribute access for indexing from inside Lua.

Obviously, this heuristic will fail to provide the required behaviour in many cases, e.g. when attribute access is required to an object that happens to support item access. To be explicit about the protocol that should be used, Lupa provides the helper functions as_attrgetter() and as_itemgetter() that restrict the view on an object to a certain protocol, both from Python and from inside Lua:

>>> lua_func = lua.eval('function(obj) return obj["get"] end')
>>> d = {'get' : 'value'}

>>> value = lua_func(d)
>>> value == d['get'] == 'value'
True

>>> value = lua_func( lupa.as_itemgetter(d) )
>>> value == d['get'] == 'value'
True

>>> dict_get = lua_func( lupa.as_attrgetter(d) )
>>> dict_get == d.get
True
>>> dict_get('get') == d.get('get') == 'value'
True

>>> lua_func = lua.eval(
...     'function(obj) return python.as_attrgetter(obj)["get"] end')
>>> dict_get = lua_func(d)
>>> dict_get('get') == d.get('get') == 'value'
True

Note that unlike Lua function objects, callable Python objects support indexing in Lua:

>>> def py_func(): pass
>>> py_func.ATTR = 2

>>> lua_func = lua.eval('function(obj) return obj.ATTR end')
>>> lua_func(py_func)
2
>>> lua_func = lua.eval(
...     'function(obj) return python.as_attrgetter(obj).ATTR end')
>>> lua_func(py_func)
2
>>> lua_func = lua.eval(
...     'function(obj) return python.as_attrgetter(obj)["ATTR"] end')
>>> lua_func(py_func)
2

Iteration over Python objects from Lua's for-loop is fully supported. However, Python iterables need to be converted using one of the utility functions which are described here. This is similar to the functions like pairs() in Lua.

To iterate over a plain Python iterable, use the python.iter() function. For example, you can manually copy a Python list into a Lua table like this:

>>> lua_copy = lua.eval('''
...     function(L)
...         local t, i = {}, 1
...         for item in python.iter(L) do
...             t[i] = item
...             i = i + 1
...         end
...         return t
...     end
... ''')

>>> table = lua_copy([1,2,3,4])
>>> len(table)
4
>>> table[1]   # Lua indexing
1

Python's enumerate() function is also supported, so the above could be simplified to:

>>> lua_copy = lua.eval('''
...     function(L)
...         local t = {}
...         for index, item in python.enumerate(L) do
...             t[ index+1 ] = item
...         end
...         return t
...     end
... ''')

>>> table = lua_copy([1,2,3,4])
>>> len(table)
4
>>> table[1]   # Lua indexing
1

For iterators that return tuples, such as dict.iteritems(), it is convenient to use the special python.iterex() function that automatically explodes the tuple items into separate Lua arguments:

>>> lua_copy = lua.eval('''
...     function(d)
...         local t = {}
...         for key, value in python.iterex(d.items()) do
...             t[key] = value
...         end
...         return t
...     end
... ''')

>>> d = dict(a=1, b=2, c=3)
>>> table = lua_copy( lupa.as_attrgetter(d) )
>>> table['b']
2

Note that accessing the d.items method from Lua requires passing the dict as attrgetter. Otherwise, attribute access in Lua would use the getitem protocol of Python dicts and look up d['items'] instead.

While None in Python and nil in Lua differ in their semantics, they usually just mean the same thing: no value. Lupa therefore tries to map one directly to the other whenever possible:

>>> lua.eval('nil') is None
True
>>> is_nil = lua.eval('function(x) return x == nil end')
>>> is_nil(None)
True

The only place where this cannot work is during iteration, because Lua considers a nil value the termination marker of iterators. Therefore, Lupa special cases None values here and replaces them by a constant python.none instead of returning nil:

>>> _ = lua.require("table")
>>> func = lua.eval('''
...     function(items)
...         local t = {}
...         for value in python.iter(items) do
...             table.insert(t, value == python.none)
...         end
...         return t
...     end
... ''')

>>> items = [1, None ,2]
>>> list(func(items).values())
[False, True, False]

Lupa avoids this value escaping whenever it's obviously not necessary. Thus, when unpacking tuples during iteration, only the first value will be subject to python.none replacement, as Lua does not look at the other items for loop termination anymore. And on enumerate() iteration, the first value is known to be always a number and never None, so no replacement is needed.

>>> func = lua.eval('''
...     function(items)
...         for a, b, c, d in python.iterex(items) do
...             return {a == python.none, a == nil,   -->  a == python.none
...                     b == python.none, b == nil,   -->  b == nil
...                     c == python.none, c == nil,   -->  c == nil
...                     d == python.none, d == nil}   -->  d == nil ...
...         end
...     end
... ''')

>>> items = [(None, None, None, None)]
>>> list(func(items).values())
[True, False, False, True, False, True, False, True]

>>> items = [(None, None)]   # note: no values for c/d => nil in Lua
>>> list(func(items).values())
[True, False, False, True, False, True, False, True]

Note that this behaviour changed in Lupa 1.0. Previously, the python.none replacement was done in more places, which made it not always very predictable.

Lua tables mimic Python's mapping protocol. For the special case of array tables, Lua automatically inserts integer indices as keys into the table. Therefore, indexing starts from 1 as in Lua instead of 0 as in Python. For the same reason, negative indexing does not work. It is best to think of Lua tables as mappings rather than arrays, even for plain array tables.

>>> table = lua.eval('{10,20,30,40}')
>>> table[1]
10
>>> table[4]
40
>>> list(table)
[1, 2, 3, 4]
>>> dict(table)
{1: 10, 2: 20, 3: 30, 4: 40}
>>> list(table.values())
[10, 20, 30, 40]
>>> len(table)
4

>>> mapping = lua.eval('{ [1] = -1 }')
>>> list(mapping)
[1]

>>> mapping = lua.eval('{ [20] = -20; [3] = -3 }')
>>> mapping[20]
-20
>>> mapping[3]
-3
>>> sorted(mapping.values())
[-20, -3]
>>> sorted(mapping.items())
[(3, -3), (20, -20)]

>>> mapping[-3] = 3     # -3 used as key, not index!
>>> mapping[-3]
3
>>> sorted(mapping)
[-3, 3, 20]
>>> sorted(mapping.items())
[(-3, 3), (3, -3), (20, -20)]

To simplify the table creation from Python, the LuaRuntime comes with a helper method that creates a Lua table from Python arguments:

>>> t = lua.table(10, 20, 30, 40)
>>> lupa.lua_type(t)
'table'
>>> list(t)
[1, 2, 3, 4]
>>> list(t.values())
[10, 20, 30, 40]

>>> t = lua.table(10, 20, 30, 40, a=1, b=2)
>>> t[3]
30
>>> t['b']
2

A second helper method, .table_from(), was added in Lupa 1.1 and accepts any number of mappings and sequences/iterables as arguments. It collects all values and key-value pairs and builds a single Lua table from them. Any keys that appear in multiple mappings get overwritten with their last value (going from left to right).

>>> t = lua.table_from([10, 20, 30], {'a': 11, 'b': 22}, (40, 50), {'b': 42})
>>> t['a']
11
>>> t['b']
42
>>> t[5]
50
>>> sorted(t.values())
[10, 11, 20, 30, 40, 42, 50]

Since Lupa 2.1, passing recursive=True will map data structures recursively to Lua tables.

>>> t = lua.table_from(
...     [
...         # t1:
...         [
...             [10, 20, 30],
...             {'a': 11, 'b': 22}
...         ],
...         # t2:
...         [
...             (40, 50),
...             {'b': 42}
...         ]
...     ],
...     recursive=True
... )
>>> t1, t2 = t.values()
>>> list(t1[1].values())
[10, 20, 30]
>>> t1[2]['a']
11
>>> t1[2]['b']
22
>>> t2[2]['b']
42
>>> list(t1[1].values())
[10, 20, 30]
>>> list(t2[1].values())
[40, 50]

A lookup of non-existing keys or indices returns None (actually nil inside of Lua). A lookup is therefore more similar to the .get() method of Python dicts than to a mapping lookup in Python.

>>> table = lua.table(10, 20, 30, 40)
>>> table[1000000] is None
True
>>> table['no such key'] is None
True

>>> mapping = lua.eval('{ [20] = -20; [3] = -3 }')
>>> mapping['no such key'] is None
True

Note that len() does the right thing for array tables but does not work on mappings:

>>> len(table)
4
>>> len(mapping)
0

This is because len() is based on the # (length) operator in Lua and because of the way Lua defines the length of a table. Remember that unset table indices always return nil, including indices outside of the table size. Thus, Lua basically looks for an index that returns nil and returns the index before that. This works well for array tables that do not contain nil values, gives barely predictable results for tables with 'holes' and does not work at all for mapping tables. For tables with both sequential and mapping content, this ignores the mapping part completely.

Note that it is best not to rely on the behaviour of len() for mappings. It might change in a later version of Lupa.

Similar to the table interface provided by Lua, Lupa also supports attribute access to table members:

>>> table = lua.eval('{ a=1, b=2 }')
>>> table.a, table.b
(1, 2)
>>> table.a == table['a']
True

This enables access to Lua 'methods' that are associated with a table, as used by the standard library modules:

>>> string = lua.eval('string')    # get the 'string' library table
>>> print( string.lower('A') )
a

As discussed earlier, Lupa allows Lua scripts to call Python functions and methods:

>>> def add_one(num):
...     return num + 1
>>> lua_func = lua.eval('function(num, py_func) return py_func(num) end')
>>> lua_func(48, add_one)
49

>>> class MyClass():
...     def my_method(self):
...         return 345
>>> obj = MyClass()
>>> lua_func = lua.eval('function(py_obj) return py_obj:my_method() end')
>>> lua_func(obj)
345

Lua doesn't have a dedicated syntax for named arguments, so by default Python callables can only be called using positional arguments.

A common pattern for implementing named arguments in Lua is passing them in a table as the first and only function argument. See http://lua-users.org/wiki/NamedParameters for more details. Lupa supports this pattern by providing two decorators: lupa.unpacks_lua_table for Python functions and lupa.unpacks_lua_table_method for methods of Python objects.

Python functions/methods wrapped in these decorators can be called from Lua code as func(foo, bar), func{foo=foo, bar=bar} or func{foo, bar=bar}. Example:

>>> @lupa.unpacks_lua_table
... def add(a, b):
...     return a + b
>>> lua_func = lua.eval('function(a, b, py_func) return py_func{a=a, b=b} end')
>>> lua_func(5, 6, add)
11
>>> lua_func = lua.eval('function(a, b, py_func) return py_func{a, b=b} end')
>>> lua_func(5, 6, add)
11

If you do not control the function implementation, you can also just manually wrap a callable object when passing it into Lupa:

>>> import operator
>>> wrapped_py_add = lupa.unpacks_lua_table(operator.add)

>>> lua_func = lua.eval('function(a, b, py_func) return py_func{a, b} end')
>>> lua_func(5, 6, wrapped_py_add)
11

There are some limitations:

  1. Avoid using lupa.unpacks_lua_table and lupa.unpacks_lua_table_method for functions where the first argument can be a Lua table. In this case py_func{foo=bar} (which is the same as py_func({foo=bar}) in Lua) becomes ambiguous: it could mean either "call py_func with a named foo argument" or "call py_func with a positional {foo=bar} argument".

  2. One should be careful with passing nil values to callables wrapped in lupa.unpacks_lua_table or lupa.unpacks_lua_table_method decorators. Depending on the context, passing nil as a parameter can mean either "omit a parameter" or "pass None". This even depends on the Lua version.

    It is possible to use python.none instead of nil to pass None values robustly. Arguments with nil values are also fine when standard braces func(a, b, c) syntax is used.

Because of these limitations lupa doesn't enable named arguments for all Python callables automatically. Decorators allow to enable named arguments on a per-callable basis.

The next is an example of Lua coroutines. A wrapped Lua coroutine behaves exactly like a Python coroutine. It needs to get created at the beginning, either by using the .coroutine() method of a function or by creating it in Lua code. Then, values can be sent into it using the .send() method or it can be iterated over. Note that the .throw() method is not supported, though.

>>> lua_code = '''\
...     function(N)
...         for i=0,N do
...             coroutine.yield( i%2 )
...         end
...     end
... '''
>>> lua = LuaRuntime()
>>> f = lua.eval(lua_code)

>>> gen = f.coroutine(4)
>>> list(enumerate(gen))
[(0, 0), (1, 1), (2, 0), (3, 1), (4, 0)]

An example where values are passed into the coroutine using its .send() method:

>>> lua_code = '''\
...     function()
...         local t,i = {},0
...         local value = coroutine.yield()
...         while value do
...             t[i] = value
...             i = i + 1
...             value = coroutine.yield()
...         end
...         return t
...     end
... '''
>>> f = lua.eval(lua_code)

>>> co = f.coroutine()   # create coroutine
>>> co.send(None)        # start coroutine (stops at first yield)

>>> for i in range(3):
...     co.send(i*2)

>>> mapping = co.send(None)   # loop termination signal
>>> sorted(mapping.items())
[(0, 0), (1, 2), (2, 4)]

It also works to create coroutines in Lua and to pass them back into Python space:

>>> lua_code = '''\
...   function f(N)
...         for i=0,N do
...             coroutine.yield( i%2 )
...         end
...   end ;
...   co1 = coroutine.create(f) ;
...   co2 = coroutine.create(f) ;
...
...   status, first_result = coroutine.resume(co2, 2) ;   -- starting!
...
...   return f, co1, co2, status, first_result
... '''

>>> lua = LuaRuntime()
>>> f, co, lua_gen, status, first_result = lua.execute(lua_code)

>>> # a running coroutine:

>>> status
True
>>> first_result
0
>>> list(lua_gen)
[1, 0]
>>> list(lua_gen)
[]

>>> # an uninitialised coroutine:

>>> gen = co(4)
>>> list(enumerate(gen))
[(0, 0), (1, 1), (2, 0), (3, 1), (4, 0)]

>>> gen = co(2)
>>> list(enumerate(gen))
[(0, 0), (1, 1), (2, 0)]

>>> # a plain function:

>>> gen = f.coroutine(4)
>>> list(enumerate(gen))
[(0, 0), (1, 1), (2, 0), (3, 1), (4, 0)]

The following example calculates a mandelbrot image in parallel threads and displays the result in PIL. It is based on a benchmark implementation for the Computer Language Benchmarks Game.

lua_code = '''\
    function(N, i, total)
        local char, unpack = string.char, table.unpack
        local result = ""
        local M, ba, bb, buf = 2/N, 2^(N%8+1)-1, 2^(8-N%8), {}
        local start_line, end_line = N/total * (i-1), N/total * i - 1
        for y=start_line,end_line do
            local Ci, b, p = y*M-1, 1, 0
            for x=0,N-1 do
                local Cr = x*M-1.5
                local Zr, Zi, Zrq, Ziq = Cr, Ci, Cr*Cr, Ci*Ci
                b = b + b
                for i=1,49 do
                    Zi = Zr*Zi*2 + Ci
                    Zr = Zrq-Ziq + Cr
                    Ziq = Zi*Zi
                    Zrq = Zr*Zr
                    if Zrq+Ziq > 4.0 then b = b + 1; break; end
                end
                if b >= 256 then p = p + 1; buf[p] = 511 - b; b = 1; end
            end
            if b ~= 1 then p = p + 1; buf[p] = (ba-b)*bb; end
            result = result .. char(unpack(buf, 1, p))
        end
        return result
    end
'''

image_size = 1280   # == 1280 x 1280
thread_count = 8

from lupa import LuaRuntime
lua_funcs = [ LuaRuntime(encoding=None).eval(lua_code)
              for _ in range(thread_count) ]

results = [None] * thread_count
def mandelbrot(i, lua_func):
    results[i] = lua_func(image_size, i+1, thread_count)

import threading
threads = [ threading.Thread(target=mandelbrot, args=(i,lua_func))
            for i, lua_func in enumerate(lua_funcs) ]
for thread in threads:
    thread.start()
for thread in threads:
    thread.join()

result_buffer = b''.join(results)

# use Pillow to display the image
from PIL import Image
image = Image.frombytes('1', (image_size, image_size), result_buffer)
image.show()

Note how the example creates a separate LuaRuntime for each thread to enable parallel execution. Each LuaRuntime is protected by a global lock that prevents concurrent access to it. The low memory footprint of Lua makes it reasonable to use multiple runtimes, but this setup also means that values cannot easily be exchanged between threads inside of Lua. They must either get copied through Python space (passing table references will not work, either) or use some Lua mechanism for explicit communication, such as a pipe or some kind of shared memory setup.

Lupa provides a simple mechanism to control access to Python objects. Each attribute access can be passed through a filter function as follows:

>>> def filter_attribute_access(obj, attr_name, is_setting):
...     if isinstance(attr_name, unicode):
...         if not attr_name.startswith('_'):
...             return attr_name
...     raise AttributeError('access denied')

>>> lua = lupa.LuaRuntime(
...           register_eval=False,
...           attribute_filter=filter_attribute_access)
>>> func = lua.eval('function(x) return x.__class__ end')
>>> func(lua)
Traceback (most recent call last):
 ...
AttributeError: access denied

The is_setting flag indicates whether the attribute is being read or set.

Note that the attributes of Python functions provide access to the current globals() and therefore to the builtins etc. If you want to safely restrict access to a known set of Python objects, it is best to work with a whitelist of safe attribute names. One way to do that could be to use a well selected list of dedicated API objects that you provide to Lua code, and to only allow Python attribute access to the set of public attribute/method names of these objects.

Since Lupa 1.0, you can alternatively provide dedicated getter and setter function implementations for a LuaRuntime:

>>> def getter(obj, attr_name):
...     if attr_name == 'yes':
...         return getattr(obj, attr_name)
...     raise AttributeError(
...         'not allowed to read attribute "%s"' % attr_name)

>>> def setter(obj, attr_name, value):
...     if attr_name == 'put':
...         setattr(obj, attr_name, value)
...         return
...     raise AttributeError(
...         'not allowed to write attribute "%s"' % attr_name)

>>> class X(object):
...     yes = 123
...     put = 'abc'
...     noway = 2.1

>>> x = X()

>>> lua = lupa.LuaRuntime(attribute_handlers=(getter, setter))
>>> func = lua.eval('function(x) return x.yes end')
>>> func(x)  # getting 'yes'
123
>>> func = lua.eval('function(x) x.put = "ABC"; end')
>>> func(x)  # setting 'put'
>>> print(x.put)
ABC
>>> func = lua.eval('function(x) x.noway = 42; end')
>>> func(x)  # setting 'noway'
Traceback (most recent call last):
 ...
AttributeError: not allowed to write attribute "noway"

Lupa provides a simple mechanism to control the maximum memory usage of the Lua Runtime since version 2.0. By default Lupa does not interfere with Lua's memory allocation, to opt-in you must set the max_memory when creating the LuaRuntime.

The LuaRuntime provides three methods for controlling and reading the memory usage:

  1. get_memory_used(total=False) to get the current memory usage of the LuaRuntime.
  2. get_max_memory(total=False) to get the current memory limit. 0 means there is no memory limitation.
  3. set_max_memory(max_memory, total=False) to change the memory limit. Values below or equal to 0 mean no limit.

There is always some memory used by the LuaRuntime itself (around ~20KiB, depending on your lua version and other factors) which is excluded from all calculations unless you specify total=True.

>>> from lupa import lua52
>>> lua = lua52.LuaRuntime(max_memory=0)  # 0 for unlimited, default is None
>>> lua.get_memory_used()  # memory used by your code
0
>>> total_lua_memory = lua.get_memory_used(total=True)  # includes memory used by the runtime itself
>>> assert total_lua_memory > 0  # exact amount depends on your lua version and other factors

Lua code hitting the memory limit will receive memory errors:

>>> lua.set_max_memory(100)
>>> lua.eval("string.rep('a', 1000)")   # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
 ...
lupa.LuaMemoryError: not enough memory

LuaMemoryError inherits from LuaError and MemoryError.

This will usually work as is, but here are the details, in case anything goes wrong for you.

To use binary modules in Lua, you need to compile them against the header files of the LuaJIT sources that you used to build Lupa, but do not link them against the LuaJIT library.

Furthermore, CPython needs to enable global symbol visibility for shared libraries before loading the Lupa module. This can be done by calling sys.setdlopenflags(flag_values). Importing the lupa module will automatically try to set up the correct dlopen flags if it can find the platform specific DLFCN Python module that defines the necessary flag constants. In that case, using binary modules in Lua should work out of the box.

If this setup fails, however, you have to set the flags manually. When using the above configuration call, the argument flag_values must represent the sum of your system's values for RTLD_NEW and RTLD_GLOBAL. If RTLD_NEW is 2 and RTLD_GLOBAL is 256, you need to call sys.setdlopenflags(258).

Assuming that the Lua luaposix (posix) module is available, the following should work on a Linux system:

>>> import sys
>>> orig_dlflags = sys.getdlopenflags()
>>> sys.setdlopenflags(258)
>>> import lupa
>>> sys.setdlopenflags(orig_dlflags)

>>> lua = lupa.LuaRuntime()
>>> posix_module = lua.require('posix')     # doctest: +SKIP

The build is configured to automatically search for an installed version of first LuaJIT and then Lua, and failing to find either, to use the bundled LuaJIT or Lua version.

If you wish to build Lupa with a specific version of Lua, you can configure the following options on setup:

Option Description
--lua-lib <libfile> Lua library file path, e.g. --lua-lib /usr/local/lib/lualib.a
--lua-includes <incdir> Lua include directory, e.g. --lua-includes /usr/local/include
--use-bundle Use bundled LuaJIT or Lua instead of searching for an installed version.
--no-bundle Don't use the bundled LuaJIT/Lua, search for an installed version of LuaJIT or Lua, e.g. using pkg-config.
--no-lua-jit Don't use or search for LuaJIT, only use or search Lua instead.

lupa's People

Contributors

aaiyer avatar amotl avatar chekunkov avatar fried avatar gdude2002 avatar guidanoli avatar jayvdb avatar jbinary avatar jmarianer avatar kmike avatar le0developer avatar maddiem4 avatar moreati avatar oddstr13 avatar rakiru avatar riconnon avatar ruihe774 avatar russelldavis avatar scoder avatar siberianfox avatar synodriver avatar wheybags avatar xxyzz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lupa's Issues

Bug with hexchat.

So I was trying to use this with hexchat and this happened:

 >>> import lupa; lua = lupa.LuaRuntime(unpack_returned_tuples=False, encoding=None)
 >>> exec("def x(*args):\n hexchat.prnt(*([x.decode(\"utf-8\") for x in args]))\n\nlua.globals().print = x",globals(),locals())
 >>> lua.eval("print('\\xC2\\xA7')")
 ยง
 >>> lua.eval("print('\\xC2\\xA7')")
 Traceback (most recent call last):
   File "<string>", line 1, in <module>
   File "lupa/_lupa.pyx", line 203, in lupa._lupa.LuaRuntime.eval (lupa/_lupa.c:3789)
   File "lupa/_lupa.pyx", line 1060, in lupa._lupa.run_lua (lupa/_lupa.c:14933)
   File "lupa/_lupa.pyx", line 1079, in lupa._lupa.execute_lua_call (lupa/_lupa.c:15193)
   File "lupa/_lupa.pyx", line 189, in lupa._lupa.LuaRuntime.reraise_on_exception (lupa/_lupa.c:3579)
   File "lupa/_lupa.pyx", line 1214, in lupa._lupa.py_call_with_gil (lupa/_lupa.c:16528)
   File "lupa/_lupa.pyx", line 1206, in lupa._lupa.call_python (lupa/_lupa.c:16408)
   File "<string>", line 2, in x
   File "<string>", line 2, in <listcomp>
 RuntimeError: lost sys.__plugin__
 >>> lua.eval("print('\\xC2\\xA7')")
 ยง

Wat?

Experimental branch that fetches and builds luajit

The biggest struggle with actually using Lupa in any kind of production environment, in my experience, has been platform-specific breakage due to the variable nature of the install process. We do probably always want flexibility in the mainline branch, but for the sake of stability, predictability and testing validity, I'm gonna make a less-flexible branch that handles its own acquisition of luajit.

lua.table keyword arguments are misleading when runtime encoding is None

When runtime encoding is None, lua.table(foo=132) creates a Lua table with userdata as a key. There is a workaround (use lua.table_from which supports dicts with binary keys). But I think it makes more sense to use Lua strings for keys for tables created by lua.table, even if the encoding is None. I expect this to work even if encoding is None:

from lupa import LuaRuntime
lua = LuaRuntime(encoding=None)
tbl = lua.table(x=1)

# this should print 1, but it prints 'nil'
lua.eval("function(tbl) print(type(tbl.x)) end")(tbl) 

# this prints
# userdata  number
lua.eval("function(tbl) for k,v in pairs(tbl) do print(type(k),type(v)) end end")(tbl)

provide a way to do insinstance() checks

Hey,

Is there a good way to check if a result of some Lua computation is _LuaTable / _LuaFunction / _LuaObject / ...?

Currently I'm writing code like this:

import lupa
lua = lupa.LuaRuntime()
_LuaTable = lua.eval("{}").__class__ 

# ...
obj = lua.globals()["myobj"]
if insinstance(obj, _LuaTable):
    # ...

What do you think about removing cython.internal declarations around these classes?

Interesting bit in readme

you may also happily assume it's just an amalgamation of the phonetic sounds that start the words "Lua" and "Python", two from each to keep the balance

What's the 2nd letter coming from "Python"? L, u and a from Lupa are all only present in the word Lua. Anyhow, nice name and project!

Cannot install, getting ImportError

Python 2.6.6 (r266:84292, Dec 27 2010, 00:02:40) 
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import lupa
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.6/dist-packages/lupa/__init__.py", line 31, in <module>
    from lupa._lupa import *
ImportError: libluajit-5.1.so.2: cannot open shared object file: No such file or directory

Lupa built and installed with no errors, so I really don't get what the deal is. It may have something to do with me screwing up during the first install and installing LuaJIT separately from Lupa, but I rebuilt LuaJIT in a subfolder and rebuilt/reinstalled Lupa after that, so I'm not sure.

pthread_cond_wait: Undefined error: 0

Hi,

Programs that use lupa sometimes start printing pthread_cond_wait: Undefined error: 0 messages and go to infinite cycle. I don't know how to reduce it to a small example; it happened with a console script and with IPython notebook.

It could be related to exceptions because a console script is a bunch of unit tests which went crazy when I started calling Python functions from Lua code with invalid number of arguments. As I recall IPython notebook went crazy after calling a coroutine incorrectly, but I'm not 100% sure.

Tests create (and destroy?) lots of LuaRuntime instances. After making them use a single LuaRuntime instance errors gone. Another created but unused LuaRuntime instance was present when tests were executed, if it matters.

I'm using Python 2.7.5, Lua 5.2 and lupa 1.0.1 on OS X 10.9.5.

Implement Lua operator overloading for wrapped Python objects

Lua objects support operator overloading in their metatable:

http://www.lua.org/manual/5.3/manual.html#2.4

Mapping the Lua arithmetic etc. operators for wrapped Python objects to the equivalent Python operators should provide a much better user experience.

This also needs some kind of security support. Either through the normal attribute access intercept (which would have to check for special methods anyway), or through a separate set of explicitly allowed operators. Maybe both: explicitly allowed operators are fast, the rest goes through the special attribute lookup.

lupa coroutines choke on nested pcalls & yield

I have been running after a weird error in error handling error lately when hacking splash and I seem to have pinned the issue down. Here's an example that looks admittedly contrived when all the unnecessary details are omitted:

from lupa import LuaRuntime
lua = LuaRuntime()

py_coro = lua.execute("""
coro = require("coroutine")

function main()
  print('pcall result:', pcall(pcall, coro.yield))
end

lua_coro = coro.create(main)

step = 1
while coro.status(lua_coro) ~= 'dead' do
   print('lua step '.. step .. ':', coro.resume(lua_coro, i))
   step = step + 1
end

return coro.create(main)
""")

for i, r in enumerate(py_coro(), 1):
    print 'py step %d:' % i, r

It produces the following output for me:

lua step 1: true
pcall result:   true    true    nil
lua step 2: true
py step 1: None
Traceback (most recent call last):
  File "test_lupa.py", line 22, in <module>
    for i, r in enumerate(py_coro(), 1):
  File "lupa/_lupa.pyx", line 810, in lupa._lupa._LuaThread.__next__ (lupa/_lupa.c:12213)
  File "lupa/_lupa.pyx", line 886, in lupa._lupa.resume_lua_thread (lupa/_lupa.c:13091)
  File "lupa/_lupa.pyx", line 1207, in lupa._lupa.raise_lua_error (lupa/_lupa.c:16746)
lupa._lupa.LuaError: error in error handling

As you can see, the coroutine is easily traversed inside Lua code but not in Python code. The issue is reproduced when all three following conditions are true:

  • there are nested pcalls in the coroutine
  • there is coro.yield somewhere inside those pcalls
  • outermost pcall invocation is not a functioncall statement, but an expression whose value is used afterwards and is not optimized away

Compilation and installation works but importing from Python seems to be broken

Hi there,

I've tried to build and install lupa on my Windows 7 64 repeatedly. The process seems to be successful, but at the time of using the installed library, Python claims:

Python 2.7.2 (default, Jun 12 2011, 14:24:46) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.

import lupa
Traceback (most recent call last):
File "", line 1, in
File "lupa__init__.py", line 31, in
from lupa._lupa import *
ImportError: No module named _lupa

The folder C:\Python27\Lib\site-packages\lupa seems to contain the correct stuff:

28/03/2013 15:25

.
28/03/2013 15:25 ..
28/03/2013 15:25 22 version.py
28/03/2013 15:25 163 version.pyc
28/03/2013 15:25 119,808 _lupa.pyd
28/03/2013 15:24 837 init.py
28/03/2013 15:25 820 init.pyc
5 File(s) 121,650 bytes

The building process seems to be successful, although with a few warnings:

C:\temp\lupa-0.20>setup.py install
found LuaJIT build in C:\temp\lupa-0.20\luajit-2.0\src
building statically
generated sources not available, need Cython to build
building with Cython 0.18
Compiling lupa/lupa.pyx because it changed.
Cythonizing lupa/lupa.pyx
running install
running build
running build_py
creating build
creating build\lib.win-amd64-2.7
creating build\lib.win-amd64-2.7\lupa
copying lupa\version.py -> build\lib.win-amd64-2.7\lupa
copying lupa__init
.py -> build\lib.win-amd64-2.7\lupa
running build_ext
building 'lupa._lupa' extension
creating build\temp.win-amd64-2.7
creating build\temp.win-amd64-2.7\Release
creating build\temp.win-amd64-2.7\Release\lupa
C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\BIN\amd64\cl.exe /c /nologo /Ox /MD /W3 /GS- /DNDEBUG -IC:\temp\lu
pa-0.20\luajit-2.0\src -IC:\Python27\include -IC:\Python27\PC /Tclupa/lupa.c /Fobuild\temp.win-amd64-2.7\Release\lupa/
lupa.obj
_lupa.c
lupa/_lupa.c(812) : warning C4244: '=' : conversion from 'Py_ssize_t' to 'int', possible loss of data
lupa/_lupa.c(825) : warning C4244: 'return' : conversion from 'Py_ssize_t' to 'int', possible loss of data
lupa/_lupa.c(846) : warning C4244: 'return' : conversion from 'Py_ssize_t' to 'int', possible loss of data
lupa/_lupa.c(3730) : warning C4244: 'function' : conversion from 'Py_ssize_t' to 'int', possible loss of data
lupa/_lupa.c(3730) : warning C4244: 'function' : conversion from 'Py_ssize_t' to 'int', possible loss of data
lupa/_lupa.c(8671) : warning C4244: '=' : conversion from 'Py_ssize_t' to 'int', possible loss of data
lupa/lupa.c(12090) : warning C4244: 'function' : conversion from 'Py_ssize_t' to 'int', possible loss of data
lupa/lupa.c(15984) : warning C4244: '=' : conversion from 'Py_ssize_t' to 'int', possible loss of data
C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\BIN\amd64\link.exe /DLL /nologo /INCREMENTAL:NO /LIBPATH:C:\Python
27\libs /LIBPATH:C:\Python27\PCbuild\amd64 /EXPORT:init_lupa build\temp.win-amd64-2.7\Release\lupa/lupa.obj C:\temp\lup
a-0.20\luajit-2.0\src\lua51.lib /OUT:build\lib.win-amd64-2.7\lupa_lupa.pyd /IMPLIB:build\temp.win-amd64-2.7\Release\lup
a_lupa.lib /MANIFESTFILE:build\temp.win-amd64-2.7\Release\lupa_lupa.pyd.manifest
lupa.obj : warning LNK4197: export 'init_lupa' specified multiple times; using first specification
Creating library build\temp.win-amd64-2.7\Release\lupa_lupa.lib and object build\temp.win-amd64-2.7\Release\lupa_lu
pa.exp
C:\Program Files\Microsoft SDKs\Windows\v7.0\bin\x64\mt.exe -nologo -manifest build\temp.win-amd64-2.7\Release\lupa_lup
a.pyd.manifest -outputresource:build\lib.win-amd64-2.7\lupa_lupa.pyd;2
running install_lib
copying build\lib.win-amd64-2.7\lupa\version.py -> C:\Python27\Lib\site-packages\lupa
copying build\lib.win-amd64-2.7\lupa_lupa.pyd -> C:\Python27\Lib\site-packages\lupa
copying build\lib.win-amd64-2.7\lupa__init
.py -> C:\Python27\Lib\site-packages\lupa
byte-compiling C:\Python27\Lib\site-packages\lupa\version.py to version.pyc
byte-compiling C:\Python27\Lib\site-packages\lupa__init
.py to init.pyc
running install_egg_info
Removing C:\Python27\Lib\site-packages\lupa-0.20-py2.7.egg-info
Writing C:\Python27\Lib\site-packages\lupa-0.20-py2.7.egg-info

Could this be an issue? I've taken the latest LuaJIT and lupa from the git repositories.

Lupa running against wrong version of lua

I have lua5.1 and lua5.3 installed on my system. I believe I have installed lupa against lua5.1. Running setup.py install outputs pkg-config found lua5.1 version 5.1.5. However, when I try to use lupa, it appears to be using lua5.3 still: lua.globals()._VERSION outputs 'Lua 5.3'. Any ideas on how to get it to use 5.1?

setup.py checks for libluajit.a; Windows incompatible?

I have built LuaJIT 2.0.0 on my Windows box, using the instructions found here: http://luajit.org/install.html

However, at the end of this build process (using msvcbuild.bat), there is no libluajit.a anywhere, so it seems impossible to build LuaJIT statically based on the checks setup.py does in find_luajit_build. It seems to me on Windows it should actually be checking for lua51.dll as well?

I don't really know much about pkg-config on Windows, quite frankly, but is that the only way to get lupa to install on Windows in this scenario? (No Cygwin or anything, I used the Windows SDK to build LuaJIT)

If that is the case, there's really no indication of that on the lupa page.

new release

Hey @scoder,

Would you mind cutting a pypi release with the current changes?

Implementing an execution timeout.

Would it be possible to implement a timeout for running a function? I'm running untrusted, sandboxed code, and I need the execution of functions to be limited. Do you have some pointers as to how this could be achieved?

LuaJIT 5.1 crashed(Segmentation fault) during out-of-range indexing on a _LuaObject

import lupa
lua = lupa.LuaRuntime()
lua.require('torch')
lg = lua.globals()
a = lg.torch.Tensor(2)
print(a[5]) # any out of range indexing will cause the core to crash.

the output is:

Segmentation fault (core dumped)

And then it quit the entire python process immediately, but in torch itself, the error can be captured:

bad argument #2 to '?' (out of range at /tmp/luarocks_torch-scm-1-6180/torch7/generic/Tensor.c:888)                                
stack traceback:                                                                                                                   
        [C]: at 0x7f67699b60b0                                                                                                     
        [C]: in function '__index'                                                                                                 
        [string "_RESULT={a[0]}"]:1: in main chunk                                                                                 
        [C]: in function 'xpcall'                                                                                                  
        /home/xxx/torch/install/share/lua/5.1/trepl/init.lua:651: in function 'repl'                                               
        .../wei/torch/install/lib/luarocks/rocks/trepl/scm-1/bin/th:199: in main chunk                                             
        [C]: at 0x00406670 

Any idea?

Install instruction problem / install problem

Hi,

So one thing I noticed in the INSTALL file is that there's no mention of running python setup.py install after running build. That's fairly obvious to me, but maybe not everyone.

On to my main problem. I installed LuaJIT from a sibling directory as lupa (both in /usr/local/src/) and when I went to run lupa, it said it couldn't find the so object it needed (output below.) Which is weird because it seemed to build OK, using pkg-config to find the LuaJIT package.

So then, I moved the LuaJIT directory that I'd installed from into the lupa directory, re-built/installed and everything seems to work now that it's statically linked.

Any idea why it wouldn't have worked originally? Thanks.


BUILDVM ../lib/vmdef.lua
DYNLINK libluajit.so
LINK luajit
OK Successfully built LuaJIT
make[1]: Leaving directory `/usr/local/src/LuaJIT-2.0.0-beta10/src'
==== Successfully built LuaJIT 2.0.0-beta10 ====
root@localhost:/usr/local/src/LuaJIT-2.0.0-beta10# make install
==== Installing LuaJIT 2.0.0-beta10 to /usr/local ====
mkdir -p /usr/local/bin /usr/local/lib /usr/local/include/luajit-2.0 /usr/local/share/man/man1 /usr/local/lib/pkgconfig /usr/local/share/luajit-2.0.0-beta10/jit /usr/local/share/lua/5.1 /usr/local/lib/lua/5.1
cd src && install -m 0755 luajit /usr/local/bin/luajit-2.0.0-beta10
cd src && test -f libluajit.a && install -m 0644 libluajit.a /usr/local/lib/libluajit-5.1.a || :
rm -f /usr/local/lib/libluajit-5.1.so.2.0.0 /usr/local/lib/libluajit-5.1.so /usr/local/lib/libluajit-5.1.so
cd src && test -f libluajit.so &&
install -m 0755 libluajit.so /usr/local/lib/libluajit-5.1.so.2.0.0 &&
ldconfig -n /usr/local/lib &&
ln -sf libluajit-5.1.so.2.0.0 /usr/local/lib/libluajit-5.1.so &&
ln -sf libluajit-5.1.so.2.0.0 /usr/local/lib/libluajit-5.1.so || :
cd etc && install -m 0644 luajit.1 /usr/local/share/man/man1
cd etc && sed -e "s|^prefix=.*|prefix=/usr/local|" luajit.pc > luajit.pc.tmp &&
install -m 0644 luajit.pc.tmp /usr/local/lib/pkgconfig/luajit.pc &&
rm -f luajit.pc.tmp
cd src && install -m 0644 lua.h lualib.h lauxlib.h luaconf.h lua.hpp luajit.h /usr/local/include/luajit-2.0
cd lib && install -m 0644 bc.lua v.lua dump.lua dis_x86.lua dis_x64.lua dis_arm.lua dis_ppc.lua dis_mips.lua dis_mipsel.lua bcsave.lua vmdef.lua /usr/local/share/luajit-2.0.0-beta10/jit
==== Successfully installed LuaJIT 2.0.0-beta10 to /usr/local ====

Note: the beta releases deliberately do NOT install a symlink for luajit
You can do this now by running this command (with sudo):

ln -sf luajit-2.0.0-beta10 /usr/local/bin/luajit

root@localhost:/usr/local/src/LuaJIT-2.0.0-beta10# ln -sf luajit-2.0.0-beta10 /usr/local/bin/luajit
root@localhost:/usr/local/src/LuaJIT-2.0.0-beta10# cd ..
root@localhost:/usr/local/src# ls
demoinfo2 demoinfo2.zip LuaJIT-2.0.0-beta10 LuaJIT-2.0.0-beta10.tar.gz lupa-0.20.tar.gz riak_1.2.0-1_i386.deb
root@localhost:/usr/local/src# tar zxvf lupa-0.20.tar.gz
lupa-0.20/
lupa-0.20/LICENSE.txt
lupa-0.20/setup.py
lupa-0.20/MANIFEST.in
lupa-0.20/README.rst
lupa-0.20/lupa/
lupa-0.20/lupa/init.py
lupa-0.20/lupa/lua.pxd
lupa-0.20/lupa/_lupa.c
lupa-0.20/lupa/lock.pxi
lupa-0.20/lupa/tests/
lupa-0.20/lupa/tests/init.py
lupa-0.20/lupa/tests/test.py
lupa-0.20/lupa/_lupa.pyx
lupa-0.20/lupa/version.py
lupa-0.20/CHANGES.rst
lupa-0.20/PKG-INFO
lupa-0.20/INSTALL.rst
lupa-0.20/MANIFEST
root@localhost:/usr/local/src# cd lupa-0.20/
root@localhost:/usr/local/src/lupa-0.20# python setup.py build
building without Cython
No local build of LuaJIT2 found in lupa directory, checking for installed library using pkg-config
Did not find LuaJIT2 using pkg-config: /bin/sh: pkg-config: not found

Traceback (most recent call last):
File "setup.py", line 111, in
ext_args = find_luajit_build()
File "setup.py", line 99, in find_luajit_build
raise RuntimeError("LuaJIT2 not found, please install the library and its development packages"
RuntimeError: LuaJIT2 not found, please install the library and its development packages, or put a local build into the lupa main directory (or pass '--no-luajit' option)
root@localhost:/usr/local/src/lupa-0.20# pkg-config
The program 'pkg-config' is currently not installed. You can install it by typing:
apt-get install pkg-config
root@localhost:/usr/local/src/lupa-0.20# aptitude install pkg-config
The following NEW packages will be installed:
pkg-config
0 packages upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 40.3 kB of archives. After unpacking 156 kB will be used.
Get: 1 http://us.archive.ubuntu.com/ubuntu/ oneiric/main pkg-config i386 0.26-1ubuntu1 [40.3 kB]
Fetched 40.3 kB in 0s (119 kB/s)
Selecting previously deselected package pkg-config.
(Reading database ... 35010 files and directories currently installed.)
Unpacking pkg-config (from .../pkg-config_0.26-1ubuntu1_i386.deb) ...
Processing triggers for man-db ...
Setting up pkg-config (0.26-1ubuntu1) ...

root@localhost:/usr/local/src/lupa-0.20# python setup.py build
building without Cython
No local build of LuaJIT2 found in lupa directory, checking for installed library using pkg-config
pkg-config found LuaJIT version 2.0.0-beta10

running build
running build_py
creating build
creating build/lib.linux-i686-2.7
creating build/lib.linux-i686-2.7/lupa
copying lupa/version.py -> build/lib.linux-i686-2.7/lupa
copying lupa/init.py -> build/lib.linux-i686-2.7/lupa
running build_ext
building 'lupa._lupa' extension
creating build/temp.linux-i686-2.7
creating build/temp.linux-i686-2.7/lupa
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I/usr/local/include/luajit-2.0 -I/usr/include/python2.7 -c lupa/_lupa.c -o build/temp.linux-i686-2.7/lupa/_lupa.o
lupa/_lupa.c: In function รข__pyx_pf_4lupa_5_lupa_9FastRLock_5__exit__รข:
lupa/_lupa.c:1561:13: warning: variable รข__pyx_v_tbรข set but not used [-Wunused-but-set-variable]
lupa/_lupa.c:1560:13: warning: variable รข__pyx_v_vรข set but not used [-Wunused-but-set-variable]
lupa/_lupa.c:1559:13: warning: variable รข__pyx_v_tรข set but not used [-Wunused-but-set-variable]
lupa/_lupa.c: In function รข__pyx_f_4lupa_5_lupa_call_pythonรข:
lupa/_lupa.c:11630:7: warning: variable รข__pyx_v_retรข set but not used [-Wunused-but-set-variable]
lupa/_lupa.c: In function รข__pyx_f_4lupa_5_lupa_py_as_functionรข:
lupa/_lupa.c:13839:42: warning: variable รข__pyx_v_py_objรข set but not used [-Wunused-but-set-variable]
lupa/_lupa.c: In function รข__pyx_f_4lupa_5_lupa_lua_object_reprรข:
lupa/_lupa.c:6202:18: warning: รข__pyx_v_ptrรข may be used uninitialized in this function [-Wuninitialized]
lupa/_lupa.c: In function รข__pyx_pf_4lupa_5_lupa_10_LuaObject_3__len__รข:
lupa/_lupa.c:4455:3: warning: รข__pyx_rรข may be used uninitialized in this function [-Wuninitialized]
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-Bsymbolic-functions build/temp.linux-i686-2.7/lupa/_lupa.o -L/usr/local/lib -lluajit-5.1 -o build/lib.linux-i686-2.7/lupa/_lupa.so
root@localhost:/usr/local/src/lupa-0.20# python
Python 2.7.2+ (default, Oct 4 2011, 20:03:08)
[GCC 4.6.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.

import lupa
Traceback (most recent call last):
File "", line 1, in
File "lupa/init.py", line 31, in
from lupa._lupa import *
ImportError: No module named _lupa
quit()
root@localhost:/usr/local/src/lupa-0.20# cd ..
root@localhost:/usr/local/src# python
Python 2.7.2+ (default, Oct 4 2011, 20:03:08)
[GCC 4.6.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
import lupa
Traceback (most recent call last):
File "", line 1, in
ImportError: No module named lupa
quit()
root@localhost:/usr/local/src# cd lupa-0.20/
root@localhost:/usr/local/src/lupa-0.20# python setup.py install
building without Cython
No local build of LuaJIT2 found in lupa directory, checking for installed library using pkg-config
pkg-config found LuaJIT version 2.0.0-beta10

running install
running build
running build_py
copying lupa/version.py -> build/lib.linux-i686-2.7/lupa
running build_ext
running install_lib
creating /usr/local/lib/python2.7/dist-packages/lupa
copying build/lib.linux-i686-2.7/lupa/version.py -> /usr/local/lib/python2.7/dist-packages/lupa
copying build/lib.linux-i686-2.7/lupa/_lupa.so -> /usr/local/lib/python2.7/dist-packages/lupa
copying build/lib.linux-i686-2.7/lupa/init.py -> /usr/local/lib/python2.7/dist-packages/lupa
byte-compiling /usr/local/lib/python2.7/dist-packages/lupa/version.py to version.pyc
byte-compiling /usr/local/lib/python2.7/dist-packages/lupa/init.py to init.pyc
running install_egg_info
Writing /usr/local/lib/python2.7/dist-packages/lupa-0.20.egg-info
root@localhost:/usr/local/src/lupa-0.20# cd ..
root@localhost:/usr/local/src# python
Python 2.7.2+ (default, Oct 4 2011, 20:03:08)
[GCC 4.6.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.

import lupa
Traceback (most recent call last):
File "", line 1, in
File "/usr/local/lib/python2.7/dist-packages/lupa/init.py", line 31, in
from lupa._lupa import *
ImportError: libluajit-5.1.so.2: cannot open shared object file: No such file or directory
quit()
root@localhost:/usr/local/src# find .. -iname 'libluajit'
../lib/libluajit-5.1.so.2.0.0
../lib/libluajit-5.1.a
../lib/libluajit-5.1.so
../lib/libluajit-5.1.so.2
../src/LuaJIT-2.0.0-beta10/src/libluajit.so
../src/LuaJIT-2.0.0-beta10/src/libluajit.a
root@localhost:/usr/local/src# ls lupa-0.20
build CHANGES.rst INSTALL.rst LICENSE.txt lupa MANIFEST MANIFEST.in PKG-INFO README.rst setup.py
root@localhost:/usr/local/src# ls lupa-0.20/build/
lib.linux-i686-2.7 temp.linux-i686-2.7
root@localhost:/usr/local/src# ls lupa-0.20/build/lib.linux-i686-2.7/
lupa
root@localhost:/usr/local/src# ls lupa-0.20/build/lib.linux-i686-2.7/lupa/
init.py _lupa.so version.py
root@localhost:/usr/local/src# ls /usr/local/include/luajit-2.0
lauxlib.h luaconf.h lua.h lua.hpp luajit.h lualib.h

Use luajit from a different location?

Hi there, I'm trying to use lupa with lua installed in a non-standard location. Is it somehow possible to point lupa to that location instead of using the default location? Concretely, when I attempt to load a file that uses torch, I get the following error:

Type "help", "copyright", "credits" or "license" for more information.
>>> from lupa import LuaRuntime
>>> lua = LuaRuntime(unpack_returned_tuples=True)
>>> lua.require('train_agent')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "lupa/_lupa.pyx", line 268, in lupa._lupa.LuaRuntime.require (lupa/_lupa.c:4683)
  File "lupa/_lupa.pyx", line 1245, in lupa._lupa.call_lua (lupa/_lupa.c:17287)
  File "lupa/_lupa.pyx", line 1254, in lupa._lupa.execute_lua_call (lupa/_lupa.c:17400)
  File "lupa/_lupa.pyx", line 1207, in lupa._lupa.raise_lua_error (lupa/_lupa.c:16746)
lupa._lupa.LuaError: ./initenv.lua:8: module 'torch' not found:
    no field package.preload['torch']
    no file '/usr/local/share/lua/5.2/torch.lua'
    no file '/usr/local/share/lua/5.2/torch/init.lua'
    no file '/usr/local/lib/lua/5.2/torch.lua'
    no file '/usr/local/lib/lua/5.2/torch/init.lua'
    no file './torch.lua'
    no file '/usr/local/lib/lua/5.2/torch.so'
    no file '/usr/local/lib/lua/5.2/loadall.so'
    no file './torch.so'
>>>

Everything works fine if I load the file using luajit from the torch installation using ~/torch/install/bin/luajit train_agent.lua.

Is it possible to somehow make this work? Any help is greatly appreciated!

Numpy support?

Hey,

I didn't found any information regarding Numpy (PyArrayObject), so I guess it's not supported.
A translation between Numpy and maybe Torch's Lua Tensor (THTensor) would be nice.

Do you know if there is any project or code out there which does that?

THTensor, saved via LuaT.

PyArrayObject.

Python exceptions can't be handled from Lua scripts using pcall

>>> import lupa; lua = lupa.LuaRuntime()
>>> pcall = lua.eval("pcall")
>>> def py_func(): raise Exception("python exception")
... 
>>> lua_func = lua.eval("function () error('lua error') end")
>>> pcall(lua_func)
(False, u'[string "<python>"]:1: lua error')
>>> pcall(py_func)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "lupa/_lupa.pyx", line 507, in lupa._lupa._LuaObject.__call__ (lupa/_lupa.c:7743)
  File "lupa/_lupa.pyx", line 1245, in lupa._lupa.call_lua (lupa/_lupa.c:17287)
  File "lupa/_lupa.pyx", line 1252, in lupa._lupa.execute_lua_call (lupa/_lupa.c:17381)
  File "lupa/_lupa.pyx", line 231, in lupa._lupa.LuaRuntime.reraise_on_exception (lupa/_lupa.c:4144)
  File "lupa/_lupa.pyx", line 1386, in lupa._lupa.py_call_with_gil (lupa/_lupa.c:18673)
  File "lupa/_lupa.pyx", line 1352, in lupa._lupa.call_python (lupa/_lupa.c:18407)
  File "<stdin>", line 1, in py_func
Exception: python exception

no attribute 'lua_type' in module lupa

i am install lupa by command: sudo pip install lupa==1.0.1; luajit version is 2.0.2. python: 2.7
it's error when execute: import lupa; lupa.lua_type();
error is: AttributeError: module object has no attribute lua_type;
how can i fix it ?

Socket.core Linking Error

Hi,

I am attempting to add load the socket library in a Lupa runtime.

I installed socket with Luarocks, so I needed to append the necessary paths.

>>> import lupa
>>> sys.setdlopenflags(orig_dlflags)
>>> lua = lupa.LuaRuntime()
>>> lua.execute("package.cpath = package.cpath .. ';/root/.luarocks/lib/lua/5.3/?.so;/usr/local/lib/lua/5.3/?.so;/usr/local/lib/lua/5.3/loadall.so;./?.so'")
>>> lua.execute("  package.path = package.path .. ';/root/.luarocks/share/lua/5.3/?.lua;/root/.luarocks/share/lua/5.3/?/init.lua;/usr/local/share/lua/5.3/?.lua;/usr/local/share/lua/5.3/?/init.lua;/usr/local/lib/lua/5.3/?.lua;/usr/local/lib/lua/5.3/?/init.lua;./?.lua;./?/init.lua'")
>>> lua.require("socket")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "lupa/_lupa.pyx", line 268, in lupa._lupa.LuaRuntime.require (lupa/_lupa.c:4683)
  File "lupa/_lupa.pyx", line 1245, in lupa._lupa.call_lua (lupa/_lupa.c:17287)
  File "lupa/_lupa.pyx", line 1254, in lupa._lupa.execute_lua_call (lupa/_lupa.c:17400)
  File "lupa/_lupa.pyx", line 1207, in lupa._lupa.raise_lua_error (lupa/_lupa.c:16746)
lupa._lupa.LuaError: error loading module 'socket.core' from file '/usr/local/lib/lua/5.3/socket/core.so':
    /usr/local/lib/lua/5.3/socket/core.so: undefined symbol: lua_rotate

A similar issue is mentioned here but the proposed solution does not seem to work for me (I am running Ubuntu):

http://stackoverflow.com/questions/8361437/linker-error-lunatic-python-lua-requiresocket-undefined-symbol-lua-getme

From my understanding, there is a linking problem to liblua5.3.so?

Any help would be greatly appreciated.

Thanks,
Alex

Surprising table equality behavior

import lupa
r = lupa.LuaRuntime()
r.globals().test == r.globals().test
False

I can supply a patch that adds richcmp and hash to _LuaTable.

lupa+coroutines segfaults in IPython

I'm using lupa 1.0, ipython 2.3 (both installed using pip) and lua 5.2.3 (installed using homebrew). The issue is quite strange: lupa segfaults in IPython but not in python.

Python:

>>> import lupa; lua = lupa.LuaRuntime()
>>> f = lua.eval("function(N) coroutine.yield(N) end")
>>> list(f.coroutine(1))
[1]
>>> f.coroutine(1)
<Lua thread at 0x7ff198f00b30>

IPython:

In [1]: import lupa; lua = lupa.LuaRuntime()
In [2]: f = lua.eval("function(N) coroutine.yield(N) end")
In [3]: list(f.coroutine(1))  # f.coroutine always seems to work fine when wrapped in list()
Out[3]: [1]
In [4]: f.coroutine(1)
Segmentation fault: 11

It is likely a manifistation of a problem unrelated to python vs ipython, it could segfault in IPython e.g. because memory layout of python and ipython processes are different. I've had a similar issue in one of my wrappers once (works in python, doesn't work in ipython); the cause was improper handling of null-terminated strings. But I'm sure you know all this stuff way better :)

Error when importing lupa

Hi, I installed lupa as explained in INSTALL.rst and it seemed to work. The output of python setup.py install is the following:

found LuaJIT build in /afs/inf.ed.ac.uk/user/s13/s1333293/lupa-1.2/LuaJIT-2.0.4/src
building statically
building without Cython
running install
running bdist_egg
running egg_info
writing lupa.egg-info/PKG-INFO
writing top-level names to lupa.egg-info/top_level.txt
writing dependency_links to lupa.egg-info/dependency_links.txt
reading manifest file 'lupa.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
writing manifest file 'lupa.egg-info/SOURCES.txt'
installing library code to build/bdist.linux-x86_64/egg
running install_lib
running build_py
creating build
creating build/lib.linux-x86_64-2.6
creating build/lib.linux-x86_64-2.6/lupa
copying lupa/version.py -> build/lib.linux-x86_64-2.6/lupa
copying lupa/__init__.py -> build/lib.linux-x86_64-2.6/lupa
running build_ext
building 'lupa._lupa' extension
creating build/temp.linux-x86_64-2.6
creating build/temp.linux-x86_64-2.6/lupa
gcc -pthread -fno-strict-aliasing -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -DNDEBUG -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -fPIC -DLUA_COMPAT_ALL -I/afs/inf.ed.ac.uk/user/s13/s1333293/lupa-1.2/LuaJIT-2.0.4/src -I/usr/include/python2.6 -c lupa/_lupa.c -o build/temp.linux-x86_64-2.6/lupa/_lupa.o
gcc -pthread -shared build/temp.linux-x86_64-2.6/lupa/_lupa.o /afs/inf.ed.ac.uk/user/s13/s1333293/lupa-1.2/LuaJIT-2.0.4/src/libluajit.a -L/usr/lib64 -lpython2.6 -o build/lib.linux-x86_64-2.6/lupa/_lupa.so
creating build/bdist.linux-x86_64
creating build/bdist.linux-x86_64/egg
creating build/bdist.linux-x86_64/egg/lupa
copying build/lib.linux-x86_64-2.6/lupa/version.py -> build/bdist.linux-x86_64/egg/lupa
copying build/lib.linux-x86_64-2.6/lupa/__init__.py -> build/bdist.linux-x86_64/egg/lupa
copying build/lib.linux-x86_64-2.6/lupa/_lupa.so -> build/bdist.linux-x86_64/egg/lupa
byte-compiling build/bdist.linux-x86_64/egg/lupa/version.py to version.pyc
byte-compiling build/bdist.linux-x86_64/egg/lupa/__init__.py to __init__.pyc
creating stub loader for lupa/_lupa.so
byte-compiling build/bdist.linux-x86_64/egg/lupa/_lupa.py to _lupa.pyc
creating build/bdist.linux-x86_64/egg/EGG-INFO
copying lupa.egg-info/PKG-INFO -> build/bdist.linux-x86_64/egg/EGG-INFO
copying lupa.egg-info/SOURCES.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying lupa.egg-info/dependency_links.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying lupa.egg-info/not-zip-safe -> build/bdist.linux-x86_64/egg/EGG-INFO
copying lupa.egg-info/top_level.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
writing build/bdist.linux-x86_64/egg/EGG-INFO/native_libs.txt
creating dist
creating 'dist/lupa-1.2-py2.6-linux-x86_64.egg' and adding 'build/bdist.linux-x86_64/egg' to it
removing 'build/bdist.linux-x86_64/egg' (and everything under it)
Processing lupa-1.2-py2.6-linux-x86_64.egg
creating /afs/inf.ed.ac.uk/user/s13/s1333293/virt_env/virt1/lib/python2.6/site-packages/lupa-1.2-py2.6-linux-x86_64.egg
Extracting lupa-1.2-py2.6-linux-x86_64.egg to /afs/inf.ed.ac.uk/user/s13/s1333293/virt_env/virt1/lib/python2.6/site-packages
Adding lupa 1.2 to easy-install.pth file

Installed /afs/inf.ed.ac.uk/user/s13/s1333293/virt_env/virt1/lib/python2.6/site-packages/lupa-1.2-py2.6-linux-x86_64.egg
Processing dependencies for lupa==1.2
Finished processing dependencies for lupa==1.2

However, when I tried to import lupa I got the error:

>>> import lupa
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "lupa/__init__.py", line 31, in <module>
    from lupa._lupa import *
ImportError: No module named _lupa

I've seen two similar issues on this but they didn't help. The funny thing is that I actually succeeded in installing using lupa a few days ago but I uninstalled it by mistake and now it doesn't seem to work anymore. Any ideas?

Marco

Update pypi version to use LuaJIT 2.0.4

LuaJIT 2.0.4 was released recently, it would be nice to have the pypi binary updated to use this version, plus reflect the newest changes (though there arent that many)

Thank you for your work! Keep it up ๐Ÿ‘

skip certain libraries (e.g. io and os) to sandbox semi-trusted code

Dear contributers, dear community,

I'd like to build a web-application in python that can be extended using lua scripts. These extensions are contributed by affiliated developers, and in general, their code is trustworthy. But I'd sleep a bit better, if I could skip some of the standard libraries when creating a new LuaRuntime.

The io and os libraries are most critical as they provide access to the file system and allow to run any programs within the privileges of the user who is running the web server.

The problem comes with the call to the luaL_openlibs function. I'd be glad, if I could state in a list, which module should be loaded when creating a new LuaRuntime. This could help: http://stackoverflow.com/questions/4551101/lual-openlibs-and-sandboxing-scripts

Thanks a lot for the great library,
Timo

From Lua to Python

I have this piece of Lua-Torch code and try to put into Python code
. I have difficulty to understand the meaning result/process of:
= nn.Linear(size1
t2h_d.data.module:share(

I would like to find equivalent of those 2 functions in numpy Python.
Here, remaining code :

   import 'nn'
   import 'nngraph'

   nh_t= nn.identity(d)
   h_d= nn.identity(d)
size1= 5
t2h_d = nn.Linear(size1, 4 * size1)(h_t):annotate{name='ih_'..L}  
d2h_d = nn.Linear(size1, 4 * size1)(h_d):annotate{name='hh_'..L}  


t2h_d.data.module:share(shared_weights[1], 'weight', 'bias', 'grdWeight', 'grdBias')
d2h_d.data.module:share(shared_weights[2], 'weight', 'bias', 'grdWeight', 'grdBias')

`lupa._lupa.LuaError: Failed to initialise Lua runtime`

$ python
Python 2.7.3 (default, Nov  1 2012, 14:21:55) 
[GCC 4.2.1 Compatible Apple Clang 4.0 ((tags/Apple/clang-421.0.57))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from lupa import LuaRuntime
>>> lua = LuaRuntime()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "_lupa.pyx", line 116, in lupa._lupa.LuaRuntime.__cinit__ (lupa/_lupa.c:2237)
lupa._lupa.LuaError: Failed to initialise Lua runtime

lupa was installed with the following command.

$ sudo pip-2.7 install lupa
Downloading/unpacking lupa
  Downloading lupa-0.20.tar.gz (119kB): 119kB downloaded
  Running setup.py egg_info for package lupa
    building with Cython 0.18
    No local build of LuaJIT2 found in lupa directory, checking for installed library using pkg-config
    pkg-config found LuaJIT version 2.0.0-beta10


Installing collected packages: lupa
  Running setup.py install for lupa
    building with Cython 0.18
    No local build of LuaJIT2 found in lupa directory, checking for installed library using pkg-config
    pkg-config found LuaJIT version 2.0.0-beta10

    skipping 'lupa/_lupa.c' Cython extension (up-to-date)
    building 'lupa._lupa' extension
    /usr/bin/clang -fno-strict-aliasing -fno-common -dynamic -pipe -O2 -fwrapv -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -I/opt/local/include/luajit-2.0 -I/opt/local/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7 -c lupa/_lupa.c -o build/temp.macosx-10.8-x86_64-2.7/lupa/_lupa.o
    lupa/_lupa.c:9253:14: warning: explicitly assigning a variable of type 'PyObject *' (aka 'struct _object *') to itself [-Wself-assign]
      __pyx_self = __pyx_self;
      ~~~~~~~~~~ ^ ~~~~~~~~~~
    lupa/_lupa.c:9331:14: warning: explicitly assigning a variable of type 'PyObject *' (aka 'struct _object *') to itself [-Wself-assign]
      __pyx_self = __pyx_self;
      ~~~~~~~~~~ ^ ~~~~~~~~~~
    2 warnings generated.
    /usr/bin/clang -bundle -undefined dynamic_lookup -isysroot / -L/opt/local/lib build/temp.macosx-10.8-x86_64-2.7/lupa/_lupa.o -L/opt/local/lib -lluajit-5.1 -o build/lib.macosx-10.8-x86_64-2.7/lupa/_lupa.so

Successfully installed lupa
Cleaning up...

__pairs metamethod is ignored when iterating Lua tables from Python

Here's a simple example:

In [16]: copy, proxy = LuaRuntime().execute("""
obj = {foo=123, bar=345}

proxy = {}
setmetatable(proxy, {
  __index = function(self, key)
    return obj[key]
  end,

  __newindex = function(self, key, value)
    obj[key] = value
  end,

  __pairs = function(self)
    return pairs(obj)
  end,
})

copy = {}
for k,v in pairs(proxy) do
  copy[k] = v
end

return copy, proxy
""")

In [17]: copy['foo']
Out[17]: 123

In [18]: proxy['foo']
Out[18]: 123

In [19]: list(copy.items())
Out[19]: [(u'foo', 123), (u'bar', 345)]

In [20]: list(proxy.items())
Out[20]: []

Binary instalation for windows python 2.7 64 or anaconda

I have tryed to build the lupa package for anaconda P2.7 64 and pyhon 2.7 64, but without success. I got this error when I use setup.py:

build\temp.win-amd64-2.7\Release\lupa\_lupa.o:_lupa.c:(.text+0x89): undefined reference to `_imp___Py_NoneStruct'
...
...
...
c:/mingw/bin/../lib/gcc/mingw32/4.8.1/../../../../mingw32/bin/ld.exe: build\temp.win-amd64-2.7\Release\lupa\_lupa.o: bad reloc address 0x0 in section `.data'
collect2.exe: error: ld returned 1 exit status
error: command 'gcc' failed with exit status 1

When I try to compile using mingw environment I got this one:

F:\python64\lupa>gcc.exe -DMS_WIN64 -shared -s build\_lupa.o "C:\Program Files (x86)\Lua\5.1\lib\lua51.dll" build\_lupa.def -LC:\Users\emilio\AppData\Local\Continuum\Anaconda\libs -LC:\Users\emilio\AppData\Local\Continuum\Anaconda\PCbuild\amd64 -lpython27 -lmsvcr90 -o build\_lupa.pyd collect2.exe: error: ld returned 5 exit status and an access violation window.

Do you know how to fix it ? I tryed 3 different versions of mingw without success, or do you know where can I found the windows binary installation ?

Best regards,
secauvr

Crashing Python by iterating Lua table [double post from lupa-dev mailing list]

Hi lupa-dev,

I am using lupa 0.19 with python 2.6.2, and I consistently get "Fatal Python error: deallocating None" error. I suspect python is trying to free a reference inside Lua that is already freed by Lua.

Here is how to reproduce it. First, create two files test.lua and test.py as shown below:

test.lua:

function test()
  local t = {}
  t.foo = 'bar'
  t.hello = 'world'
  return t
end

END of test.lua

test.py:

import lupa
from lupa import LuaRuntime


def func():
  py_obj = dict()
  py_table = dict()
  table = lua.globals().test()
  for k, v in table.items():
    py_table[k] = v
  py_obj['table'] = py_table

def func2():
  py_obj = dict()
  py_table = dict()
  table = lua.globals().test()
  py_table['foo'] = table['foo']
  py_table['hello'] = table['hello']
  py_obj['table'] = py_table
    
lua = LuaRuntime()

f = open("./test.lua", "r")
lines = "".join(f.readlines())
lua.execute(lines)

for i in range(1, 100000):
  func() # this will crash
  #func2() # this will not crash

END of test.py

Run test.py, and by switching between func() and func2(), you will see func() crashes, but func2() doesn't. The only difference between func and func2 is funct() calls the table.items() iterator to extract the content, and func2() directly indexes into table.

Surprising `is` behaviour

Currently, a new _LuaTable python object is created every time a Lua table is access from Python. This causes a case where the table appears to not be itself:

>>> import lupa
>>> lua = lupa.LuaRuntime()
>>> lua.execute("foo = {}")
>>> f = lua.globals().foo
>>> g = lua.globals().foo
>>> f is g
False
>>> f
<Lua table at 0x0000000000327C18>
>>> g
<Lua table at 0x0000000000327C18>

The repr makes it especially confusing, since it shows the address of the underlying Lua object.

My suggestion would be to use a weakref cache when creating the objects, either in py_from_lua or the new_lua_* functions. I don't imagine this would greatly affect performance for cache misses, and in addition to fixing the identity issue, should cut down on some garbage.

I'm not actually able to compile lupa on my current machine, and I don't have much experience with Cython, but I can attempt a patch for this if I get the go-ahead.

Strange access behavior.

This is PROBABLY in the docs and I just misunderstood this, but what is the explanation for this behavior?

from lupa import LuaRuntime
lua = LuaRuntime()

fone = lua.eval('function (a) return a.thing == nil end')
ftwo = lua.eval('function (a) return a.thing() == nil end')

class Thing(object):
... def init(self):
... self.thing = None
...
class ThingTwo(object):
... def thing(self):
... return None
...
fone(Thing())
False
ftwo(ThingTwo())
True

convert a python function in py_to_lua

Hi,

I am trying to pass a python function as argument into a function defined in lua code, it complaining that: LuaError: XXXXXX.lua:44: attempt to call local 'opfunc' (a table value).

def tuple_fun():
    return "one", "two", "three", "four"
lua.globals()['fun'] = tuple_fun

lua.eval('type(fun)')
# output userdata

lua.eval('type(python.as_function(fun))')
# output function

I know that by using python.as_function in lua code can make lupa recognize the object as a function, but in my case, the lua code is not possible to change because it's in a third party library, I try to call the function in lupa code.

I checked the code in _lupa.pyx, in the function named py_to_lua, why not just convert the python function into a lua object? or add the python.as_function automatically?

Can anyone help?

installation fails with luajit from homebrew

Hi,

Thanks for an awesome project!

I'm installing lupa into 2.7 virtualenv on OS X 10.9.5. The following seems to work fine:

brew install lua
pip install lupa

It detects lua 5.2.3, builds fine and examples from readme work.

With

brew install luajit
pip install lupa

it correctly detects luajit 2.0.3 and builds without errors, but lupa doesn't work:

>>> import lupa
>>> lupa.LuaRuntime()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "lupa/_lupa.pyx", line 147, in lupa._lupa.LuaRuntime.__cinit__ (lupa/_lupa.c:2906)
lupa._lupa.LuaError: Failed to initialise Lua runtime
>>> 

From the install instructions it is unclear if it should ever work, but since non-jit lua seems to work maybe there is a way to make lupa work with homebrew-installed luajit too?

Python functions called from Lua do not unpack returned tuples.

def pyfun():
    return "one", "two", "three"

lua.globals()['pyfun'] = pyfun
lua.execute("a, b = pyfun() print(a) print(b)")

prints:

('one', 'two', 'three')
nil

where one might expect:

one
two

This behavior makes it difficult (or impossible) to write Python libraries that implement the Lua convention:

result, message = somePossiblyFailingFunction(...)

I'm wondering if there is a reason I am overlooking for not unpacking returned tuples. Maybe this is just an oversight? It seems pretty easy to alter this behavior. I can offer you a patch (and unit tests) if you are interested.

Cannot compile with local LuaJIT

I ran into this in my Fetchy branch, but I can reproduce in master, so I know it's not me.

git clone [email protected]:scoder/lupa.git
cd lupa
wget http://luajit.org/download/LuaJIT-2.0.2.tar.gz
tar xvf LuaJIT-2.0.2.tar.gz
cd LuaJIT-2.0.2/
make
cd ..
virtualenv venv
source venv/bin/activate
python setup.py install

If I've got my reproduction steps right, this should grab the latest Lupa, then the latest LuaJIT, then builds LuaJIT, creates a virtualenv, and attempts to install Lupa into that virtualenv.

The error goes like this:

found LuaJIT build in /home/philip/projects/lupa/LuaJIT-2.0.2/src
building statically
building without Cython
running install
running build
running build_py
copying lupa/version.py -> build/lib.linux-x86_64-2.7/lupa
running build_ext
building 'lupa._lupa' extension
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I/home/philip/projects/lupa/LuaJIT-2.0.2/src -I/usr/include/python2.7 -c lupa/_lupa.c -o build/temp.linux-x86_64-2.7/lupa/_lupa.o
gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-z,relro build/temp.linux-x86_64-2.7/lupa/_lupa.o /home/philip/projects/lupa/LuaJIT-2.0.2/src/libluajit.a -o build/lib.linux-x86_64-2.7/lupa/_lupa.so
/usr/bin/ld: /home/philip/projects/lupa/LuaJIT-2.0.2/src/libluajit.a(lj_err.o): relocation R_X86_64_32S against `.rodata' can not be used when making a shared object; recompile with -fPIC
/home/philip/projects/lupa/LuaJIT-2.0.2/src/libluajit.a: could not read symbols: Bad value
collect2: error: ld returned 1 exit status
error: command 'gcc' failed with exit status 1

I'm hoping you'll have more insight into this issue than I do.

tables as kwargs for Python functions

I'm using decorators to make it possible for Python functions to be called from Lua with both "named arguments" and "positional arguments": code, tests.

The decorator has caveats, but if you think it is OK to provide it in lupa I can submit a PR (if we fix #26).

SEGFAULT calling lua

I am getting segfaults from time to time. It seems to happen when python objects are wrapped and sent to the lua runtime.
Lupa version 1.4
Lua: tested version 5.1.5, 5.2.3, and luajit 2.0.4

Backtraces of the crash:
Lua:

#0  0x00007f639189c261 in lua_type () from /usr/lib64/liblua5.2.so.0
#1  0x00007f6391ae1b21 in __pyx_f_4lupa_5_lupa_lua_object_repr ()
   from /home/micke/Documents/dev/telldus/tellstick-server/build/env/lib/python2.7/site-packages/lupa/_lupa.so
#2  0x00007f6391adf4ac in __pyx_pf_4lupa_5_lupa_10_LuaObject_14__str__ ()
   from /home/micke/Documents/dev/telldus/tellstick-server/build/env/lib/python2.7/site-packages/lupa/_lupa.so
#3  0x00007f6391ade33d in __pyx_pw_4lupa_5_lupa_10_LuaObject_15__str__ ()
   from /home/micke/Documents/dev/telldus/tellstick-server/build/env/lib/python2.7/site-packages/lupa/_lupa.so
#4  0x00007f63994c974a in _PyObject_Str () from /usr/lib64/libpython2.7.so.1.0
#5  0x00007f63994dad98 in PyString_Format () from /usr/lib64/libpython2.7.so.1.0
#6  0x00007f639952735f in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#7  0x00007f63995265cb in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#8  0x00007f63995265cb in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#9  0x00007f63995265cb in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#10 0x00007f63995265cb in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#11 0x00007f63995265cb in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#12 0x00007f63995265cb in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#13 0x00007f63995265cb in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#14 0x00007f6399529ad0 in PyEval_EvalCodeEx () from /usr/lib64/libpython2.7.so.1.0
#15 0x00007f63994b30cd in ?? () from /usr/lib64/libpython2.7.so.1.0
#16 0x00007f639948d333 in PyObject_Call () from /usr/lib64/libpython2.7.so.1.0
#17 0x00007f639952365e in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#18 0x00007f6399529ad0 in PyEval_EvalCodeEx () from /usr/lib64/libpython2.7.so.1.0
#19 0x00007f63994b30cd in ?? () from /usr/lib64/libpython2.7.so.1.0
#20 0x00007f639948d333 in PyObject_Call () from /usr/lib64/libpython2.7.so.1.0
#21 0x00007f639952365e in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#22 0x00007f6399529ad0 in PyEval_EvalCodeEx () from /usr/lib64/libpython2.7.so.1.0
#23 0x00007f63995264ae in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#24 0x00007f6399529ad0 in PyEval_EvalCodeEx () from /usr/lib64/libpython2.7.so.1.0
#25 0x00007f63994b30cd in ?? () from /usr/lib64/libpython2.7.so.1.0
#26 0x00007f639948d333 in PyObject_Call () from /usr/lib64/libpython2.7.so.1.0
#27 0x00007f639952365e in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#28 0x00007f63995265cb in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#29 0x00007f63995265cb in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#30 0x00007f6399529ad0 in PyEval_EvalCodeEx () from /usr/lib64/libpython2.7.so.1.0
#31 0x00007f63994b2fec in ?? () from /usr/lib64/libpython2.7.so.1.0
#32 0x00007f639948d333 in PyObject_Call () from /usr/lib64/libpython2.7.so.1.0
#33 0x00007f639949c275 in ?? () from /usr/lib64/libpython2.7.so.1.0
#34 0x00007f639948d333 in PyObject_Call () from /usr/lib64/libpython2.7.so.1.0
#35 0x00007f639951fd77 in PyEval_CallObjectWithKeywords () from /usr/lib64/libpython2.7.so.1.0
#36 0x00007f6399558ab2 in ?? () from /usr/lib64/libpython2.7.so.1.0
#37 0x00007f639922c444 in start_thread () from /lib64/libpthread.so.0
#38 0x00007f6398f735ed in clone () from /lib64/libc.so.6

Luajit:

#0  0x00007f555d8a3c78 in lua_rawgeti () from /usr/lib64/libluajit-5.1.so.2
#1  0x00007f555db13b3e in __pyx_f_4lupa_5_lupa_10_LuaObject_push_lua_object ()
   from /home/micke/Documents/dev/telldus/tellstick-server/build/env/lib/python2.7/site-packages/lupa/_lupa.so
#2  0x00007f555db15736 in __pyx_pf_4lupa_5_lupa_10_LuaObject_14__str__ ()
   from /home/micke/Documents/dev/telldus/tellstick-server/build/env/lib/python2.7/site-packages/lupa/_lupa.so
#3  0x00007f555db152f7 in __pyx_pw_4lupa_5_lupa_10_LuaObject_15__str__ ()
   from /home/micke/Documents/dev/telldus/tellstick-server/build/env/lib/python2.7/site-packages/lupa/_lupa.so
#4  0x00007f556550074a in _PyObject_Str () from /usr/lib64/libpython2.7.so.1.0
#5  0x00007f5565511d98 in PyString_Format () from /usr/lib64/libpython2.7.so.1.0
#6  0x00007f556555e35f in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#7  0x00007f556555d5cb in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#8  0x00007f556555d5cb in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#9  0x00007f556555d5cb in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#10 0x00007f556555d5cb in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#11 0x00007f556555d5cb in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#12 0x00007f556555d5cb in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#13 0x00007f556555d5cb in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#14 0x00007f5565560ad0 in PyEval_EvalCodeEx () from /usr/lib64/libpython2.7.so.1.0
#15 0x00007f55654ea0cd in ?? () from /usr/lib64/libpython2.7.so.1.0
#16 0x00007f55654c4333 in PyObject_Call () from /usr/lib64/libpython2.7.so.1.0
#17 0x00007f556555a65e in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#18 0x00007f5565560ad0 in PyEval_EvalCodeEx () from /usr/lib64/libpython2.7.so.1.0
#19 0x00007f55654ea0cd in ?? () from /usr/lib64/libpython2.7.so.1.0
#20 0x00007f55654c4333 in PyObject_Call () from /usr/lib64/libpython2.7.so.1.0
#21 0x00007f556555a65e in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#22 0x00007f5565560ad0 in PyEval_EvalCodeEx () from /usr/lib64/libpython2.7.so.1.0
#23 0x00007f556555d4ae in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#24 0x00007f5565560ad0 in PyEval_EvalCodeEx () from /usr/lib64/libpython2.7.so.1.0
#25 0x00007f55654ea0cd in ?? () from /usr/lib64/libpython2.7.so.1.0
#26 0x00007f55654c4333 in PyObject_Call () from /usr/lib64/libpython2.7.so.1.0
#27 0x00007f556555a65e in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#28 0x00007f556555d5cb in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#29 0x00007f556555d5cb in PyEval_EvalFrameEx () from /usr/lib64/libpython2.7.so.1.0
#30 0x00007f5565560ad0 in PyEval_EvalCodeEx () from /usr/lib64/libpython2.7.so.1.0
#31 0x00007f55654e9fec in ?? () from /usr/lib64/libpython2.7.so.1.0
#32 0x00007f55654c4333 in PyObject_Call () from /usr/lib64/libpython2.7.so.1.0
#33 0x00007f55654d3275 in ?? () from /usr/lib64/libpython2.7.so.1.0
#34 0x00007f55654c4333 in PyObject_Call () from /usr/lib64/libpython2.7.so.1.0
#35 0x00007f5565556d77 in PyEval_CallObjectWithKeywords () from /usr/lib64/libpython2.7.so.1.0
#36 0x00007f556558fab2 in ?? () from /usr/lib64/libpython2.7.so.1.0
#37 0x00007f5565263444 in start_thread () from /lib64/libpthread.so.0
#38 0x00007f5564faa5ed in clone () from /lib64/libc.so.6

How to specify the lua version

Hi, My lua version is 5.2.But when I execute lua.require('socket'), out :

LuaError: module 'socket' not found:
    no field package.preload['socket']
    no file './socket.lua'
    no file '/usr/share/luajit-2.0.0-beta9/socket.lua'
    no file '/usr/local/share/lua/5.1/socket.lua'
    no file '/usr/local/share/lua/5.1/socket/init.lua'
    no file '/usr/share/lua/5.1/socket.lua'
    no file '/usr/share/lua/5.1/socket/init.lua'
    no file './socket.so'
    no file '/usr/local/lib/lua/5.1/socket.so'
    no file '/usr/lib/x86_64-linux-gnu/lua/5.1/socket.so'
    no file '/usr/lib/lua/5.1/socket.so'
    no file '/usr/local/lib/lua/5.1/loadall.so'

And lua.globals()._VERSION is Lua 5.1

Can not used in centos

After I install lupa 1.1 by "pip install lupa" in CentOS, I found this error:

[vagrant@localhost download]$ python
Python 2.7.5 (default, Jun 24 2015, 00:41:19) 
[GCC 4.8.3 20140911 (Red Hat 4.8.3-9)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import lupa
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/site-packages/lupa/__init__.py", line 31, in <module>
    from lupa._lupa import *
ImportError: /usr/lib/python2.7/site-packages/lupa/_lupa.so: undefined symbol: lua_gettop
>>> 

Can anyone help me?

Named functions

Hi !

I am using lupa to validate some LUA expressions persisted to a data store from a python webapp. The thing is my expressions contains some custom functions defined elsewhere. I am wondering is there is a way to define them before running the expression I am trying to validate ?

EG:

expr = "field('brand') == 'audi'"

Here field is a custom function, I am just trying to enforce that the LUA syntax is valid, not execute exactly the whole function so I figured I had to do something like that :

lua = LuaRuntime(unpack_returned_tuples=True)
lua.eval('function field(x) return x end')
print lua.eval(expr)

But I keep having this error :

Traceback (most recent call last):
  File "test-lua.py", line 8, in <module>
    lua.eval('function field((x) return x end')
  File "_lupa.pyx", line 168, in lupa._lupa.LuaRuntime.eval (lupa/_lupa.c:3399)
  File "_lupa.pyx", line 950, in lupa._lupa.run_lua (lupa/_lupa.c:13350)
lupa._lupa.LuaSyntaxError: error loading code: [string "<python>"]:1: '(' expected near 'field'

Am I doing something wrong or is this just not possible ?

Thanks a lot

PyPy support

The package compiles and installs 'correctly' for the "pypy" and "pypy3" target on travis ( see build ).

However when it is used the following exception occurs on "pypy":

Traceback (most recent call last):
...
  File "/home/travis/build/jayvdb/lupa/lupa/__init__.py", line 31, in <module>
    from lupa._lupa import *
ImportError: unable to load extension module '/home/travis/build/jayvdb/lupa/lupa/_lupa.pypy-25.so': /home/travis/build/jayvdb/lupa/lupa/_lupa.pypy-25.so: undefined symbol: PyByteArray_Check

This appears to have a hack which works around the problem, but maybe it only pushes the problem further along
https://mail.python.org/pipermail/pypy-dev/2014-August/012701.html

And on PyPy3:

Traceback (most recent call last):
...
  File "/home/travis/build/jayvdb/lupa/lupa/__init__.py", line 31, in <module>
    from lupa._lupa import *
ImportError: unable to load extension module '/home/travis/build/jayvdb/lupa/lupa/_lupa.pypy3-24.so': /home/travis/build/jayvdb/lupa/lupa/_lupa.pypy3-24.so: undefined symbol: PyBytes_FromFormat

which is https://bitbucket.org/pypy/pypy/issues/2022

If it isnt possible to workaround these issues easily, the setup.py could detect pypy and emit a useful error. README.rst currently does mention Cpython a few times prominently , but it could be more clear and say that it definitely doesnt work on PyPy and probably wont compile on other implementations of Python .

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.