Skip to content

Commit

Permalink
Dont try to initialize frame for python 3.11 and later
Browse files Browse the repository at this point in the history
  • Loading branch information
yglukhov committed May 4, 2023
1 parent cdcc5e4 commit eb0bb29
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 45 deletions.
4 changes: 1 addition & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
nim-channel: [stable, devel]
python-version: ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10"]
python-version: ["2.7", "3.7", "3.8", "3.9", "3.10", "3.11"]
exclude:
- os: windows-latest
python-version: "2.7" # DLL not found, specific to CI?
- os: windows-latest
python-version: "3.10" # FIXME: eval doesn't work

name: ${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.nim-channel }}
runs-on: ${{ matrix.os }}
Expand Down
10 changes: 8 additions & 2 deletions nimpy.nim
Original file line number Diff line number Diff line change
Expand Up @@ -941,11 +941,15 @@ proc pyBuiltins*(): PyObject =

proc pyGlobals*(): PyObject =
initPyLibIfNeeded()
newPyObject(pyLib.PyEval_GetGlobals())
let r = pyLib.PyEval_GetGlobals()
if not r.isNil:
result = newPyObject(r)

proc pyLocals*(): PyObject =
initPyLibIfNeeded()
newPyObject(pyLib.PyEval_GetLocals())
let r = pyLib.PyEval_GetLocals()
if not r.isNil:
result = newPyObject(r)

proc dir*(v: PyObject): seq[string] =
let lst = pyLib.PyObject_Dir(v.rawPyObj)
Expand Down Expand Up @@ -1002,6 +1006,8 @@ macro toPyDictRaw(a: untyped): PPyObject =
template toPyDict*(a: untyped): PyObject =
newPyObjectConsumingRef(toPyDictRaw(a))

proc pyDict*(): PyObject =
newPyObjectConsumingRef(toPyDictRaw(()))

################################################################################
################################################################################
Expand Down
26 changes: 16 additions & 10 deletions nimpy/py_lib.nim
Original file line number Diff line number Diff line change
Expand Up @@ -488,10 +488,13 @@ proc initPyThreadFrame() =
echo "Testing libpython: ", nimpyTestLibPython
pyInitLibPath(nimpyTestLibPython)

# https://stackoverflow.com/questions/42974139/valueerror-call-stack-is-not-deep-enough-when-calling-ipython-embed-method
# needed for eval and stuff like pandas.query() otherwise crash (call stack is not deep enough)
if unlikely pyLib.isNil:
initPyLib(pythonLibHandleFromExternalLib())

# https://stackoverflow.com/questions/42974139/valueerror-call-stack-is-not-deep-enough-when-calling-ipython-embed-method
# needed for eval and stuff like pandas.query() otherwise crash (call stack is not deep enough)
#
# XXX Unfortunately this doesn't work with python 3.11 and later.
pyThreadFrameInited = true

let
Expand All @@ -502,7 +505,8 @@ proc initPyThreadFrame() =
of 2:
if not cast[ptr PyThreadState2](pyThread).frame.isNil: return
of 3:
if not cast[ptr PyThreadState3](pyThread).frame.isNil: return
if pyLib.pythonVersion < (3, 11, 0):
if not cast[ptr PyThreadState3](pyThread).frame.isNil: return
else:
doAssert(false, "unreachable")

Expand All @@ -513,17 +517,19 @@ proc initPyThreadFrame() =
pyFrameNew = cast[proc(p1, p2, p3, p4: pointer): pointer {.pyfunc.}](pyLib.module.symAddr("PyFrame_New"))

