Skip to content

Commit 5b8d634

Browse files
author
Christopher Doris
committed
adds corresponding python module
1 parent 73fce52 commit 5b8d634

File tree

14 files changed

+321
-194
lines changed

14 files changed

+321
-194
lines changed

juliapy/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__pycache__

juliapy/julia/__init__.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
_CONFIG = dict()
2+
3+
def _init_():
4+
import os, os.path, sys, ctypes as c, types, shutil, subprocess
5+
libpath = os.environ.get('JULIAPY_LIB')
6+
if libpath is None:
7+
exepath = os.environ.get('JULIAPY_EXE')
8+
if exepath is None:
9+
exepath = shutil.which('julia')
10+
if exepath is None:
11+
raise Exception('Cannot find Julia. Ensure it is in your PATH, or set JULIAPY_EXE to its path.')
12+
else:
13+
if not os.path.isfile(exepath):
14+
raise Exception('JULIAPY_EXE=%s does not exist' % repr(exepath))
15+
_CONFIG['exepath'] = exepath
16+
libpath = subprocess.run([exepath, '-e', 'import Libdl; print(abspath(Libdl.dlpath("libjulia")))'], stdout=(subprocess.PIPE)).stdout.decode('utf8')
17+
else:
18+
if not os.path.isfile(libpath):
19+
raise Exception('JULIAPY_LIB=%s does not exist' % repr(libpath))
20+
_CONFIG['libpath'] = libpath
21+
try:
22+
d = os.getcwd()
23+
os.chdir(os.path.dirname(libpath))
24+
lib = c.CDLL(libpath)
25+
finally:
26+
os.chdir(d)
27+
28+
_CONFIG['lib'] = lib
29+
lib.jl_init__threading.argtypes = []
30+
lib.jl_init__threading.restype = None
31+
lib.jl_init__threading()
32+
lib.jl_eval_string.argtypes = [c.c_char_p]
33+
lib.jl_eval_string.restype = c.c_void_p
34+
res = lib.jl_eval_string(
35+
'''
36+
ENV["PYTHONJL_LIBPTR"] = "{}"
37+
import Python
38+
Python.with_gil() do
39+
Python.pyimport("sys").modules["julia"].Main = Python.pyjl(Main)
40+
end
41+
'''.format(c.pythonapi._handle).encode('utf8'))
42+
if res is None:
43+
raise Exception('Python.jl did not start properly. Ensure that the Python package is installed in Julia.')
44+
45+
class Wrapper(types.ModuleType):
46+
47+
def __getattr__(self, k):
48+
return getattr(self.Main, k)
49+
50+
def __dir__(self):
51+
return super().__dir__() + self.Main.__dir__()
52+
53+
sys.modules['julia'].__class__ = Wrapper
54+
55+
_init_()
56+
del _init_
57+
58+
Core = Main.Core
59+
Base = Main.Base
60+
Python = Main.Python
61+
62+
def _import(*names):
63+
Main.eval(Base.Meta.parse('import ' + ', '.join(names)))

juliapy/setup.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import setuptools
2+
3+
setuptools.setup(
4+
name="julia",
5+
version="0.1.0",
6+
author="Christopher Rowley",
7+
# author_email="author@example.com",
8+
description="Julia-Python interoperability",
9+
# long_description=long_description,
10+
# long_description_content_type="text/markdown",
11+
# url="https://github.com/pypa/sampleproject",
12+
packages=["julia"],
13+
classifiers=[
14+
"Programming Language :: Python :: 3",
15+
"License :: OSI Approved :: MIT License",
16+
"Operating System :: OS Independent",
17+
],
18+
python_requires='>=3.3',
19+
)

src/PyBuffer.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ mutable struct PyBuffer
2626
b = new(info)
2727
finalizer(b) do b
2828
if CONFIG.isinitialized
29-
s = gil_on()
30-
err = C.PyBuffer_Release(pointer(b.info))
31-
s || gil_off()
29+
err = with_gil() do
30+
C.PyBuffer_Release(pointer(b.info))
31+
end
3232
check(err)
3333
end
3434
end

