Skip to content

Commit 6ea28a7

Browse files
author
Christopher Doris
committed
Merge branch 'main' into gc
2 parents 627bba8 + 3ca4cd2 commit 6ea28a7

24 files changed

+529
-41
lines changed

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "PythonCall"
22
uuid = "6099a3de-0909-46bc-b1f4-468b9a2dfc0d"
33
authors = ["Christopher Doris <github.com/cjdoris>"]
4-
version = "0.9.3"
4+
version = "0.9.4"
55

66
[deps]
77
CondaPkg = "992eb4ea-22a4-4c89-a5bb-47a3300528ab"
@@ -16,7 +16,7 @@ Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
1616
UnsafePointers = "e17b2a0c-0bdf-430a-bd0c-3a23cae4ff39"
1717

1818
[compat]
19-
CondaPkg = "0.2.11"
19+
CondaPkg = "0.2.12"
2020
MacroTools = "0.5"
2121
Requires = "1"
2222
Tables = "1"

docs/src/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ Bringing [**Python®**](https://www.python.org/) and [**Julia**](https://juliala
55
- Simple syntax, so the Python code looks like Python and the Julia code looks like Julia.
66
- Intuitive and flexible conversions between Julia and Python: anything can be converted, you are in control.
77
- Fast non-copying conversion of numeric arrays in either direction: modify Python arrays (e.g. `bytes`, `array.array`, `numpy.ndarray`) from Julia or Julia arrays from Python.
8-
- Helpful wrappers: interpret Python sequences, dictionaries, arrays, dataframes and IO streams as their Julia couterparts, and vice versa.
8+
- Helpful wrappers: interpret Python sequences, dictionaries, arrays, dataframes and IO streams as their Julia counterparts, and vice versa.
99
- Beautiful stack-traces.
1010
- Works anywhere: tested on Windows, MacOS and Linux, 32- and 64-bit, Julia Julia 1.6.1 upwards and Python 3.7 upwards.

docs/src/releasenotes.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
# Release Notes
22

33
## Unreleased
4+
* Experimental new function `juliacall.interactive()` allows the Julia async event loop to
5+
run in the background of the Python REPL.
6+
* Experimental new IPython extension `juliacall.ipython` providing the `%jl` and `%%jl`
7+
magics for executing Julia code.
8+
* Experimental new module `juliacall.importer` allowing you to write Python modules in
9+
Julia.
10+
* Bug fixes.
11+
12+
## 0.9.4 (2022-07-26)
413
* Bug fixes.
514

615
## 0.9.3 (2022-07-02)

pysrc/juliacall/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# This module gets modified by PythonCall when it is loaded, e.g. to include Core, Base
22
# and Main modules.
33

4-
__version__ = '0.9.3'
4+
__version__ = '0.9.4'
55

66
_newmodule = None
77

@@ -21,6 +21,13 @@ def convert(T, x):
2121
_convert = PythonCall.seval("pyjlcallback((T,x)->pyjl(pyconvert(pyjlvalue(T)::Type,x)))")
2222
return _convert(T, x)
2323

24+
def interactive(enable=True):
25+
"Allow the Julia event loop to run in the background of the Python REPL."
26+
if enable:
27+
PythonCall._set_python_input_hook()
28+
else:
29+
PythonCall._unset_python_input_hook()
30+
2431
class JuliaError(Exception):
2532
"An error arising in Julia code."
2633
def __init__(self, exception, backtrace=None):
@@ -122,6 +129,7 @@ def args_from_config():
122129
CONFIG['opt_sysimage'] = sysimg = path_option('sysimage', check_exists=True)[0]
123130
CONFIG['opt_threads'] = int_option('threads', accept_auto=True)[0]
124131
CONFIG['opt_warn_overwrite'] = choice('warn_overwrite', ['yes', 'no'])[0]
132+
CONFIG['opt_handle_signals'] = 'no'
125133

126134
# Stop if we already initialised
127135
if CONFIG['inited']:
@@ -178,6 +186,7 @@ def jlstr(x):
178186
return 'raw"' + x.replace('"', '\\"').replace('\\', '\\\\') + '"'
179187
script = '''
180188
try
189+
Base.require(Main, :CompilerSupportLibraries_jll)
181190
import Pkg
182191
ENV["JULIA_PYTHONCALL_LIBPTR"] = {}
183192
ENV["JULIA_PYTHONCALL_EXE"] = {}

pysrc/juliacall/importer.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import base64
2+
import io
3+
import os
4+
import sys
5+
6+
from . import newmodule, Base
7+
from importlib.machinery import ModuleSpec, SourceFileLoader
8+
9+
class Finder:
10+
def __init__(self, jlext='.jl', pyext='.py'):
11+
self.jlext = jlext
12+
self.pyext = pyext
13+
14+
def find_spec(self, fullname, path, target=None):
15+
if path is None:
16+
path = sys.path
17+
if '.' in fullname:
18+
return
19+
name = fullname
20+
else:
21+
name = fullname.split('.')[-1]
22+
for root in path:
23+
jlfile = os.path.join(root, name + self.jlext)
24+
if os.path.isfile(jlfile):
25+
jlfile = os.path.realpath(jlfile)
26+
pyfile = os.path.join(root, name + self.pyext)
27+
gen_file(jlfile, pyfile)
28+
return ModuleSpec(fullname, SourceFileLoader(fullname, pyfile), origin=jlfile)
29+
30+
def install(**kw):
31+
finder = Finder(**kw)
32+
sys.meta_path.insert(0, finder)
33+
return finder
34+
35+
def uninstall(finder):
36+
sys.meta_path.remove(finder)
37+
38+
def gen_code(jl):
39+
buf = io.StringIO()
40+
pr = lambda x: print(x, file=buf)
41+
jl2 = jl.replace('\\', '\\\\').replace("'", "\\'")
42+
pr('# This file was automatically generated by juliacall.importer')
43+
pr('import juliacall.importer')
44+
pr('juliacall.importer.exec_module(__name__,')
45+
pr("'''"+jl2+"''')")
46+
return buf.getvalue()
47+
48+
def gen_file(jl, py):
49+
with open(jl, encoding='utf-8') as fp:
50+
jlcode = fp.read()
51+
pycode = gen_code(jlcode)
52+
with open(py, 'w', encoding='utf-8') as fp:
53+
fp.write(pycode)
54+
55+
def exec_module(name, code):
56+
pymod = sys.modules[name]
57+
jlmod = newmodule(name)
58+
jlmod.seval('begin\n' + code + '\nend')
59+
delattr(pymod, 'juliacall')
60+
setattr(pymod, '__jl_code__', code)
61+
setattr(pymod, '__jl_module__', jlmod)
62+
ks = [str(k) for k in Base.names(jlmod)]
63+
ks = [k for k in ks if k != name]
64+
if not ks:
65+
ks = [str(k) for k in Base.names(jlmod, all=True)]
66+
ks = [k for k in ks if not (k == name or k == 'include' or k == 'eval' or k.startswith('_') or '#' in k)]
67+
setattr(pymod, '__all__', ks)
68+
setattr(pymod, '__doc__', str(Base.Docs.doc(jlmod)))
69+
for k in ks:
70+
setattr(pymod, k, getattr(jlmod, k))

pysrc/juliacall/ipython.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from IPython.core.magic import Magics, magics_class, line_cell_magic
2+
from . import Main, Base, PythonCall
3+
4+
@magics_class
5+
class JuliaMagics(Magics):
6+
7+
@line_cell_magic
8+
def julia(self, line, cell=None):
9+
code = line if cell is None else cell
10+
ans = Main.seval('begin\n' + code + '\nend')
11+
Base.flush(Base.stdout)
12+
Base.flush(Base.stderr)
13+
if not code.strip().endswith(';'):
14+
return ans
15+
16+
def load_ipython_extension(ip):
17+
# register magics
18+
ip.register_magics(JuliaMagics(ip))
19+
# redirect stdout/stderr
20+
PythonCall.seval("""begin
21+
const _redirected_stdout = redirect_stdout()
22+
const _redirected_stderr = redirect_stderr()
23+
const _py_stdout = PyIO(pyimport("sys" => "stdout"), buflen=1)
24+
const _py_stderr = PyIO(pyimport("sys" => "stderr"), buflen=1)
25+
const _redirect_stdout_task = @async write($_py_stdout, $_redirected_stdout)
26+
const _redirect_stderr_task = @async write($_py_stderr, $_redirected_stderr)
27+
end""")

pysrc/juliacall/juliapkg-dev.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"packages": {
44
"PythonCall": {
55
"uuid": "6099a3de-0909-46bc-b1f4-468b9a2dfc0d",
6-
"version": "0.9.3",
6+
"version": "=0.9.4",
77
"path": "../..",
88
"dev": true
99
}

pysrc/juliacall/juliapkg.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"packages": {
44
"PythonCall": {
55
"uuid": "6099a3de-0909-46bc-b1f4-468b9a2dfc0d",
6-
"version": "0.9.3"
6+
"version": "=0.9.4"
77
}
88
}
99
}

setup.cfg

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = juliacall
3-
version = 0.9.3
3+
version = 0.9.4
44
description = Julia and Python in seamless harmony
55
long_description = file: README.md
66
long_description_content_type = text/markdown
@@ -15,9 +15,9 @@ zip_safe = False
1515
package_dir =
1616
=pysrc
1717
packages = juliacall
18-
python_requires = ~=3.5
18+
python_requires = ~=3.7
1919
install_requires =
20-
juliapkg ~=0.1.0
20+
juliapkg ~=0.1.8
2121

2222
[options.package_data]
2323
juliacall =

src/Py.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,8 @@ Base.powermod(x::Number, y::Py, z::Number) = pypow(x, y, z)
445445
Base.powermod(x::Py, y::Number, z::Number) = pypow(x, y, z)
446446

447447
# documentation
448-
function Base.Docs.getdoc(x::Py, @nospecialize(sig))
448+
function Base.Docs.getdoc(x::Py, @nospecialize(sig)=Union{})
449+
pyisnull(x) && return nothing
449450
parts = []
450451
inspect = pyimport("inspect")
451452
# head line

0 commit comments

Comments
 (0)