From 02b9ba1dc1f1d4bd21217205daf2280def9f721b Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Tue, 15 Jul 2025 17:48:02 +0200 Subject: [PATCH 01/10] hotfix: misaligned pointer cast --- Project.toml | 2 +- pyproject.toml | 2 +- src/C/consts.jl | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index e7731150..5645f2cf 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "PythonCall" uuid = "6099a3de-0909-46bc-b1f4-468b9a2dfc0d" authors = ["Christopher Doris "] -version = "0.9.25" +version = "0.9.26" [deps] CondaPkg = "992eb4ea-22a4-4c89-a5bb-47a3300528ab" diff --git a/pyproject.toml b/pyproject.toml index 672643d3..8d5fd72c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "juliacall" -version = "0.9.25" +version = "0.9.26" description = "Julia and Python in seamless harmony" readme = { file = "README.md", content-type = "text/markdown" } classifiers = [ diff --git a/src/C/consts.jl b/src/C/consts.jl index 72e49d34..ca0496e0 100644 --- a/src/C/consts.jl +++ b/src/C/consts.jl @@ -315,6 +315,11 @@ end finalize::Ptr{Cvoid} = C_NULL vectorcall::Ptr{Cvoid} = C_NULL + # Python 3.12+ fields + tp_watched::Cchar = 0 + + # Python 3.13+ fields + tp_versions_used::Cuint = 0 end const PyTypePtr = Ptr{PyTypeObject} From d13789f6987ad32edb3620312969ab58dc53dbc3 Mon Sep 17 00:00:00 2001 From: Christopher Rowley Date: Tue, 15 Jul 2025 17:04:27 +0100 Subject: [PATCH 02/10] Correct field type --- src/C/consts.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/C/consts.jl b/src/C/consts.jl index ca0496e0..d3bc775e 100644 --- a/src/C/consts.jl +++ b/src/C/consts.jl @@ -319,7 +319,7 @@ end tp_watched::Cchar = 0 # Python 3.13+ fields - tp_versions_used::Cuint = 0 + tp_versions_used::Cuint16 = 0 end const PyTypePtr = Ptr{PyTypeObject} From f96afce003fc7f1e199f558f3b77fdf878d4e943 Mon Sep 17 00:00:00 2001 From: Christopher Rowley Date: Tue, 15 Jul 2025 17:04:58 +0100 Subject: [PATCH 03/10] Revert version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8d5fd72c..672643d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "juliacall" -version = "0.9.26" +version = "0.9.25" description = "Julia and Python in seamless harmony" readme = { file = "README.md", content-type = "text/markdown" } classifiers = [ From 5d53c66ad1193382c8eabe8ac93da1956f6075a5 Mon Sep 17 00:00:00 2001 From: Christopher Rowley Date: Tue, 15 Jul 2025 17:05:19 +0100 Subject: [PATCH 04/10] Revert version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 5645f2cf..e7731150 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "PythonCall" uuid = "6099a3de-0909-46bc-b1f4-468b9a2dfc0d" authors = ["Christopher Doris "] -version = "0.9.26" +version = "0.9.25" [deps] CondaPkg = "992eb4ea-22a4-4c89-a5bb-47a3300528ab" From 124785a5c5f76a1215fe36e90bebf34a48c7c683 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Tue, 15 Jul 2025 18:15:19 +0200 Subject: [PATCH 05/10] fix type --- src/C/consts.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/C/consts.jl b/src/C/consts.jl index d3bc775e..0bad8267 100644 --- a/src/C/consts.jl +++ b/src/C/consts.jl @@ -319,7 +319,7 @@ end tp_watched::Cchar = 0 # Python 3.13+ fields - tp_versions_used::Cuint16 = 0 + tp_versions_used::Cushort = 0 end const PyTypePtr = Ptr{PyTypeObject} From 45bc5651717b01b31c8df707da698dd54366daaa Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 17 Jul 2025 14:11:22 +0200 Subject: [PATCH 06/10] switch to heap types --- src/C/consts.jl | 26 ++++++++++++++++++++++++ src/C/extras.jl | 16 +++++++-------- src/C/pointers.jl | 3 +++ src/JlWrap/C.jl | 50 ++++++++++++++++++++++++++--------------------- test/HeapType.jl | 45 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 110 insertions(+), 30 deletions(-) create mode 100644 test/HeapType.jl diff --git a/src/C/consts.jl b/src/C/consts.jl index 0bad8267..95686d42 100644 --- a/src/C/consts.jl +++ b/src/C/consts.jl @@ -324,6 +324,32 @@ end const PyTypePtr = Ptr{PyTypeObject} +@kwdef struct PyType_Slot + slot::Cint = 0 + pfunc::Ptr{Cvoid} = C_NULL +end + +@kwdef struct PyType_Spec + name::Cstring = C_NULL + basicsize::Cint = 0 + itemsize::Cint = 0 + flags::Cuint = 0 + slots::Ptr{PyType_Slot} = C_NULL +end + +# These numbers are part of the CPython stable ABI and +# are guaranteed to be the same. +# https://raw.githubusercontent.com/python/cpython/main/Include/typeslots.h +const Py_bf_getbuffer = 1 +const Py_bf_releasebuffer = 2 +const Py_tp_alloc = 47 +const Py_tp_dealloc = 52 +const Py_tp_methods = 64 +const Py_tp_new = 65 +const Py_tp_members = 72 +const Py_tp_getset = 73 +const Py_tp_free = 74 + @kwdef struct PySimpleObject{T} ob_base::PyObject = PyObject() value::T diff --git a/src/C/extras.jl b/src/C/extras.jl index cf46b6b8..ab610ed8 100644 --- a/src/C/extras.jl +++ b/src/C/extras.jl @@ -13,32 +13,32 @@ PyType_IsSubtypeFast(t, f::Integer) = PyMemoryView_GET_BUFFER(m) = Base.GC.@preserve m Ptr{Py_buffer}(UnsafePtr{PyMemoryViewObject}(asptr(m)).view) PyType_CheckBuffer(t) = Base.GC.@preserve t begin - p = UnsafePtr{PyTypeObject}(asptr(t)).as_buffer[] - return p != C_NULL && p.get[!] != C_NULL + p = PyType_GetSlot(asptr(t), Py_bf_getbuffer) + return p != C_NULL end PyObject_CheckBuffer(o) = Base.GC.@preserve o PyType_CheckBuffer(Py_Type(asptr(o))) PyObject_GetBuffer(_o, b, flags) = Base.GC.@preserve _o begin o = asptr(_o) - p = UnsafePtr{PyTypeObject}(Py_Type(o)).as_buffer[] - if p == C_NULL || p.get[!] == C_NULL + getbuf = PyType_GetSlot(Py_Type(o), Py_bf_getbuffer) + if getbuf == C_NULL PyErr_SetString( POINTERS.PyExc_TypeError, "a bytes-like object is required, not '$(String(UnsafePtr{PyTypeObject}(Py_Type(o)).name[]))'", ) return Cint(-1) end - return ccall(p.get[!], Cint, (PyPtr, Ptr{Py_buffer}, Cint), o, b, flags) + return ccall(getbuf, Cint, (PyPtr, Ptr{Py_buffer}, Cint), o, b, flags) end PyBuffer_Release(_b) = begin b = UnsafePtr(Base.unsafe_convert(Ptr{Py_buffer}, _b)) o = b.obj[] o == C_NULL && return - p = UnsafePtr{PyTypeObject}(Py_Type(o)).as_buffer[] - if (p != C_NULL && p.release[!] != C_NULL) - ccall(p.release[!], Cvoid, (PyPtr, Ptr{Py_buffer}), o, b) + releasebuf = PyType_GetSlot(Py_Type(o), Py_bf_releasebuffer) + if releasebuf != C_NULL + ccall(releasebuf, Cvoid, (PyPtr, Ptr{Py_buffer}), o, b) end b.obj[] = C_NULL Py_DecRef(o) diff --git a/src/C/pointers.jl b/src/C/pointers.jl index 6faabb60..356f6f5f 100644 --- a/src/C/pointers.jl +++ b/src/C/pointers.jl @@ -77,6 +77,9 @@ const CAPI_FUNC_SIGS = Dict{Symbol,Pair{Tuple,Type}}( :PyType_IsSubtype => (PyPtr, PyPtr) => Cint, :PyType_Ready => (PyPtr,) => Cint, :PyType_GenericNew => (PyPtr, PyPtr, PyPtr) => PyPtr, + :PyType_FromSpec => (Ptr{PyType_Spec},) => PyPtr, + :PyType_FromSpecWithBases => (Ptr{PyType_Spec}, PyPtr) => PyPtr, + :PyType_GetSlot => (PyPtr, Cint) => Ptr{Cvoid}, # MAPPING :PyMapping_HasKeyString => (PyPtr, Ptr{Cchar}) => Cint, :PyMapping_SetItemString => (PyPtr, Ptr{Cchar}, PyPtr) => Cint, diff --git a/src/JlWrap/C.jl b/src/JlWrap/C.jl index fa96dd36..315aa11c 100644 --- a/src/JlWrap/C.jl +++ b/src/JlWrap/C.jl @@ -9,7 +9,6 @@ using Serialization: serialize, deserialize @kwdef struct PyJuliaValueObject ob_base::C.PyObject = C.PyObject() value::Int = 0 - weaklist::C.PyPtr = C_NULL end const PyJuliaBase_Type = Ref(C.PyNULL) @@ -21,9 +20,9 @@ const PYJLVALUES = [] const PYJLFREEVALUES = Int[] function _pyjl_new(t::C.PyPtr, ::C.PyPtr, ::C.PyPtr) - o = ccall(UnsafePtr{C.PyTypeObject}(t).alloc[!], C.PyPtr, (C.PyPtr, C.Py_ssize_t), t, 0) + allocptr = C.PyType_GetSlot(t, C.Py_tp_alloc) + o = ccall(allocptr, C.PyPtr, (C.PyPtr, C.Py_ssize_t), t, 0) o == C.PyNULL && return C.PyNULL - UnsafePtr{PyJuliaValueObject}(o).weaklist[] = C.PyNULL UnsafePtr{PyJuliaValueObject}(o).value[] = 0 return o end @@ -34,8 +33,8 @@ function _pyjl_dealloc(o::C.PyPtr) PYJLVALUES[idx] = nothing push!(PYJLFREEVALUES, idx) end - UnsafePtr{PyJuliaValueObject}(o).weaklist[!] == C.PyNULL || C.PyObject_ClearWeakRefs(o) - ccall(UnsafePtr{C.PyTypeObject}(C.Py_Type(o)).free[!], Cvoid, (C.PyPtr,), o) + freeptr = C.PyType_GetSlot(C.Py_Type(o), C.Py_tp_free) + ccall(freeptr, Cvoid, (C.PyPtr,), o) nothing end @@ -269,14 +268,12 @@ function _pyjl_deserialize(t::C.PyPtr, v::C.PyPtr) end const _pyjlbase_name = "juliacall.ValueBase" -const _pyjlbase_type = fill(C.PyTypeObject()) const _pyjlbase_isnull_name = "_jl_isnull" const _pyjlbase_callmethod_name = "_jl_callmethod" const _pyjlbase_reduce_name = "__reduce__" const _pyjlbase_serialize_name = "_jl_serialize" const _pyjlbase_deserialize_name = "_jl_deserialize" const _pyjlbase_methods = Vector{C.PyMethodDef}() -const _pyjlbase_as_buffer = fill(C.PyBufferProcs()) function init_c() empty!(_pyjlbase_methods) @@ -309,28 +306,37 @@ function init_c() ), C.PyMethodDef(), ) - _pyjlbase_as_buffer[] = C.PyBufferProcs( - get = @cfunction(_pyjl_get_buffer, Cint, (C.PyPtr, Ptr{C.Py_buffer}, Cint)), - release = @cfunction(_pyjl_release_buffer, Cvoid, (C.PyPtr, Ptr{C.Py_buffer})), - ) - _pyjlbase_type[] = C.PyTypeObject( + slots = C.PyType_Slot[ + C.PyType_Slot(slot = C.Py_tp_dealloc, + pfunc = @cfunction(_pyjl_dealloc, Cvoid, (C.PyPtr,))), + C.PyType_Slot(slot = C.Py_tp_new, + pfunc = @cfunction(_pyjl_new, C.PyPtr, + (C.PyPtr, C.PyPtr, C.PyPtr))), + C.PyType_Slot(slot = C.Py_tp_methods, pfunc = pointer(_pyjlbase_methods)), + C.PyType_Slot(slot = C.Py_bf_getbuffer, + pfunc = @cfunction(_pyjl_get_buffer, Cint, + (C.PyPtr, Ptr{C.Py_buffer}, Cint))), + C.PyType_Slot(slot = C.Py_bf_releasebuffer, + pfunc = @cfunction(_pyjl_release_buffer, Cvoid, + (C.PyPtr, Ptr{C.Py_buffer}))), + C.PyType_Slot(), + ] + spec = C.PyType_Spec( name = pointer(_pyjlbase_name), basicsize = sizeof(PyJuliaValueObject), - # new = C.POINTERS.PyType_GenericNew, - new = @cfunction(_pyjl_new, C.PyPtr, (C.PyPtr, C.PyPtr, C.PyPtr)), - dealloc = @cfunction(_pyjl_dealloc, Cvoid, (C.PyPtr,)), + itemsize = 0, flags = C.Py_TPFLAGS_BASETYPE | C.Py_TPFLAGS_HAVE_VERSION_TAG, - weaklistoffset = fieldoffset(PyJuliaValueObject, 3), - # getattro = C.POINTERS.PyObject_GenericGetAttr, - # setattro = C.POINTERS.PyObject_GenericSetAttr, - methods = pointer(_pyjlbase_methods), - as_buffer = pointer(_pyjlbase_as_buffer), + slots = pointer(slots), ) - o = PyJuliaBase_Type[] = C.PyPtr(pointer(_pyjlbase_type)) - if C.PyType_Ready(o) == -1 + spec_ref = Ref(spec) + o = GC.@preserve spec slots _pyjlbase_methods spec_ref begin + C.PyType_FromSpec(Base.unsafe_convert(Ptr{C.PyType_Spec}, spec_ref)) + end + if o == C.PyNULL C.PyErr_Print() error("Error initializing 'juliacall.ValueBase'") end + PyJuliaBase_Type[] = o end function __init__() diff --git a/test/HeapType.jl b/test/HeapType.jl new file mode 100644 index 00000000..8be511d9 --- /dev/null +++ b/test/HeapType.jl @@ -0,0 +1,45 @@ +@testitem "heap type from spec" begin + using PythonCall + using PythonCall.C + + function _ht_answer(self::C.PyPtr, args::C.PyPtr) + return C.PyLong_FromLongLong(42) + end + + meths = C.PyMethodDef[ + C.PyMethodDef( + name = pointer("answer"), + meth = @cfunction(_ht_answer, C.PyPtr, (C.PyPtr, C.PyPtr)), + flags = C.Py_METH_NOARGS, + ), + C.PyMethodDef(), + ] + + slots = C.PyType_Slot[ + C.PyType_Slot(slot = C.Py_tp_methods, pfunc = pointer(meths)), + C.PyType_Slot(slot = C.Py_tp_new, pfunc = C.POINTERS.PyType_GenericNew), + C.PyType_Slot(), + ] + + spec = C.PyType_Spec( + name = pointer("juliacall.HeapType"), + basicsize = sizeof(C.PyObject), + itemsize = 0, + flags = C.Py_TPFLAGS_DEFAULT | C.Py_TPFLAGS_BASETYPE, + slots = pointer(slots), + ) + + spec_ref = Ref(spec) + typ_ptr = GC.@preserve spec slots meths spec_ref begin + C.PyType_FromSpec(Base.unsafe_convert(Ptr{C.PyType_Spec}, spec_ref)) + end + @test typ_ptr != C.PyNULL + typ = PythonCall.pynew(typ_ptr) + + obj = pycall(typ) + ans = pyconvert(Int, pycall(pygetattr(obj, "answer"))) + @test ans == 42 + + PythonCall.pydel!(obj) + PythonCall.pydel!(typ) +end From 928c0497a7fe7fab0165532e09beea13439305ab Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 17 Jul 2025 14:33:47 +0200 Subject: [PATCH 07/10] use GetFlags --- src/C/extras.jl | 2 +- src/C/pointers.jl | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/C/extras.jl b/src/C/extras.jl index ab610ed8..28248cb7 100644 --- a/src/C/extras.jl +++ b/src/C/extras.jl @@ -8,7 +8,7 @@ Py_TypeCheck(o, t) = Base.GC.@preserve o t PyType_IsSubtype(Py_Type(asptr(o)), a Py_TypeCheckFast(o, f::Integer) = Base.GC.@preserve o PyType_IsSubtypeFast(Py_Type(asptr(o)), f) PyType_IsSubtypeFast(t, f::Integer) = - Base.GC.@preserve t Cint(!iszero(UnsafePtr{PyTypeObject}(asptr(t)).flags[] & f)) + Base.GC.@preserve t Cint(!iszero(PyType_GetFlags(asptr(t)) & f)) PyMemoryView_GET_BUFFER(m) = Base.GC.@preserve m Ptr{Py_buffer}(UnsafePtr{PyMemoryViewObject}(asptr(m)).view) diff --git a/src/C/pointers.jl b/src/C/pointers.jl index 356f6f5f..c4df7456 100644 --- a/src/C/pointers.jl +++ b/src/C/pointers.jl @@ -80,6 +80,8 @@ const CAPI_FUNC_SIGS = Dict{Symbol,Pair{Tuple,Type}}( :PyType_FromSpec => (Ptr{PyType_Spec},) => PyPtr, :PyType_FromSpecWithBases => (Ptr{PyType_Spec}, PyPtr) => PyPtr, :PyType_GetSlot => (PyPtr, Cint) => Ptr{Cvoid}, + :PyType_GetFlags => (PyPtr,) => Culong, + # TODO: PyType_GetName, once we are Python 3.11+ exclusively # MAPPING :PyMapping_HasKeyString => (PyPtr, Ptr{Cchar}) => Cint, :PyMapping_SetItemString => (PyPtr, Ptr{Cchar}, PyPtr) => Cint, From 7c844e28fcdb047690cabc270259001475c9be78 Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 17 Jul 2025 14:35:22 +0200 Subject: [PATCH 08/10] remove buggy heaptype test --- test/HeapType.jl | 45 --------------------------------------------- 1 file changed, 45 deletions(-) delete mode 100644 test/HeapType.jl diff --git a/test/HeapType.jl b/test/HeapType.jl deleted file mode 100644 index 8be511d9..00000000 --- a/test/HeapType.jl +++ /dev/null @@ -1,45 +0,0 @@ -@testitem "heap type from spec" begin - using PythonCall - using PythonCall.C - - function _ht_answer(self::C.PyPtr, args::C.PyPtr) - return C.PyLong_FromLongLong(42) - end - - meths = C.PyMethodDef[ - C.PyMethodDef( - name = pointer("answer"), - meth = @cfunction(_ht_answer, C.PyPtr, (C.PyPtr, C.PyPtr)), - flags = C.Py_METH_NOARGS, - ), - C.PyMethodDef(), - ] - - slots = C.PyType_Slot[ - C.PyType_Slot(slot = C.Py_tp_methods, pfunc = pointer(meths)), - C.PyType_Slot(slot = C.Py_tp_new, pfunc = C.POINTERS.PyType_GenericNew), - C.PyType_Slot(), - ] - - spec = C.PyType_Spec( - name = pointer("juliacall.HeapType"), - basicsize = sizeof(C.PyObject), - itemsize = 0, - flags = C.Py_TPFLAGS_DEFAULT | C.Py_TPFLAGS_BASETYPE, - slots = pointer(slots), - ) - - spec_ref = Ref(spec) - typ_ptr = GC.@preserve spec slots meths spec_ref begin - C.PyType_FromSpec(Base.unsafe_convert(Ptr{C.PyType_Spec}, spec_ref)) - end - @test typ_ptr != C.PyNULL - typ = PythonCall.pynew(typ_ptr) - - obj = pycall(typ) - ans = pyconvert(Int, pycall(pygetattr(obj, "answer"))) - @test ans == 42 - - PythonCall.pydel!(obj) - PythonCall.pydel!(typ) -end From 1080a674ba655148c2d78a8590a6fc3dece1af1b Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 17 Jul 2025 14:36:07 +0200 Subject: [PATCH 09/10] remove PyTypeObject --- src/C/consts.jl | 75 ----------------------------------------------- src/C/extras.jl | 16 +++++++++- src/C/pointers.jl | 1 + 3 files changed, 16 insertions(+), 76 deletions(-) diff --git a/src/C/consts.jl b/src/C/consts.jl index 95686d42..d887421a 100644 --- a/src/C/consts.jl +++ b/src/C/consts.jl @@ -249,81 +249,6 @@ end weakreflist::PyPtr = PyNULL end -@kwdef struct PyTypeObject - ob_base::PyVarObject = PyVarObject() - name::Cstring = C_NULL - - basicsize::Py_ssize_t = 0 - itemsize::Py_ssize_t = 0 - - dealloc::Ptr{Cvoid} = C_NULL - vectorcall_offset::Py_ssize_t = 0 - getattr::Ptr{Cvoid} = C_NULL - setattr::Ptr{Cvoid} = C_NULL - as_async::Ptr{Cvoid} = C_NULL - repr::Ptr{Cvoid} = C_NULL - - as_number::Ptr{PyNumberMethods} = C_NULL - as_sequence::Ptr{PySequenceMethods} = C_NULL - as_mapping::Ptr{PyMappingMethods} = C_NULL - - hash::Ptr{Cvoid} = C_NULL - call::Ptr{Cvoid} = C_NULL - str::Ptr{Cvoid} = C_NULL - getattro::Ptr{Cvoid} = C_NULL - setattro::Ptr{Cvoid} = C_NULL - - as_buffer::Ptr{PyBufferProcs} = C_NULL - - flags::Culong = 0 - - doc::Cstring = C_NULL - - traverse::Ptr{Cvoid} = C_NULL - - clear::Ptr{Cvoid} = C_NULL - - richcompare::Ptr{Cvoid} = C_NULL - - weaklistoffset::Py_ssize_t = 0 - - iter::Ptr{Cvoid} = C_NULL - iternext::Ptr{Cvoid} = C_NULL - - methods::Ptr{PyMethodDef} = C_NULL - members::Ptr{PyMemberDef} = C_NULL - getset::Ptr{PyGetSetDef} = C_NULL - base::PyPtr = C_NULL - dict::PyPtr = C_NULL - descr_get::Ptr{Cvoid} = C_NULL - descr_set::Ptr{Cvoid} = C_NULL - dictoffset::Py_ssize_t = 0 - init::Ptr{Cvoid} = C_NULL - alloc::Ptr{Cvoid} = C_NULL - new::Ptr{Cvoid} = C_NULL - free::Ptr{Cvoid} = C_NULL - is_gc::Ptr{Cvoid} = C_NULL - bases::PyPtr = C_NULL - mro::PyPtr = C_NULL - cache::PyPtr = C_NULL - subclasses::PyPtr = C_NULL - weaklist::PyPtr = C_NULL - del::Ptr{Cvoid} = C_NULL - - version_tag::Cuint = 0 - - finalize::Ptr{Cvoid} = C_NULL - vectorcall::Ptr{Cvoid} = C_NULL - - # Python 3.12+ fields - tp_watched::Cchar = 0 - - # Python 3.13+ fields - tp_versions_used::Cushort = 0 -end - -const PyTypePtr = Ptr{PyTypeObject} - @kwdef struct PyType_Slot slot::Cint = 0 pfunc::Ptr{Cvoid} = C_NULL diff --git a/src/C/extras.jl b/src/C/extras.jl index 28248cb7..ffb46784 100644 --- a/src/C/extras.jl +++ b/src/C/extras.jl @@ -10,6 +10,20 @@ Py_TypeCheckFast(o, f::Integer) = Base.GC.@preserve o PyType_IsSubtypeFast(Py_Ty PyType_IsSubtypeFast(t, f::Integer) = Base.GC.@preserve t Cint(!iszero(PyType_GetFlags(asptr(t)) & f)) +function pytypename(t::PyPtr) + name_obj = PyObject_GetAttrString(t, "__name__") + name_obj == C_NULL && return "(unknown type)" + cstr = PyUnicode_AsUTF8(name_obj) + if cstr == C_NULL + Py_DecRef(name_obj) + return "(unknown type)" + else + str = unsafe_string(cstr) + Py_DecRef(name_obj) + return str + end +end + PyMemoryView_GET_BUFFER(m) = Base.GC.@preserve m Ptr{Py_buffer}(UnsafePtr{PyMemoryViewObject}(asptr(m)).view) PyType_CheckBuffer(t) = Base.GC.@preserve t begin @@ -25,7 +39,7 @@ PyObject_GetBuffer(_o, b, flags) = Base.GC.@preserve _o begin if getbuf == C_NULL PyErr_SetString( POINTERS.PyExc_TypeError, - "a bytes-like object is required, not '$(String(UnsafePtr{PyTypeObject}(Py_Type(o)).name[]))'", + "a bytes-like object is required, not '$(pytypename(Py_Type(o)))'", ) return Cint(-1) end diff --git a/src/C/pointers.jl b/src/C/pointers.jl index c4df7456..c2e1ab90 100644 --- a/src/C/pointers.jl +++ b/src/C/pointers.jl @@ -144,6 +144,7 @@ const CAPI_FUNC_SIGS = Dict{Symbol,Pair{Tuple,Type}}( :PyComplex_AsCComplex => (PyPtr,) => Py_complex, # STR :PyUnicode_DecodeUTF8 => (Ptr{Cchar}, Py_ssize_t, Ptr{Cchar}) => PyPtr, + :PyUnicode_AsUTF8 => (PyPtr,) => Ptr{Cchar}, :PyUnicode_AsUTF8String => (PyPtr,) => PyPtr, :PyUnicode_InternInPlace => (Ptr{PyPtr},) => Cvoid, # BYTES From 80b6c55080a647d31818aeb3341628a009dae78f Mon Sep 17 00:00:00 2001 From: MilesCranmer Date: Thu, 17 Jul 2025 14:43:04 +0200 Subject: [PATCH 10/10] update to stable python/julia --- .github/workflows/tests.yml | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cff42ccd..a1f0eaf2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,7 +19,7 @@ jobs: matrix: arch: [x64] # x86 unsupported by MicroMamba os: [ubuntu-latest, windows-latest, macos-latest] - jlversion: ['1','1.9'] + jlversion: ['1','1.10'] steps: - uses: actions/checkout@v4 @@ -56,7 +56,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - pyversion: ["3", "3.8"] + pyversion: ["3", "3.9"] steps: - uses: actions/checkout@v4 diff --git a/pyproject.toml b/pyproject.toml index 8d5fd72c..a2bd772a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] -requires-python = ">=3.8, <4" +requires-python = ">=3.9, <4" dependencies = ["juliapkg >=0.1.17, <0.2"] [dependency-groups]