src/PyObjectArray.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ mutable struct PyObjectArray{N} <: AbstractArray{PyObject, N}
44
x = new{N}(fill(CPyPtr(C_NULL), dims))
55
finalizer(x) do x
66
if CONFIG.isinitialized
7-
s = gil_on()
8-
for ptr in x.ptrs
9-
C.Py_DecRef(ptr)
7+
with_gil() do
8+
for ptr in x.ptrs
9+
C.Py_DecRef(ptr)
10+
end
1011
end
11-
s || gil_off()
1212
end
1313
end
1414
end

src/Python.jl

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,8 @@ include("utils.jl")
2727
pyprogname_w :: Vector{Cwchar_t} = []
2828
"True if this is stackless Python."
2929
isstackless :: Bool = false
30-
"True if the Python library was already loaded."
31-
preloaded :: Bool = false
32-
"True if the Python interpreter was already initialized."
33-
preinitialized :: Bool = false
30+
"""True if Julia is embedded into Python (indicated by ENV["PYTHONJL_LIBPTR"] being set)."""
31+
isembedded :: Bool = false
3432
"True if the Python interpreter is currently initialized."
3533
isinitialized :: Bool = false
3634
"The running Python version."
@@ -47,8 +45,6 @@ include("utils.jl")
4745
sysautolasttraceback :: Bool = true
4846
"True if the Python input hook is currently running."
4947
inputhookrunning :: Bool = false
50-
"If NULL, we have the GIL. If non-NULL, this is the saved thread state."
51-
gilstate :: Ptr{Cvoid} = C_NULL
5248
end
5349
Base.show(io::IO, ::MIME"text/plain", c::Config) =
5450
for k in fieldnames(Config)

src/builtins.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,6 @@ for n in [
5555
]
5656
j = Symbol(:py, lowercase(string(n)))
5757
p = QuoteNode(Symbol(:PyExc_, n))
58-
@eval const $j = pylazyobject(() -> pyborrowedobject(unsafe_load(Ptr{CPyPtr}(C.@pyglobal($p)))))
58+
@eval const $j = pylazyobject(() -> pyborrowedobject(unsafe_load(Ptr{CPyPtr}(C.pyglobal($p)))))
5959
@eval export $j
6060
end

src/convert.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,18 +62,22 @@ Base.convert(::Type{Any}, o::PyObject) = o
6262
### SPECIAL CONVERSIONS
6363

6464
@generated _eltype(o) = try eltype(o); catch; missing; end
65+
_eltype(o::Type) = missing
6566

6667
@generated _keytype(o) = try keytype(o); catch; missing; end
6768
_keytype(o::Base.RefValue) = Tuple{}
6869
_keytype(o::NamedTuple) = Union{Symbol,Int}
6970
_keytype(o::Tuple) = Int
71+
_keytype(o::Type) = missing
7072

7173
@generated _valtype(o, k...) = try valtype(o); catch; missing; end
7274
_valtype(o::NamedTuple, k::Int) = fieldtype(typeof(o), k)
7375
_valtype(o::NamedTuple, k::Symbol) = fieldtype(typeof(o), k)
7476
_valtype(o::Tuple, k::Int) = fieldtype(typeof(o), k)
77+
_valtype(o::Type) = missing
7578

7679
hasmultiindex(o) = true
80+
hasmultiindex(o::Type) = true
7781
hasmultiindex(o::AbstractDict) = false
7882
hasmultiindex(o::NamedTuple) = false
7983
hasmultiindex(o::Tuple) = false

src/cpython.jl

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ module CPython
55
using Base: @kwdef
66
using UnsafePointers: UnsafePtr
77

8+
@enum PyGILState_STATE::Cint PyGILState_LOCKED=0 PyGILState_UNLOCKED=1
9+
810
const Py_LT = Cint(0)
911
const Py_LE = Cint(1)
1012
const Py_EQ = Cint(2)
@@ -301,9 +303,7 @@ module CPython
301303

302304
const PyTypePtr = Ptr{PyTypeObject}
303305