if not pyImportAddModule.isNil and not pyModuleGetDict.isNil and not pyCodeNewEmpty.isNil and not pyFrameNew.isNil:
let
main_module = pyImportAddModule("__main__")
main_dict = pyModuleGetDict(main_module)
code_object = pyCodeNewEmpty("null.py", "f", 0)
root_frame = pyFrameNew(pyThread, code_object, main_dict, main_dict)
proc makeRootFrame(): pointer =
let
main_module = pyImportAddModule("__main__")
main_dict = pyModuleGetDict(main_module)
code_object = pyCodeNewEmpty("null.py", "f", 0)
pyFrameNew(pyThread, code_object, main_dict, main_dict)

case pyLib.pythonVersion.major
of 2:
cast[ptr PyThreadState2](pyThread).frame = root_frame
cast[ptr PyThreadState2](pyThread).frame = makeRootFrame()
of 3:
cast[ptr PyThreadState3](pyThread).frame = root_frame
if pyLib.pythonVersion < (3, 11, 0):
cast[ptr PyThreadState3](pyThread).frame = makeRootFrame()
else:
doAssert(false, "unreachable")

Expand Down
62 changes: 32 additions & 30 deletions tests/tpyfromnim.nim
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ proc test*() {.gcsafe.} =

block: # eval
let py = pyBuiltinsModule()
doAssert(py.eval("3+3").to(int) == 6)
doAssert(py.eval(""" "hello" * 2 """).to(string) == "hellohello")
doAssert(py.eval("3+3", pyDict(), pyDict()).to(int) == 6)
doAssert(py.eval(""" "hello" * 2 """, pyDict(), pyDict()).to(string) == "hellohello")

block:
var ints = newSeq[int]()
Expand Down Expand Up @@ -244,43 +244,45 @@ proc test*() {.gcsafe.} =
doAssert(pfn.test_nil_marshalling(myNil).to(bool))

block: # Kinda subclassing python objects in nim and calling super
if pyImport("sys").version_info.major.to(int) >= 3: # Only test with python 3
let py = pyBuiltinsModule()
let py = pyBuiltinsModule()
let locals = toPyDict(()) # Create empty dict

# Let's say there's this python code:
discard py.exec("""
class Foo:
def overrideMe(self):
return 2
# Let's say there's this python code:
discard py.exec("""
class Foo:
def overrideMe(self):
return 2
def useFoo(foo):
return foo.overrideMe()
""".dedent(), pyDict(), locals)

def useFoo(foo):
return foo.overrideMe()
""".dedent())
let fooClass = locals["Foo"]

# Create a subclass of Foo in Nim:
proc createFooSubclassInstance(): PyObject =
# The subclass is created with python `type` function
# Currently we don't have means to get `self` argument inside a method,
# so we keep `self` around in the closure environment
# Create a subclass of Foo in Nim:
proc createFooSubclassInstance(): PyObject =
# The subclass is created with python `type` function
# Currently we don't have means to get `self` argument inside a method,
# so we keep `self` around in the closure environment

var self: PyObject
var self: PyObject

proc overrideMe(): int =
self.super.overrideMe().to(int) + 123 # Call super
proc overrideMe(): int =
self.super.overrideMe().to(int) + 123 # Call super

self = py.`type`("_", (pyGlobals()["Foo"], ), toPyDict({
"overrideMe": overrideMe
})).to(proc(): PyObject {.gcsafe.})()
return self
self = py.`type`("_", (fooClass, ), toPyDict({
"overrideMe": overrideMe
})).to(proc(): PyObject {.gcsafe.})()
return self

# Create an instance of Bar
let b = createFooSubclassInstance()
# Create an instance of Bar
let b = createFooSubclassInstance()

# Get `useFoo` proc
let useFoo = pyGlobals()["useFoo"].to(proc(self: PyObject): int {.gcsafe.})
# Get `useFoo` proc
let useFoo = locals["useFoo"].to(proc(self: PyObject): int {.gcsafe.})

# Pass b to `useFoo`
doAssert(useFoo(b) == 125)
# Pass b to `useFoo`
doAssert(useFoo(b) == 125)

when isMainModule:
test()
Expand Down

0 comments on commit eb0bb29

Please sign in to comment.