diff options
author | bnewbold <bnewbold@robocracy.org> | 2016-04-04 17:32:24 -0400 |
---|---|---|
committer | bnewbold <bnewbold@robocracy.org> | 2016-04-04 17:32:24 -0400 |
commit | 6ca0769a2f82c7c7d6a34fda2571b9ae15950842 (patch) | |
tree | 56e32d7ad1f1054190c30a4459b14464547daa1e | |
parent | b20c077ec646fdc2612e31f3305e8cfcedae207b (diff) | |
download | PyX.jl-6ca0769a2f82c7c7d6a34fda2571b9ae15950842.tar.gz PyX.jl-6ca0769a2f82c7c7d6a34fda2571b9ae15950842.zip |
add recursive version of PyCall's pywrap
Will be used to have deeply nested Julia Modules for deeply nested
Python modules Eg, os.path.genericpath.* in python becomes
os.path.genericpath.* in Julia.
This version only works with modules, not Python classes.
Thanks to James Porter at the Recurse Center for helping with this!
-rw-r--r-- | src/pyrecwrap.jl | 56 | ||||
-rw-r--r-- | test/runtests.jl | 5 | ||||
-rw-r--r-- | test/test_pyrecwrap.jl | 4 |
3 files changed, 65 insertions, 0 deletions
diff --git a/src/pyrecwrap.jl b/src/pyrecwrap.jl new file mode 100644 index 0000000..7b31e56 --- /dev/null +++ b/src/pyrecwrap.jl @@ -0,0 +1,56 @@ + +# This file contains a recursive version of the pywrap() function from PyCall: +# it will generate nested Julia Modules for nested Python modules. +# This extends to recursive or infinitely looping modules. For example, the +# Python "os" module goes infinitely deep: +# +# os.path.genericpath.os.path.genericpath.os.path.genericpath.os.path... +# +# It was created for PyX.jl because PyX has deeply nested modules (eg, +# pyx.graph.graphxy), which are not handled by the generic pywrap() from +# PyCall. + +_pyrecwrap_cache = Dict{PyObject,Module}() + +function pyrecwrap(o::PyObject, mname::Symbol=:__anon__) + members = convert(Vector{Tuple{AbstractString,PyObject}}, + pycall(PyCall.inspect["getmembers"], PyObject, o)) + if PyCall.pyversion >= v"3" + # Blacklist the "inspect" module under Python3; fail if called on it, and filter it + # out of recursive imports. + # See also: https://github.com/stevengj/PyCall.jl/issues/252 + if o == PyCall.inspect + error("Wrapping the 'inspect' module under Python3 causes a hang") + end + filter!(m -> !(m[1] == PyCall.inspect), members) + end + filter!(m -> !(m[1] in PyCall.reserved), members) + m = Module(mname, false) + # Preload module cache with this (so far empty) module + _pyrecwrap_cache[o] = m + consts = Expr[] + for (ms, mo) in members # ms is Symbol, mo is PyObject) + if pyisinstance(mo, PyCall.@pyglobalobj :PyModule_Type) + if mo == PyCall.inspect + continue + end + # Before recursing, check if we've seen this python module before + if !haskey(_pyrecwrap_cache, mo) + _pyrecwrap_cache[mo] = pyrecwrap(mo) + end + mm = _pyrecwrap_cache[mo] + push!(consts, Expr(:const, Expr(:(=), symbol(ms), mm))) + else + push!(consts, Expr(:const, Expr(:(=), symbol(ms), convert(PyAny, mo)))) + end + end + exports = try + convert(Vector{Symbol}, o["__all__"]) + catch + [symbol(x[1]) for x in filter(x -> x[1][1] != '_', members)] + end + eval(m, Expr(:toplevel, consts..., :(pymember(s) = $(getindex)($(o), s)), + Expr(:export, exports...))) + m +end + diff --git a/test/runtests.jl b/test/runtests.jl index d0bb3c7..c4beb52 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,8 +3,13 @@ println("Importing libs...") include("../src/PyX.jl") using PyX using LaTeXStrings +using PyCall using Base.Test +##### Helpers +println("Testing helpers...") +include("test_pyrecwrap.jl") + ##### Setup Common Objects println("Creating test objects...") include("test_objects.jl") diff --git a/test/test_pyrecwrap.jl b/test/test_pyrecwrap.jl new file mode 100644 index 0000000..cef3f08 --- /dev/null +++ b/test/test_pyrecwrap.jl @@ -0,0 +1,4 @@ + +os_test = PyX.pyrecwrap(pyimport("os")) +@test os_test.path.genericpath.os.path.genericpath.os.path.genericpath != nothing + |