aboutsummaryrefslogtreecommitdiffstats
path: root/src/pyrecwrap.jl
blob: 9cfc5e4a5301fda8d31979a50e6f115351694dce (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

# 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) ||
                pyisinstance(mo, PyCall.@pyglobalobj :PyType_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