{"id":897,"library":"lupa","title":"Lupa: Python Wrapper around Lua and LuaJIT","description":"Lupa is a Python library that seamlessly integrates the runtimes of Lua or LuaJIT2 into CPython. It enables Python developers to embed Lua code, call Lua functions from Python, and interact with Python objects from within Lua. Key features include separate Lua runtime states, Python coroutine wrappers for Lua coroutines, and robust iteration support between the two languages. Currently at version 2.6, Lupa is actively maintained, with releases focusing on cross-version compatibility and feature enhancements.","status":"active","version":"2.6","language":"python","source_language":"en","source_url":"https://github.com/scoder/lupa","tags":["lua","luajit","interoperability","scripting","embedding","ffi"],"install":[{"cmd":"pip install lupa","lang":"bash","label":"Basic installation"},{"cmd":"pip install lupa --global-option=\"--with-luajit\"","lang":"bash","label":"Install with LuaJIT (may require LuaJIT dev headers)"}],"dependencies":[{"reason":"Runtime dependency; Lupa wraps these runtimes. Development headers (e.g., `liblua5.x-dev` or LuaJIT source) may be needed at build time if pre-built wheels aren't available or for specific configurations.","package":"Lua 5.x or LuaJIT 2.x","optional":false},{"reason":"Used by setup.py to automatically locate Lua or LuaJIT development files on some systems.","package":"pkg-config","optional":true},{"reason":"Build dependency for Lupa itself, as it's implemented in Cython.","package":"Cython","optional":false}],"imports":[{"symbol":"LuaRuntime","correct":"from lupa import LuaRuntime"},{"symbol":"lupa","correct":"import lupa"}],"quickstart":{"code":"import os\nfrom lupa import LuaRuntime\n\n# Create a Lua runtime instance\nlua = LuaRuntime(unpack_returned_tuples=True)\n\n# Evaluate Lua code directly\nresult_eval = lua.eval('1 + 2')\nprint(f'Lua eval(\"1 + 2\"): {result_eval}')\n\n# Define a Lua function and call it from Python\nlua_add_func = lua.eval('function(x, y) return x + y end')\nresult_lua_call = lua_add_func(5, 7)\nprint(f'Called Lua function (5, 7): {result_lua_call}')\n\n# Pass a Python function into Lua and call it\ndef py_multiply(a, b):\n    return a * b\n\nlua.globals().py_multiply = py_multiply\nresult_python_call_from_lua = lua.eval('py_multiply(3, 4)')\nprint(f'Called Python function from Lua (3, 4): {result_python_call_from_lua}')\n\n# Access Python builtins from Lua\npython_str_from_lua = lua.eval('python.builtins.str(123)')\nprint(f'Python str from Lua: {python_str_from_lua}')","lang":"python","description":"This quickstart demonstrates basic interoperability: creating a Lua runtime, evaluating Lua code, defining and calling Lua functions from Python, and exposing Python functions and builtins to the Lua environment."},"warnings":[{"fix":"Adjust any code that parses Lua stack traces from Lupa's Python exceptions to account for the reversed order.","message":"In Lupa 2.0, Lua stack traces within Python exception messages were reversed to align with Python's stack trace order. If you relied on the previous ordering, your error parsing logic may break.","severity":"breaking","affected_versions":">=2.0"},{"fix":"Avoid using `len()` on Lua tables from Python if they are not strict sequences without `nil` values. Instead, iterate over the table if possible, or ensure Lua-side logic handles table lengths carefully.","message":"The behavior of Lua's `#` (length) operator, and thus Lupa's `len()` for wrapped Lua tables, can be unpredictable for tables containing `nil` values ('holes') or primarily acting as mappings. It generally stops at the first `nil` element, making it unsuitable for non-sequence tables. It's best not to rely on `len()` for mappings in Lupa.","severity":"gotcha","affected_versions":"All"},{"fix":"Be mindful of Python object structure when interacting from Lua. If precise control is needed, you might need to wrap Python objects in Lua with explicit accessors or ensure `__getitem__` is implemented (or not) as desired.","message":"When passing Python objects to Lua, Lupa employs a heuristic for indexing: if the Python object has a `__getitem__` method, it's preferred for Lua's `obj[x]` and `obj.x` operations. Otherwise, attribute access is used. This can lead to unexpected behavior if an object has both attributes and `__getitem__` and you expect a specific access method from Lua.","severity":"gotcha","affected_versions":"All"},{"fix":"For recursive data structure conversion, ensure you are using Lupa 2.1 or newer and explicitly pass `recursive=True` to the appropriate conversion methods.","message":"Prior to Lupa 2.1, recursive mapping of complex Python data structures (like nested lists/dicts) to Lua tables was not straightforward. Since Lupa 2.1, explicit `recursive=True` is needed in conversion functions (e.g., `LuaRuntime.table_from`) to enable deep conversion.","severity":"gotcha","affected_versions":"<2.1"},{"fix":"If experiencing issues with Lua binary modules, check your system's `dlopen` flags and ensure they are compatible. You might need to manually configure `sys.setdlopenflags` before importing `lupa` if the automatic setup is insufficient. Consult platform-specific documentation for `dlfcn` for correct flag values.","message":"Importing Lua binary modules (C modules) within a Lupa runtime typically requires CPython to enable global symbol visibility for shared libraries by calling `sys.setdlopenflags`. Lupa attempts to set these flags automatically, but it might fail on some platforms or configurations, leading to module import errors in Lua.","severity":"gotcha","affected_versions":"All"}],"env_vars":null,"search_vec":"'2.6':69 'activ':72 'around':4 'call':32 'code':31 'compat':81 'coroutin':53,57 'cpython':23 'cross':79 'cross-vers':78 'current':66 'develop':27 'emb':29 'embed':89 'enabl':25 'enhanc':84 'featur':46,83 'ffi':90 'focus':76 'function':34 'includ':47 'integr':15 'interact':38 'interoper':87 'iter':60 'key':45 'languag':65 'librari':12 'lua':5,19,30,33,44,49,56,85 'luajit':7,86 'luajit2':21 'lupa':1,8,70 'maintain':73 'object':41 'python':2,11,26,36,40,52 'releas':75 'robust':59 'runtim':17,50 'script':88 'seamless':14 'separ':48 'state':51 'support':61 'two':64 'version':68,80 'within':43 'wrapper':3,54","created_at":"2026-03-29T06:06:44.224741+00:00","updated_at":"2026-04-16T16:22:55.600402+00:00","problems":[{"fix":"Ensure `lupa` is installed by running `pip install lupa`. If you are developing and running from the source directory, install it via `python setup.py install` or ensure you are running your script from a directory *outside* the `lupa` source folder. If Cython is missing, install it first: `pip install cython`.","cause":"This error occurs when the `lupa` package or its underlying C extension modules are not correctly installed or Python cannot find them in its search path. It can also happen if you try to import `lupa` from within its source directory before it has been properly installed.","error":"ModuleNotFoundError: No module named 'lupa'"},{"fix":"To enable dynamic library loading, you need to set the appropriate `dlopen` flags for CPython before importing `lupa`. Add `import sys, os; sys.setdlopenflags(os.RTLD_NOW | os.RTLD_GLOBAL)` before `import lupa`. Some environments or `lupa` versions might attempt to set this automatically.","cause":"This error indicates that the Lua runtime embedded by Lupa is configured to prevent loading dynamic C modules (e.g., `.so` or `.dll` files), which is often a security measure or a build-time configuration.","error":"lupa.lua54.LuaError: error loading module '...' from file '...': dynamic libraries not enabled; check your Lua installation."},{"fix":"Verify that LuaJIT or Lua (the version Lupa was built against) is correctly installed and its shared libraries are discoverable by your system's linker (e.g., in `LD_LIBRARY_PATH` on Linux). Reinstalling `lupa` after ensuring LuaJIT/Lua development files are present might resolve the issue. If building manually, ensure the `LuaJIT` or `Lua` source is in the correct directory relative to `lupa`'s `setup.py` during compilation.","cause":"This error typically means that Lupa could not properly initialize the Lua or LuaJIT runtime. This is often due to missing shared libraries (like `libluajit-5.1.so` or `lua51.dll`), an incompatible LuaJIT/Lua version, or incorrect build configuration preventing Lupa from linking with the Lua runtime.","error":"lupa._lupa.LuaError: Failed to initialise Lua runtime"},{"fix":"Use the `lua.execute()` method for executing Lua statements or blocks of code. `lua.execute()` is designed to run arbitrary Lua code that doesn't necessarily return a value, or that may define functions or manipulate the Lua environment.\n```python\nimport lupa\nlua = lupa.LuaRuntime()\nlua.execute(\"\"\"for i=1,4 do print(i) end\"\"\")\n```","cause":"This specific Lua error occurs when you try to execute Lua *statements* (like a `for` loop, variable assignments, or function definitions) using the `lua.eval()` method. The `eval()` method expects a Lua *expression* that returns a single value.","error":"error loading code: [string \"<python>\"]:1: unexpected symbol near 'for'"}],"ecosystem":"pypi","meta_description":null,"install_score":100,"quickstart_score":null,"quickstart_tag":null,"pypi_latest":"2.8","cli_name":"","cli_version":null,"type":"library","homepage":null,"github":"https://github.com/scoder/lupa","docs":null,"changelog":null,"pypi":"https://pypi.org/project/lupa/","npm":null,"openapi_spec":null,"status_page":null,"smithery":null,"categories":["serialization"],"base_url":null,"auth_type":null,"provenance":{"verified_status":"passing","verified_at":"2026-06-28","last_verified":"2026-06-28","next_check":"2026-07-28","install_tag":"verified"}}