304-
macro pyglobal(name)
305-
:(CONFIG.preloaded ? cglobal($(esc(name))) : dlsym(CONFIG.libptr, $(esc(name))))
306-
end
306+
pyglobal(name) = dlsym(CONFIG.libptr, name)
307307

308308
macro cdef(name, rettype, argtypes)
309309
name isa QuoteNode && name.value isa Symbol || error("name must be a symbol, got $name")
@@ -320,7 +320,7 @@ module CPython
320320
function $jname($(args...))
321321
ptr = $refname[]
322322
if ptr == C_NULL
323-
ptr = $refname[] = @pyglobal($name)
323+
ptr = $refname[] = pyglobal($name)
324324
end
325325
ccall(ptr, $rettype, $argtypes, $(args...))
326326
end
@@ -344,6 +344,9 @@ module CPython
344344
@cdef :PyEval_SaveThread Ptr{Cvoid} ()
345345
@cdef :PyEval_RestoreThread Cvoid (Ptr{Cvoid},)
346346

347+
@cdef :PyGILState_Ensure PyGILState_STATE ()
348+
@cdef :PyGILState_Release Cvoid (PyGILState_STATE,)
349+
347350
@cdef :PyImport_ImportModule PyPtr (Cstring,)
348351
@cdef :PyImport_Import PyPtr (PyPtr,)
349352

@@ -480,7 +483,7 @@ module CPython
480483
function PyObject_GetBuffer(o, b, flags)
481484
p = UnsafePtr{PyTypeObject}(Py_Type(o)).as_buffer[]
482485
if p == C_NULL || p.get[] == C_NULL
483-
PyErr_SetString(unsafe_load(Ptr{PyPtr}(@pyglobal(:PyExc_TypeError))), "a bytes-like object is required, not '$(String(UnsafePtr{PyTypeObject}(Py_Type(o)).name[]))'")
486+
PyErr_SetString(unsafe_load(Ptr{PyPtr}(pyglobal(:PyExc_TypeError))), "a bytes-like object is required, not '$(String(UnsafePtr{PyTypeObject}(Py_Type(o)).name[]))'")
484487
return Cint(-1)
485488
end
486489
ccall(p.get[!], Cint, (PyPtr, Ptr{Py_buffer}, Cint), o, b, flags)
@@ -500,7 +503,7 @@ module CPython
500503
end
501504

502505
function PyOS_RunInputHook()
503-
hook = unsafe_load(Ptr{Ptr{Cvoid}}(@pyglobal(:PyOS_InputHook)))
506+
hook = unsafe_load(Ptr{Ptr{Cvoid}}(pyglobal(:PyOS_InputHook)))
504507
hook == C_NULL || ccall(hook, Cint, ())
505508
nothing
506509
end

src/gil.jl

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,12 @@
1-
"""
2-
gil_is_on()
3-
4-
True if we own the GIL. If so, we can safely interact with Python.
5-
"""
6-
gil_is_on() = CONFIG.gilstate == C_NULL
7-
8-
"""
9-
gil_on()
10-
11-
Ensure the GIL is on, so that we can safely interact with Python. Return true if the GIL was already on.
12-
"""
13-
gil_on() =
14-
if gil_is_on()
15-
true
1+
function with_gil(f)
2+
if CONFIG.isembedded
3+
g = C.PyGILState_Ensure()
4+
try
5+
f()
6+
finally
7+
C.PyGILState_Release(g)
8+
end
169
else
17-
C.PyEval_RestoreThread(CONFIG.gilstate)
18-
CONFIG.gilstate = C_NULL
19-
false
20-
end
21-
22-
"""
23-
gil_off()
24-
25-
Ensure the GIL is off. Return true if the GIL was on.
26-
27-
After calling this, you must not call any Python functions until `gil_on()` is called. (It is OK if the garbage collector frees Python objects with the GIL off: the finalizer will temporarily acquire it.)
28-
"""
29-
gil_off() =
30-
if gil_is_on()
31-
CONFIG.gilstate = C.PyEval_SaveThread()
32-
true
33-
else
34-
false
10+
f()
3511
end
12+
end

0 commit comments

Comments
 (0)