-
Notifications
You must be signed in to change notification settings - Fork 67
/
Copy pathimporter.py
99 lines (82 loc) · 3.2 KB
/
importer.py
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
"""Experimental module for loading Julia files as Python modules.
Being experimental, it does not form part of the JuliaCall API. It may be changed or removed
in any release.
Basic usage is:
import juliacall.importer
juliacall.importer.install()
And now you can import any Julia file as Python module. That is, if 'foo.jl' is in your
Python path, then 'import foo' will work.
By default, all objects at the top level of the file whose name does not start with '_' are
exposed. Alternatively, you may explicitly 'export' objects.
The importer is intended for development use. To release a package, use 'gen_file' on each
Julia module file to generate a corresponding Python file and only include the Python files
in the release.
"""
import io
import os
import sys
from importlib.machinery import ModuleSpec, SourceFileLoader
from . import Base, newmodule
class Finder:
def __init__(self, jlext='.jl', pyext='.py'):
self.jlext = jlext
self.pyext = pyext
def find_spec(self, fullname, path, target=None):
if path is None:
path = sys.path
if '.' in fullname:
return
name = fullname
else:
name = fullname.split('.')[-1]
for root in path:
jlfile = os.path.join(root, name + self.jlext)
if os.path.isfile(jlfile):
jlfile = os.path.realpath(jlfile)
pyfile = os.path.join(root, name + self.pyext)
gen_file(jlfile, pyfile)
return ModuleSpec(fullname, SourceFileLoader(fullname, pyfile), origin=jlfile)
def install(**kw):
"""Install the Julia importer.
After calling this, files ending '.jl' in your Python path may be imported as Python
modules.
The return value may be passed to 'uninstall'.
"""
finder = Finder(**kw)
sys.meta_path.insert(0, finder)
return finder
def uninstall(finder):
"""Uninstall the Julia importer."""
sys.meta_path.remove(finder)
def gen_code(jl):
buf = io.StringIO()
pr = lambda x: print(x, file=buf)
jl2 = jl.replace('\\', '\\\\').replace("'", "\\'")
pr('# This file was automatically generated by juliacall.importer')
pr('import juliacall.importer')
pr('juliacall.importer.exec_module(__name__,')
pr("'''"+jl2+"''')")
return buf.getvalue()
def gen_file(jl, py):
"""Convert a Julia module file to a Python module file."""
with open(jl, encoding='utf-8') as fp:
jlcode = fp.read()
pycode = gen_code(jlcode)
with open(py, 'w', encoding='utf-8') as fp:
fp.write(pycode)
def exec_module(name, code):
pymod = sys.modules[name]
jlmod = newmodule(name)
jlmod.seval('begin\n' + code + '\nend')
delattr(pymod, 'juliacall')
setattr(pymod, '__jl_code__', code)
setattr(pymod, '__jl_module__', jlmod)
ks = [str(k) for k in Base.names(jlmod)]
ks = [k for k in ks if k != name]
if not ks:
ks = [str(k) for k in Base.names(jlmod, all=True)]
ks = [k for k in ks if not (k == name or k == 'include' or k == 'eval' or k.startswith('_') or '#' in k)]
setattr(pymod, '__all__', ks)
setattr(pymod, '__doc__', str(Base.Docs.doc(jlmod)))
for k in ks:
setattr(pymod, k, getattr(jlmod, k))