aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbnewbold <bnewbold@robocracy.org>2016-04-04 17:32:24 -0400
committerbnewbold <bnewbold@robocracy.org>2016-04-04 17:32:24 -0400
commit6ca0769a2f82c7c7d6a34fda2571b9ae15950842 (patch)
tree56e32d7ad1f1054190c30a4459b14464547daa1e
parentb20c077ec646fdc2612e31f3305e8cfcedae207b (diff)
downloadPyX.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.jl56
-rw-r--r--test/runtests.jl5
-rw-r--r--test/test_pyrecwrap.jl4
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
+