diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..b2bb93db --- /dev/null +++ b/.clang-format @@ -0,0 +1,17 @@ +# A clang-format style that approximates Python's PEP 7 +BasedOnStyle: Google +AlwaysBreakAfterReturnType: All +AllowShortIfStatementsOnASingleLine: false +AlignAfterOpenBracket: Align +BreakBeforeBraces: Stroustrup +ColumnLimit: 95 +DerivePointerAlignment: false +IndentWidth: 4 +Language: Cpp +PointerAlignment: Right +ReflowComments: true +SpaceBeforeParens: ControlStatements +SpacesInParentheses: false +TabWidth: 4 +UseTab: Never +SortIncludes: false diff --git a/.clangd b/.clangd new file mode 100644 index 00000000..6c88d686 --- /dev/null +++ b/.clangd @@ -0,0 +1,4 @@ +Diagnostics: + Includes: + IgnoreHeader: + - "pythoncapi_compat.*\\.h" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index de10a2c9..451aca9f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,7 +17,7 @@ jobs: # job. strategy: matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"] os: [ubuntu-latest, macos-latest, windows-latest] loop: [asyncio, uvloop] exclude: diff --git a/.gitignore b/.gitignore index 53c0daa1..ec9c96ac 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,8 @@ docs/_build /.pytest_cache/ /.eggs /.vscode +/.zed /.mypy_cache /.venv* /.tox +/compile_commands.json diff --git a/asyncpg/connection.py b/asyncpg/connection.py index 3e7da7b1..71fb04f8 100644 --- a/asyncpg/connection.py +++ b/asyncpg/connection.py @@ -2751,8 +2751,8 @@ def _check_record_class(record_class): and issubclass(record_class, protocol.Record) ): if ( - record_class.__new__ is not object.__new__ - or record_class.__init__ is not object.__init__ + record_class.__new__ is not protocol.Record.__new__ + or record_class.__init__ is not protocol.Record.__init__ ): raise exceptions.InterfaceError( 'record_class must not redefine __new__ or __init__' diff --git a/asyncpg/protocol/__init__.py b/asyncpg/protocol/__init__.py index af9287bd..e94ccf15 100644 --- a/asyncpg/protocol/__init__.py +++ b/asyncpg/protocol/__init__.py @@ -8,4 +8,5 @@ from __future__ import annotations -from .protocol import Protocol, Record, NO_TIMEOUT, BUILTIN_TYPE_NAME_MAP +from .protocol import Protocol, NO_TIMEOUT, BUILTIN_TYPE_NAME_MAP +from ..record import Record diff --git a/asyncpg/protocol/codecs/base.pxd b/asyncpg/protocol/codecs/base.pxd index 1cfed833..f5492590 100644 --- a/asyncpg/protocol/codecs/base.pxd +++ b/asyncpg/protocol/codecs/base.pxd @@ -22,6 +22,18 @@ ctypedef object (*codec_decode_func)(Codec codec, FRBuffer *buf) +cdef class CodecMap: + cdef: + void** binary_codec_map + void** text_codec_map + dict extra_codecs + + cdef inline void *get_binary_codec_ptr(self, uint32_t idx) + cdef inline void set_binary_codec_ptr(self, uint32_t idx, void *ptr) + cdef inline void *get_text_codec_ptr(self, uint32_t idx) + cdef inline void set_text_codec_ptr(self, uint32_t idx, void *ptr) + + cdef enum CodecType: CODEC_UNDEFINED = 0 CODEC_C = 1 diff --git a/asyncpg/protocol/codecs/base.pyx b/asyncpg/protocol/codecs/base.pyx index e8b44c74..e63a369d 100644 --- a/asyncpg/protocol/codecs/base.pyx +++ b/asyncpg/protocol/codecs/base.pyx @@ -11,9 +11,31 @@ import asyncpg from asyncpg import exceptions -cdef void* binary_codec_map[(MAXSUPPORTEDOID + 1) * 2] -cdef void* text_codec_map[(MAXSUPPORTEDOID + 1) * 2] -cdef dict EXTRA_CODECS = {} +# The class indirection is needed because Cython +# does not (as of 3.1.0) store global cdef variables +# in module state. +@cython.final +cdef class CodecMap: + + def __cinit__(self): + self.extra_codecs = {} + self.binary_codec_map = cpython.PyMem_Calloc((MAXSUPPORTEDOID + 1) * 2, sizeof(void *)); + self.text_codec_map = cpython.PyMem_Calloc((MAXSUPPORTEDOID + 1) * 2, sizeof(void *)); + + cdef inline void *get_binary_codec_ptr(self, uint32_t idx): + return self.binary_codec_map[idx] + + cdef inline void set_binary_codec_ptr(self, uint32_t idx, void *ptr): + self.binary_codec_map[idx] = ptr + + cdef inline void *get_text_codec_ptr(self, uint32_t idx): + return self.text_codec_map[idx] + + cdef inline void set_text_codec_ptr(self, uint32_t idx, void *ptr): + self.text_codec_map[idx] = ptr + + +codec_map = CodecMap() @cython.final @@ -67,7 +89,7 @@ cdef class Codec: ) if element_names is not None: - self.record_desc = record.ApgRecordDesc_New( + self.record_desc = RecordDescriptor( element_names, tuple(element_names)) else: self.record_desc = None @@ -271,7 +293,7 @@ cdef class Codec: schema=self.schema, data_type=self.name, ) - result = record.ApgRecord_New(asyncpg.Record, self.record_desc, elem_count) + result = self.record_desc.make_record(asyncpg.Record, elem_count) for i in range(elem_count): elem_typ = self.element_type_oids[i] received_elem_typ = hton.unpack_int32(frb_read(buf, 4)) @@ -301,7 +323,7 @@ cdef class Codec: settings, frb_slice_from(&elem_buf, buf, elem_len)) cpython.Py_INCREF(elem) - record.ApgRecord_SET_ITEM(result, i, elem) + recordcapi.ApgRecord_SET_ITEM(result, i, elem) return result @@ -811,9 +833,9 @@ cdef inline Codec get_core_codec( if oid > MAXSUPPORTEDOID: return None if format == PG_FORMAT_BINARY: - ptr = binary_codec_map[oid * xformat] + ptr = (codec_map).get_binary_codec_ptr(oid * xformat) elif format == PG_FORMAT_TEXT: - ptr = text_codec_map[oid * xformat] + ptr = (codec_map).get_text_codec_ptr(oid * xformat) if ptr is NULL: return None @@ -839,7 +861,10 @@ cdef inline Codec get_any_core_codec( cdef inline int has_core_codec(uint32_t oid): - return binary_codec_map[oid] != NULL or text_codec_map[oid] != NULL + return ( + (codec_map).get_binary_codec_ptr(oid) != NULL + or (codec_map).get_text_codec_ptr(oid) != NULL + ) cdef register_core_codec(uint32_t oid, @@ -867,9 +892,9 @@ cdef register_core_codec(uint32_t oid, cpython.Py_INCREF(codec) # immortalize if format == PG_FORMAT_BINARY: - binary_codec_map[oid * xformat] = codec + (codec_map).set_binary_codec_ptr(oid * xformat, codec) elif format == PG_FORMAT_TEXT: - text_codec_map[oid * xformat] = codec + (codec_map).set_text_codec_ptr(oid * xformat, codec) else: raise exceptions.InternalClientError( 'invalid data format: {}'.format(format)) @@ -888,8 +913,8 @@ cdef register_extra_codec(str name, codec = Codec(INVALIDOID) codec.init(name, None, kind, CODEC_C, format, PG_XFORMAT_OBJECT, encode, decode, None, None, None, None, None, None, None, 0) - EXTRA_CODECS[name, format] = codec + (codec_map).extra_codecs[name, format] = codec cdef inline Codec get_extra_codec(str name, ServerDataFormat format): - return EXTRA_CODECS.get((name, format)) + return (codec_map).extra_codecs.get((name, format)) diff --git a/asyncpg/protocol/coreproto.pyx b/asyncpg/protocol/coreproto.pyx index 19857878..da96c412 100644 --- a/asyncpg/protocol/coreproto.pyx +++ b/asyncpg/protocol/coreproto.pyx @@ -11,7 +11,7 @@ import hashlib include "scram.pyx" -cdef dict AUTH_METHOD_NAME = { +AUTH_METHOD_NAME = { AUTH_REQUIRED_KERBEROS: 'kerberosv5', AUTH_REQUIRED_PASSWORD: 'password', AUTH_REQUIRED_PASSWORDMD5: 'md5', @@ -1229,5 +1229,5 @@ cdef class CoreProtocol: pass -cdef bytes SYNC_MESSAGE = bytes(WriteBuffer.new_message(b'S').end_message()) -cdef bytes FLUSH_MESSAGE = bytes(WriteBuffer.new_message(b'H').end_message()) +SYNC_MESSAGE = bytes(WriteBuffer.new_message(b'S').end_message()) +FLUSH_MESSAGE = bytes(WriteBuffer.new_message(b'H').end_message()) diff --git a/asyncpg/protocol/encodings.pyx b/asyncpg/protocol/encodings.pyx index dcd692b7..1463dbe4 100644 --- a/asyncpg/protocol/encodings.pyx +++ b/asyncpg/protocol/encodings.pyx @@ -10,7 +10,7 @@ https://www.postgresql.org/docs/current/static/multibyte.html#CHARSET-TABLE ''' -cdef dict ENCODINGS_MAP = { +ENCODINGS_MAP = { 'abc': 'cp1258', 'alt': 'cp866', 'euc_cn': 'euccn', diff --git a/asyncpg/protocol/pgtypes.pxi b/asyncpg/protocol/pgtypes.pxi index e9bb782f..86f8e663 100644 --- a/asyncpg/protocol/pgtypes.pxi +++ b/asyncpg/protocol/pgtypes.pxi @@ -113,7 +113,7 @@ DEF ANYCOMPATIBLEARRAYOID = 5078 DEF ANYCOMPATIBLENONARRAYOID = 5079 DEF ANYCOMPATIBLERANGEOID = 5080 -cdef ARRAY_TYPES = (_TEXTOID, _OIDOID,) +ARRAY_TYPES = {_TEXTOID, _OIDOID} BUILTIN_TYPE_OID_MAP = { ABSTIMEOID: 'abstime', diff --git a/asyncpg/protocol/prepared_stmt.pyx b/asyncpg/protocol/prepared_stmt.pyx index cb0afa24..4145c664 100644 --- a/asyncpg/protocol/prepared_stmt.pyx +++ b/asyncpg/protocol/prepared_stmt.pyx @@ -230,7 +230,7 @@ cdef class PreparedStatementState: return if self.cols_num == 0: - self.cols_desc = record.ApgRecordDesc_New({}, ()) + self.cols_desc = RecordDescriptor({}, ()) return cols_mapping = collections.OrderedDict() @@ -252,7 +252,7 @@ cdef class PreparedStatementState: codecs.append(codec) - self.cols_desc = record.ApgRecordDesc_New( + self.cols_desc = RecordDescriptor( cols_mapping, tuple(cols_names)) self.rows_codecs = tuple(codecs) @@ -310,7 +310,7 @@ cdef class PreparedStatementState: 'different from what was described ({})'.format( fnum, self.cols_num)) - dec_row = record.ApgRecord_New(self.record_class, self.cols_desc, fnum) + dec_row = self.cols_desc.make_record(self.record_class, fnum) for i in range(fnum): flen = hton.unpack_int32(frb_read(&rbuf, 4)) @@ -333,7 +333,7 @@ cdef class PreparedStatementState: frb_set_len(&rbuf, bl - flen) cpython.Py_INCREF(val) - record.ApgRecord_SET_ITEM(dec_row, i, val) + recordcapi.ApgRecord_SET_ITEM(dec_row, i, val) if frb_get_len(&rbuf) != 0: raise BufferError('unexpected trailing {} bytes in buffer'.format( diff --git a/asyncpg/protocol/protocol.pyi b/asyncpg/protocol/protocol.pyi index b81d13cd..c6370f94 100644 --- a/asyncpg/protocol/protocol.pyi +++ b/asyncpg/protocol/protocol.pyi @@ -2,7 +2,7 @@ import asyncio import asyncio.protocols import hmac from codecs import CodecInfo -from collections.abc import Callable, Iterable, Iterator, Sequence +from collections.abc import Callable, Iterable, Sequence from hashlib import md5, sha256 from typing import ( Any, @@ -22,8 +22,8 @@ import asyncpg.pgproto.pgproto from ..connect_utils import _ConnectionParameters from ..pgproto.pgproto import WriteBuffer from ..types import Attribute, Type +from ..record import Record -_T = TypeVar('_T') _Record = TypeVar('_Record', bound=Record) _OtherRecord = TypeVar('_OtherRecord', bound=Record) _PreparedStatementState = TypeVar( @@ -254,24 +254,6 @@ class DataCodecConfig: class Protocol(BaseProtocol[_Record], asyncio.protocols.Protocol): ... -class Record: - @overload - def get(self, key: str) -> Any | None: ... - @overload - def get(self, key: str, default: _T) -> Any | _T: ... - def items(self) -> Iterator[tuple[str, Any]]: ... - def keys(self) -> Iterator[str]: ... - def values(self) -> Iterator[Any]: ... - @overload - def __getitem__(self, index: str) -> Any: ... - @overload - def __getitem__(self, index: int) -> Any: ... - @overload - def __getitem__(self, index: slice) -> tuple[Any, ...]: ... - def __iter__(self) -> Iterator[Any]: ... - def __contains__(self, x: object) -> bool: ... - def __len__(self) -> int: ... - class Timer: def __init__(self, budget: float | None) -> None: ... def __enter__(self) -> None: ... diff --git a/asyncpg/protocol/protocol.pyx b/asyncpg/protocol/protocol.pyx index bd2ad05c..0d731fac 100644 --- a/asyncpg/protocol/protocol.pyx +++ b/asyncpg/protocol/protocol.pyx @@ -34,7 +34,7 @@ from asyncpg.pgproto.pgproto cimport ( from asyncpg.pgproto cimport pgproto from asyncpg.protocol cimport cpythonx -from asyncpg.protocol cimport record +from asyncpg.protocol cimport recordcapi from libc.stdint cimport int8_t, uint8_t, int16_t, uint16_t, \ int32_t, uint32_t, int64_t, uint64_t, \ @@ -46,6 +46,7 @@ from asyncpg import types as apg_types from asyncpg import exceptions as apg_exc from asyncpg.pgproto cimport hton +from asyncpg.record import Record, RecordDescriptor include "consts.pxi" @@ -1049,17 +1050,14 @@ def _create_record(object mapping, tuple elems): int32_t i if mapping is None: - desc = record.ApgRecordDesc_New({}, ()) + desc = RecordDescriptor({}, ()) else: - desc = record.ApgRecordDesc_New( + desc = RecordDescriptor( mapping, tuple(mapping) if mapping else ()) - rec = record.ApgRecord_New(Record, desc, len(elems)) + rec = desc.make_record(Record, len(elems)) for i in range(len(elems)): elem = elems[i] cpython.Py_INCREF(elem) - record.ApgRecord_SET_ITEM(rec, i, elem) + recordcapi.ApgRecord_SET_ITEM(rec, i, elem) return rec - - -Record = record.ApgRecord_InitTypes() diff --git a/asyncpg/protocol/record/recordobj.c b/asyncpg/protocol/record/recordobj.c deleted file mode 100644 index 9767f43b..00000000 --- a/asyncpg/protocol/record/recordobj.c +++ /dev/null @@ -1,1025 +0,0 @@ -/* Big parts of this file are copied (with modifications) from - CPython/Objects/tupleobject.c. - - Portions Copyright (c) PSF (and other CPython copyright holders). - Portions Copyright (c) 2016-present MagicStack Inc. - License: PSFL v2; see CPython/LICENSE for details. -*/ - -#include "recordobj.h" - -#ifdef _PyObject_GC_IS_TRACKED -# define _ApgObject_GC_IS_TRACKED _PyObject_GC_IS_TRACKED -#else -# define _ApgObject_GC_IS_TRACKED PyObject_GC_IsTracked -#endif - -static PyObject * record_iter(PyObject *); -static PyObject * record_new_items_iter(PyObject *); - -static ApgRecordObject *free_list[ApgRecord_MAXSAVESIZE]; -static int numfree[ApgRecord_MAXSAVESIZE]; - -static size_t MAX_RECORD_SIZE = ( - ((size_t)PY_SSIZE_T_MAX - sizeof(ApgRecordObject) - sizeof(PyObject *)) - / sizeof(PyObject *) -); - - -PyObject * -ApgRecord_New(PyTypeObject *type, PyObject *desc, Py_ssize_t size) -{ - ApgRecordObject *o; - Py_ssize_t i; - int need_gc_track = 0; - - if (size < 0 || desc == NULL || !ApgRecordDesc_CheckExact(desc)) { - PyErr_BadInternalCall(); - return NULL; - } - - if (type == &ApgRecord_Type) { - if (size < ApgRecord_MAXSAVESIZE && (o = free_list[size]) != NULL) { - free_list[size] = (ApgRecordObject *) o->ob_item[0]; - numfree[size]--; - _Py_NewReference((PyObject *)o); - } - else { - /* Check for overflow */ - if ((size_t)size > MAX_RECORD_SIZE) { - return PyErr_NoMemory(); - } - o = PyObject_GC_NewVar(ApgRecordObject, &ApgRecord_Type, size); - if (o == NULL) { - return NULL; - } - } - - need_gc_track = 1; - } else { - assert(PyType_IsSubtype(type, &ApgRecord_Type)); - - if ((size_t)size > MAX_RECORD_SIZE) { - return PyErr_NoMemory(); - } - o = (ApgRecordObject *)type->tp_alloc(type, size); - if (!_ApgObject_GC_IS_TRACKED((PyObject *)o)) { - PyErr_SetString( - PyExc_TypeError, - "record subclass is not tracked by GC" - ); - return NULL; - } - } - - for (i = 0; i < size; i++) { - o->ob_item[i] = NULL; - } - - Py_INCREF(desc); - o->desc = (ApgRecordDescObject*)desc; - o->self_hash = -1; - if (need_gc_track) { - PyObject_GC_Track(o); - } - return (PyObject *) o; -} - - -static void -record_dealloc(ApgRecordObject *o) -{ - Py_ssize_t i; - Py_ssize_t len = Py_SIZE(o); - - PyObject_GC_UnTrack(o); - - o->self_hash = -1; - - Py_CLEAR(o->desc); - - Py_TRASHCAN_BEGIN(o, record_dealloc) - if (len > 0) { - i = len; - while (--i >= 0) { - Py_CLEAR(o->ob_item[i]); - } - - if (len < ApgRecord_MAXSAVESIZE && - numfree[len] < ApgRecord_MAXFREELIST && - ApgRecord_CheckExact(o)) - { - o->ob_item[0] = (PyObject *) free_list[len]; - numfree[len]++; - free_list[len] = o; - goto done; /* return */ - } - } - Py_TYPE(o)->tp_free((PyObject *)o); -done: - Py_TRASHCAN_END -} - - -static int -record_traverse(ApgRecordObject *o, visitproc visit, void *arg) -{ - Py_ssize_t i; - - Py_VISIT(o->desc); - - for (i = Py_SIZE(o); --i >= 0;) { - if (o->ob_item[i] != NULL) { - Py_VISIT(o->ob_item[i]); - } - } - - return 0; -} - - -static Py_ssize_t -record_length(ApgRecordObject *o) -{ - return Py_SIZE(o); -} - - -#if PY_VERSION_HEX >= 0x03080000 - -#if SIZEOF_PY_UHASH_T > 4 -#define _PyHASH_XXPRIME_1 ((Py_uhash_t)11400714785074694791ULL) -#define _PyHASH_XXPRIME_2 ((Py_uhash_t)14029467366897019727ULL) -#define _PyHASH_XXPRIME_5 ((Py_uhash_t)2870177450012600261ULL) -#define _PyHASH_XXROTATE(x) ((x << 31) | (x >> 33)) /* Rotate left 31 bits */ -#else -#define _PyHASH_XXPRIME_1 ((Py_uhash_t)2654435761UL) -#define _PyHASH_XXPRIME_2 ((Py_uhash_t)2246822519UL) -#define _PyHASH_XXPRIME_5 ((Py_uhash_t)374761393UL) -#define _PyHASH_XXROTATE(x) ((x << 13) | (x >> 19)) /* Rotate left 13 bits */ -#endif - -static Py_hash_t -record_hash(ApgRecordObject *v) -{ - Py_uhash_t acc = _PyHASH_XXPRIME_5; - size_t i, len = (size_t)Py_SIZE(v); - PyObject **els = v->ob_item; - for (i = 0; i < len; i++) { - Py_uhash_t lane = (Py_uhash_t)PyObject_Hash(els[i]); - if (lane == (Py_uhash_t)-1) { - return -1; - } - acc += lane * _PyHASH_XXPRIME_2; - acc = _PyHASH_XXROTATE(acc); - acc *= _PyHASH_XXPRIME_1; - } - - /* Add input length, mangled to keep the historical value of hash(()). */ - acc += len ^ (_PyHASH_XXPRIME_5 ^ 3527539UL); - - if (acc == (Py_uhash_t)-1) { - return 1546275796; - } - return (Py_hash_t)acc; -} - -#else - -static Py_hash_t -record_hash(ApgRecordObject *v) -{ - Py_uhash_t x; /* Unsigned for defined overflow behavior. */ - Py_hash_t y; - Py_ssize_t len; - PyObject **p; - Py_uhash_t mult; - - if (v->self_hash != -1) { - return v->self_hash; - } - - len = Py_SIZE(v); - mult = _PyHASH_MULTIPLIER; - - x = 0x345678UL; - p = v->ob_item; - while (--len >= 0) { - y = PyObject_Hash(*p++); - if (y == -1) { - return -1; - } - x = (x ^ (Py_uhash_t)y) * mult; - /* the cast might truncate len; that doesn't change hash stability */ - mult += (Py_uhash_t)(82520UL + (size_t)len + (size_t)len); - } - x += 97531UL; - if (x == (Py_uhash_t)-1) { - x = (Py_uhash_t)-2; - } - v->self_hash = (Py_hash_t)x; - return (Py_hash_t)x; -} - -#endif - - -static PyObject * -record_richcompare(PyObject *v, PyObject *w, int op) -{ - Py_ssize_t i; - Py_ssize_t vlen, wlen; - int v_is_tuple = 0; - int w_is_tuple = 0; - int v_is_record = 0; - int w_is_record = 0; - int comp; - - if (PyTuple_Check(v)) { - v_is_tuple = 1; - } - else if (ApgRecord_CheckExact(v)) { - v_is_record = 1; - } - else if (!ApgRecord_Check(v)) { - Py_RETURN_NOTIMPLEMENTED; - } - - if (PyTuple_Check(w)) { - w_is_tuple = 1; - } - else if (ApgRecord_CheckExact(w)) { - w_is_record = 1; - } - else if (!ApgRecord_Check(w)) { - Py_RETURN_NOTIMPLEMENTED; - } - - -#define V_ITEM(i) \ - (v_is_tuple ? \ - PyTuple_GET_ITEM(v, i) \ - : (v_is_record ? ApgRecord_GET_ITEM(v, i) : PySequence_GetItem(v, i))) -#define W_ITEM(i) \ - (w_is_tuple ? \ - PyTuple_GET_ITEM(w, i) \ - : (w_is_record ? ApgRecord_GET_ITEM(w, i) : PySequence_GetItem(w, i))) - - vlen = Py_SIZE(v); - wlen = Py_SIZE(w); - - if (op == Py_EQ && vlen != wlen) { - /* Checking if v == w, but len(v) != len(w): return False */ - Py_RETURN_FALSE; - } - - if (op == Py_NE && vlen != wlen) { - /* Checking if v != w, and len(v) != len(w): return True */ - Py_RETURN_TRUE; - } - - /* Search for the first index where items are different. - * Note that because tuples are immutable, it's safe to reuse - * vlen and wlen across the comparison calls. - */ - for (i = 0; i < vlen && i < wlen; i++) { - comp = PyObject_RichCompareBool(V_ITEM(i), W_ITEM(i), Py_EQ); - if (comp < 0) { - return NULL; - } - if (!comp) { - break; - } - } - - if (i >= vlen || i >= wlen) { - /* No more items to compare -- compare sizes */ - int cmp; - switch (op) { - case Py_LT: cmp = vlen < wlen; break; - case Py_LE: cmp = vlen <= wlen; break; - case Py_EQ: cmp = vlen == wlen; break; - case Py_NE: cmp = vlen != wlen; break; - case Py_GT: cmp = vlen > wlen; break; - case Py_GE: cmp = vlen >= wlen; break; - default: return NULL; /* cannot happen */ - } - if (cmp) { - Py_RETURN_TRUE; - } - else { - Py_RETURN_FALSE; - } - } - - /* We have an item that differs -- shortcuts for EQ/NE */ - if (op == Py_EQ) { - Py_RETURN_FALSE; - } - if (op == Py_NE) { - Py_RETURN_TRUE; - } - - /* Compare the final item again using the proper operator */ - return PyObject_RichCompare(V_ITEM(i), W_ITEM(i), op); - -#undef V_ITEM -#undef W_ITEM -} - - -static PyObject * -record_item(ApgRecordObject *o, Py_ssize_t i) -{ - if (i < 0 || i >= Py_SIZE(o)) { - PyErr_SetString(PyExc_IndexError, "record index out of range"); - return NULL; - } - Py_INCREF(o->ob_item[i]); - return o->ob_item[i]; -} - - -typedef enum item_by_name_result { - APG_ITEM_FOUND = 0, - APG_ERROR = -1, - APG_ITEM_NOT_FOUND = -2 -} item_by_name_result_t; - - -/* Lookup a record value by its name. Return 0 on success, -2 if the - * value was not found (with KeyError set), and -1 on all other errors. - */ -static item_by_name_result_t -record_item_by_name(ApgRecordObject *o, PyObject *item, PyObject **result) -{ - PyObject *mapped; - PyObject *val; - Py_ssize_t i; - - mapped = PyObject_GetItem(o->desc->mapping, item); - if (mapped == NULL) { - goto noitem; - } - - if (!PyIndex_Check(mapped)) { - Py_DECREF(mapped); - goto error; - } - - i = PyNumber_AsSsize_t(mapped, PyExc_IndexError); - Py_DECREF(mapped); - - if (i < 0) { - if (PyErr_Occurred()) - PyErr_Clear(); - goto error; - } - - val = record_item(o, i); - if (val == NULL) { - PyErr_Clear(); - goto error; - } - - *result = val; - - return APG_ITEM_FOUND; - -noitem: - PyErr_SetObject(PyExc_KeyError, item); - return APG_ITEM_NOT_FOUND; - -error: - PyErr_SetString(PyExc_RuntimeError, "invalid record descriptor"); - return APG_ERROR; -} - - -static PyObject * -record_subscript(ApgRecordObject* o, PyObject* item) -{ - if (PyIndex_Check(item)) { - Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError); - if (i == -1 && PyErr_Occurred()) - return NULL; - if (i < 0) { - i += Py_SIZE(o); - } - return record_item(o, i); - } - else if (PySlice_Check(item)) { - Py_ssize_t start, stop, step, slicelength, cur, i; - PyObject* result; - PyObject* it; - PyObject **src, **dest; - - if (PySlice_GetIndicesEx( - item, - Py_SIZE(o), - &start, &stop, &step, &slicelength) < 0) - { - return NULL; - } - - if (slicelength <= 0) { - return PyTuple_New(0); - } - else { - result = PyTuple_New(slicelength); - if (!result) return NULL; - - src = o->ob_item; - dest = ((PyTupleObject *)result)->ob_item; - for (cur = start, i = 0; i < slicelength; cur += step, i++) { - it = src[cur]; - Py_INCREF(it); - dest[i] = it; - } - - return result; - } - } - else { - PyObject* result; - - if (record_item_by_name(o, item, &result) < 0) - return NULL; - else - return result; - } -} - - -static const char * -get_typename(PyTypeObject *type) -{ - assert(type->tp_name != NULL); - const char *s = strrchr(type->tp_name, '.'); - if (s == NULL) { - s = type->tp_name; - } - else { - s++; - } - return s; -} - - -static PyObject * -record_repr(ApgRecordObject *v) -{ - Py_ssize_t i, n; - PyObject *keys_iter, *type_prefix; - _PyUnicodeWriter writer; - - n = Py_SIZE(v); - if (n == 0) { - return PyUnicode_FromFormat("<%s>", get_typename(Py_TYPE(v))); - } - - keys_iter = PyObject_GetIter(v->desc->keys); - if (keys_iter == NULL) { - return NULL; - } - - i = Py_ReprEnter((PyObject *)v); - if (i != 0) { - Py_DECREF(keys_iter); - if (i > 0) { - return PyUnicode_FromFormat("<%s ...>", get_typename(Py_TYPE(v))); - } - return NULL; - } - - _PyUnicodeWriter_Init(&writer); - writer.overallocate = 1; - writer.min_length = 12; /* */ - - type_prefix = PyUnicode_FromFormat("<%s ", get_typename(Py_TYPE(v))); - if (_PyUnicodeWriter_WriteStr(&writer, type_prefix) < 0) { - Py_DECREF(type_prefix); - goto error; - } - Py_DECREF(type_prefix); - - for (i = 0; i < n; ++i) { - PyObject *key; - PyObject *key_repr; - PyObject *val_repr; - - if (i > 0) { - if (_PyUnicodeWriter_WriteChar(&writer, ' ') < 0) { - goto error; - } - } - - if (Py_EnterRecursiveCall(" while getting the repr of a record")) { - goto error; - } - val_repr = PyObject_Repr(v->ob_item[i]); - Py_LeaveRecursiveCall(); - if (val_repr == NULL) { - goto error; - } - - key = PyIter_Next(keys_iter); - if (key == NULL) { - Py_DECREF(val_repr); - PyErr_SetString(PyExc_RuntimeError, "invalid record mapping"); - goto error; - } - - key_repr = PyObject_Str(key); - Py_DECREF(key); - if (key_repr == NULL) { - Py_DECREF(val_repr); - goto error; - } - - if (_PyUnicodeWriter_WriteStr(&writer, key_repr) < 0) { - Py_DECREF(key_repr); - Py_DECREF(val_repr); - goto error; - } - Py_DECREF(key_repr); - - if (_PyUnicodeWriter_WriteChar(&writer, '=') < 0) { - Py_DECREF(val_repr); - goto error; - } - - if (_PyUnicodeWriter_WriteStr(&writer, val_repr) < 0) { - Py_DECREF(val_repr); - goto error; - } - Py_DECREF(val_repr); - } - - writer.overallocate = 0; - if (_PyUnicodeWriter_WriteChar(&writer, '>') < 0) { - goto error; - } - - Py_DECREF(keys_iter); - Py_ReprLeave((PyObject *)v); - return _PyUnicodeWriter_Finish(&writer); - -error: - Py_DECREF(keys_iter); - _PyUnicodeWriter_Dealloc(&writer); - Py_ReprLeave((PyObject *)v); - return NULL; -} - - - -static PyObject * -record_values(PyObject *o, PyObject *args) -{ - return record_iter(o); -} - - -static PyObject * -record_keys(PyObject *o, PyObject *args) -{ - if (!ApgRecord_Check(o)) { - PyErr_BadInternalCall(); - return NULL; - } - - return PyObject_GetIter(((ApgRecordObject*)o)->desc->keys); -} - - -static PyObject * -record_items(PyObject *o, PyObject *args) -{ - if (!ApgRecord_Check(o)) { - PyErr_BadInternalCall(); - return NULL; - } - - return record_new_items_iter(o); -} - - -static int -record_contains(ApgRecordObject *o, PyObject *arg) -{ - if (!ApgRecord_Check(o)) { - PyErr_BadInternalCall(); - return -1; - } - - return PySequence_Contains(o->desc->mapping, arg); -} - - -static PyObject * -record_get(ApgRecordObject* o, PyObject* args) -{ - PyObject *key; - PyObject *defval = Py_None; - PyObject *val = NULL; - int res; - - if (!PyArg_UnpackTuple(args, "get", 1, 2, &key, &defval)) - return NULL; - - res = record_item_by_name(o, key, &val); - if (res == APG_ITEM_NOT_FOUND) { - PyErr_Clear(); - Py_INCREF(defval); - val = defval; - } - - return val; -} - - -static PySequenceMethods record_as_sequence = { - (lenfunc)record_length, /* sq_length */ - 0, /* sq_concat */ - 0, /* sq_repeat */ - (ssizeargfunc)record_item, /* sq_item */ - 0, /* sq_slice */ - 0, /* sq_ass_item */ - 0, /* sq_ass_slice */ - (objobjproc)record_contains, /* sq_contains */ -}; - - -static PyMappingMethods record_as_mapping = { - (lenfunc)record_length, /* mp_length */ - (binaryfunc)record_subscript, /* mp_subscript */ - 0 /* mp_ass_subscript */ -}; - - -static PyMethodDef record_methods[] = { - {"values", (PyCFunction)record_values, METH_NOARGS}, - {"keys", (PyCFunction)record_keys, METH_NOARGS}, - {"items", (PyCFunction)record_items, METH_NOARGS}, - {"get", (PyCFunction)record_get, METH_VARARGS}, - {NULL, NULL} /* sentinel */ -}; - - -PyTypeObject ApgRecord_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "asyncpg.Record", - .tp_basicsize = sizeof(ApgRecordObject) - sizeof(PyObject *), - .tp_itemsize = sizeof(PyObject *), - .tp_dealloc = (destructor)record_dealloc, - .tp_repr = (reprfunc)record_repr, - .tp_as_sequence = &record_as_sequence, - .tp_as_mapping = &record_as_mapping, - .tp_hash = (hashfunc)record_hash, - .tp_getattro = PyObject_GenericGetAttr, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, - .tp_traverse = (traverseproc)record_traverse, - .tp_richcompare = record_richcompare, - .tp_iter = record_iter, - .tp_methods = record_methods, - .tp_free = PyObject_GC_Del, -}; - - -/* Record Iterator */ - - -typedef struct { - PyObject_HEAD - Py_ssize_t it_index; - ApgRecordObject *it_seq; /* Set to NULL when iterator is exhausted */ -} ApgRecordIterObject; - - -static void -record_iter_dealloc(ApgRecordIterObject *it) -{ - PyObject_GC_UnTrack(it); - Py_CLEAR(it->it_seq); - PyObject_GC_Del(it); -} - - -static int -record_iter_traverse(ApgRecordIterObject *it, visitproc visit, void *arg) -{ - Py_VISIT(it->it_seq); - return 0; -} - - -static PyObject * -record_iter_next(ApgRecordIterObject *it) -{ - ApgRecordObject *seq; - PyObject *item; - - assert(it != NULL); - seq = it->it_seq; - if (seq == NULL) - return NULL; - assert(ApgRecord_Check(seq)); - - if (it->it_index < Py_SIZE(seq)) { - item = ApgRecord_GET_ITEM(seq, it->it_index); - ++it->it_index; - Py_INCREF(item); - return item; - } - - it->it_seq = NULL; - Py_DECREF(seq); - return NULL; -} - - -static PyObject * -record_iter_len(ApgRecordIterObject *it) -{ - Py_ssize_t len = 0; - if (it->it_seq) { - len = Py_SIZE(it->it_seq) - it->it_index; - } - return PyLong_FromSsize_t(len); -} - - -PyDoc_STRVAR(record_iter_len_doc, - "Private method returning an estimate of len(list(it))."); - - -static PyMethodDef record_iter_methods[] = { - {"__length_hint__", (PyCFunction)record_iter_len, METH_NOARGS, - record_iter_len_doc}, - {NULL, NULL} /* sentinel */ -}; - - -PyTypeObject ApgRecordIter_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "RecordIterator", - .tp_basicsize = sizeof(ApgRecordIterObject), - .tp_dealloc = (destructor)record_iter_dealloc, - .tp_getattro = PyObject_GenericGetAttr, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, - .tp_traverse = (traverseproc)record_iter_traverse, - .tp_iter = PyObject_SelfIter, - .tp_iternext = (iternextfunc)record_iter_next, - .tp_methods = record_iter_methods, -}; - - -static PyObject * -record_iter(PyObject *seq) -{ - ApgRecordIterObject *it; - - if (!ApgRecord_Check(seq)) { - PyErr_BadInternalCall(); - return NULL; - } - it = PyObject_GC_New(ApgRecordIterObject, &ApgRecordIter_Type); - if (it == NULL) - return NULL; - it->it_index = 0; - Py_INCREF(seq); - it->it_seq = (ApgRecordObject *)seq; - PyObject_GC_Track(it); - return (PyObject *)it; -} - - -/* Record Items Iterator */ - - -typedef struct { - PyObject_HEAD - Py_ssize_t it_index; - PyObject *it_key_iter; - ApgRecordObject *it_seq; /* Set to NULL when iterator is exhausted */ -} ApgRecordItemsObject; - - -static void -record_items_dealloc(ApgRecordItemsObject *it) -{ - PyObject_GC_UnTrack(it); - Py_CLEAR(it->it_key_iter); - Py_CLEAR(it->it_seq); - PyObject_GC_Del(it); -} - - -static int -record_items_traverse(ApgRecordItemsObject *it, visitproc visit, void *arg) -{ - Py_VISIT(it->it_key_iter); - Py_VISIT(it->it_seq); - return 0; -} - - -static PyObject * -record_items_next(ApgRecordItemsObject *it) -{ - ApgRecordObject *seq; - PyObject *key; - PyObject *val; - PyObject *tup; - - assert(it != NULL); - seq = it->it_seq; - if (seq == NULL) { - return NULL; - } - assert(ApgRecord_Check(seq)); - assert(it->it_key_iter != NULL); - - key = PyIter_Next(it->it_key_iter); - if (key == NULL) { - /* likely it_key_iter had less items than seq has values */ - goto exhausted; - } - - if (it->it_index < Py_SIZE(seq)) { - val = ApgRecord_GET_ITEM(seq, it->it_index); - ++it->it_index; - Py_INCREF(val); - } - else { - /* it_key_iter had more items than seq has values */ - Py_DECREF(key); - goto exhausted; - } - - tup = PyTuple_New(2); - if (tup == NULL) { - Py_DECREF(val); - Py_DECREF(key); - goto exhausted; - } - - PyTuple_SET_ITEM(tup, 0, key); - PyTuple_SET_ITEM(tup, 1, val); - return tup; - -exhausted: - Py_CLEAR(it->it_key_iter); - Py_CLEAR(it->it_seq); - return NULL; -} - - -static PyObject * -record_items_len(ApgRecordItemsObject *it) -{ - Py_ssize_t len = 0; - if (it->it_seq) { - len = Py_SIZE(it->it_seq) - it->it_index; - } - return PyLong_FromSsize_t(len); -} - - -PyDoc_STRVAR(record_items_len_doc, - "Private method returning an estimate of len(list(it()))."); - - -static PyMethodDef record_items_methods[] = { - {"__length_hint__", (PyCFunction)record_items_len, METH_NOARGS, - record_items_len_doc}, - {NULL, NULL} /* sentinel */ -}; - - -PyTypeObject ApgRecordItems_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "RecordItemsIterator", - .tp_basicsize = sizeof(ApgRecordItemsObject), - .tp_dealloc = (destructor)record_items_dealloc, - .tp_getattro = PyObject_GenericGetAttr, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, - .tp_traverse = (traverseproc)record_items_traverse, - .tp_iter = PyObject_SelfIter, - .tp_iternext = (iternextfunc)record_items_next, - .tp_methods = record_items_methods, -}; - - -static PyObject * -record_new_items_iter(PyObject *seq) -{ - ApgRecordItemsObject *it; - PyObject *key_iter; - - if (!ApgRecord_Check(seq)) { - PyErr_BadInternalCall(); - return NULL; - } - - key_iter = PyObject_GetIter(((ApgRecordObject*)seq)->desc->keys); - if (key_iter == NULL) { - return NULL; - } - - it = PyObject_GC_New(ApgRecordItemsObject, &ApgRecordItems_Type); - if (it == NULL) - return NULL; - - it->it_key_iter = key_iter; - it->it_index = 0; - Py_INCREF(seq); - it->it_seq = (ApgRecordObject *)seq; - PyObject_GC_Track(it); - - return (PyObject *)it; -} - - -PyTypeObject * -ApgRecord_InitTypes(void) -{ - if (PyType_Ready(&ApgRecord_Type) < 0) { - return NULL; - } - - if (PyType_Ready(&ApgRecordDesc_Type) < 0) { - return NULL; - } - - if (PyType_Ready(&ApgRecordIter_Type) < 0) { - return NULL; - } - - if (PyType_Ready(&ApgRecordItems_Type) < 0) { - return NULL; - } - - return &ApgRecord_Type; -} - - -/* ----------------- */ - - -static void -record_desc_dealloc(ApgRecordDescObject *o) -{ - PyObject_GC_UnTrack(o); - Py_CLEAR(o->mapping); - Py_CLEAR(o->keys); - PyObject_GC_Del(o); -} - - -static int -record_desc_traverse(ApgRecordDescObject *o, visitproc visit, void *arg) -{ - Py_VISIT(o->mapping); - Py_VISIT(o->keys); - return 0; -} - - -PyTypeObject ApgRecordDesc_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "RecordDescriptor", - .tp_basicsize = sizeof(ApgRecordDescObject), - .tp_dealloc = (destructor)record_desc_dealloc, - .tp_getattro = PyObject_GenericGetAttr, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, - .tp_traverse = (traverseproc)record_desc_traverse, - .tp_iter = PyObject_SelfIter, -}; - - -PyObject * -ApgRecordDesc_New(PyObject *mapping, PyObject *keys) -{ - ApgRecordDescObject *o; - - if (!mapping || !keys || !PyTuple_CheckExact(keys)) { - PyErr_BadInternalCall(); - return NULL; - } - - o = PyObject_GC_New(ApgRecordDescObject, &ApgRecordDesc_Type); - if (o == NULL) { - return NULL; - } - - Py_INCREF(mapping); - o->mapping = mapping; - - Py_INCREF(keys); - o->keys = keys; - - PyObject_GC_Track(o); - return (PyObject *) o; -} diff --git a/asyncpg/protocol/record/recordobj.h b/asyncpg/protocol/record/recordobj.h deleted file mode 100644 index 373c8967..00000000 --- a/asyncpg/protocol/record/recordobj.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef APG_RECORDOBJ_H -#define APG_RECORDOBJ_H - -#include "Python.h" - - -/* Largest record to save on free list */ -#define ApgRecord_MAXSAVESIZE 20 - -/* Maximum number of records of each size to save */ -#define ApgRecord_MAXFREELIST 2000 - - -typedef struct { - PyObject_HEAD - PyObject *mapping; - PyObject *keys; -} ApgRecordDescObject; - - -typedef struct { - PyObject_VAR_HEAD - Py_hash_t self_hash; - ApgRecordDescObject *desc; - PyObject *ob_item[1]; - - /* ob_item contains space for 'ob_size' elements. - * Items must normally not be NULL, except during construction when - * the record is not yet visible outside the function that builds it. - */ -} ApgRecordObject; - - -extern PyTypeObject ApgRecord_Type; -extern PyTypeObject ApgRecordIter_Type; -extern PyTypeObject ApgRecordItems_Type; - -extern PyTypeObject ApgRecordDesc_Type; - -#define ApgRecord_Check(self) PyObject_TypeCheck(self, &ApgRecord_Type) -#define ApgRecord_CheckExact(o) (Py_TYPE(o) == &ApgRecord_Type) -#define ApgRecordDesc_CheckExact(o) (Py_TYPE(o) == &ApgRecordDesc_Type) - -#define ApgRecord_SET_ITEM(op, i, v) \ - (((ApgRecordObject *)(op))->ob_item[i] = v) -#define ApgRecord_GET_ITEM(op, i) \ - (((ApgRecordObject *)(op))->ob_item[i]) - -PyTypeObject *ApgRecord_InitTypes(void); -PyObject *ApgRecord_New(PyTypeObject *, PyObject *, Py_ssize_t); -PyObject *ApgRecordDesc_New(PyObject *, PyObject *); - -#endif diff --git a/asyncpg/protocol/record/__init__.pxd b/asyncpg/protocol/recordcapi.pxd similarity index 56% rename from asyncpg/protocol/record/__init__.pxd rename to asyncpg/protocol/recordcapi.pxd index 43ac5e33..3e994131 100644 --- a/asyncpg/protocol/record/__init__.pxd +++ b/asyncpg/protocol/recordcapi.pxd @@ -8,12 +8,7 @@ cimport cpython -cdef extern from "record/recordobj.h": +cdef extern from "../record/recordobj.h": - cpython.PyTypeObject *ApgRecord_InitTypes() except NULL - - int ApgRecord_CheckExact(object) - object ApgRecord_New(type, object, int) void ApgRecord_SET_ITEM(object, int, object) - - object ApgRecordDesc_New(object, object) + object RecordDescriptor(object, object) diff --git a/asyncpg/record.pyi b/asyncpg/record.pyi new file mode 100644 index 00000000..308f3109 --- /dev/null +++ b/asyncpg/record.pyi @@ -0,0 +1,29 @@ +from typing import ( + Any, + TypeVar, + overload, +) + +from collections.abc import Iterator + + +_T = TypeVar("_T") + + +class Record: + @overload + def get(self, key: str) -> Any | None: ... + @overload + def get(self, key: str, default: _T) -> Any | _T: ... + def items(self) -> Iterator[tuple[str, Any]]: ... + def keys(self) -> Iterator[str]: ... + def values(self) -> Iterator[Any]: ... + @overload + def __getitem__(self, index: str) -> Any: ... + @overload + def __getitem__(self, index: int) -> Any: ... + @overload + def __getitem__(self, index: slice) -> tuple[Any, ...]: ... + def __iter__(self) -> Iterator[Any]: ... + def __contains__(self, x: object) -> bool: ... + def __len__(self) -> int: ... diff --git a/asyncpg/record/pythoncapi_compat.h b/asyncpg/record/pythoncapi_compat.h new file mode 100644 index 00000000..6a7037ef --- /dev/null +++ b/asyncpg/record/pythoncapi_compat.h @@ -0,0 +1,2559 @@ +// Header file providing new C API functions to old Python versions. +// +// File distributed under the Zero Clause BSD (0BSD) license. +// Copyright Contributors to the pythoncapi_compat project. +// +// Homepage: +// https://github.com/python/pythoncapi_compat +// +// Latest version: +// https://raw.githubusercontent.com/python/pythoncapi-compat/main/pythoncapi_compat.h +// +// SPDX-License-Identifier: 0BSD + +#ifndef PYTHONCAPI_COMPAT +#define PYTHONCAPI_COMPAT + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include // offsetof() + +// Python 3.11.0b4 added PyFrame_Back() to Python.h +#if PY_VERSION_HEX < 0x030b00B4 && !defined(PYPY_VERSION) +# include "frameobject.h" // PyFrameObject, PyFrame_GetBack() +#endif +#if PY_VERSION_HEX < 0x030C00A3 +# include // T_SHORT, READONLY +#endif + + +#ifndef _Py_CAST +# define _Py_CAST(type, expr) ((type)(expr)) +#endif + +// Static inline functions should use _Py_NULL rather than using directly NULL +// to prevent C++ compiler warnings. On C23 and newer and on C++11 and newer, +// _Py_NULL is defined as nullptr. +#ifndef _Py_NULL +# if (defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L) \ + || (defined(__cplusplus) && __cplusplus >= 201103) +# define _Py_NULL nullptr +# else +# define _Py_NULL NULL +# endif +#endif + +// Cast argument to PyObject* type. +#ifndef _PyObject_CAST +# define _PyObject_CAST(op) _Py_CAST(PyObject*, op) +#endif + +#ifndef Py_BUILD_ASSERT +# define Py_BUILD_ASSERT(cond) \ + do { \ + (void)sizeof(char [1 - 2 * !(cond)]); \ + } while(0) +#endif + + +// bpo-42262 added Py_NewRef() to Python 3.10.0a3 +#if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_NewRef) +static inline PyObject* _Py_NewRef(PyObject *obj) +{ + Py_INCREF(obj); + return obj; +} +#define Py_NewRef(obj) _Py_NewRef(_PyObject_CAST(obj)) +#endif + + +// bpo-42262 added Py_XNewRef() to Python 3.10.0a3 +#if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_XNewRef) +static inline PyObject* _Py_XNewRef(PyObject *obj) +{ + Py_XINCREF(obj); + return obj; +} +#define Py_XNewRef(obj) _Py_XNewRef(_PyObject_CAST(obj)) +#endif + + +// bpo-39573 added Py_SET_REFCNT() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_REFCNT) +static inline void _Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) +{ + ob->ob_refcnt = refcnt; +} +#define Py_SET_REFCNT(ob, refcnt) _Py_SET_REFCNT(_PyObject_CAST(ob), refcnt) +#endif + + +// Py_SETREF() and Py_XSETREF() were added to Python 3.5.2. +// It is excluded from the limited C API. +#if (PY_VERSION_HEX < 0x03050200 && !defined(Py_SETREF)) && !defined(Py_LIMITED_API) +#define Py_SETREF(dst, src) \ + do { \ + PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \ + PyObject *_tmp_dst = (*_tmp_dst_ptr); \ + *_tmp_dst_ptr = _PyObject_CAST(src); \ + Py_DECREF(_tmp_dst); \ + } while (0) + +#define Py_XSETREF(dst, src) \ + do { \ + PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \ + PyObject *_tmp_dst = (*_tmp_dst_ptr); \ + *_tmp_dst_ptr = _PyObject_CAST(src); \ + Py_XDECREF(_tmp_dst); \ + } while (0) +#endif + + +// bpo-43753 added Py_Is(), Py_IsNone(), Py_IsTrue() and Py_IsFalse() +// to Python 3.10.0b1. +#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_Is) +# define Py_Is(x, y) ((x) == (y)) +#endif +#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_IsNone) +# define Py_IsNone(x) Py_Is(x, Py_None) +#endif +#if (PY_VERSION_HEX < 0x030A00B1 || defined(PYPY_VERSION)) && !defined(Py_IsTrue) +# define Py_IsTrue(x) Py_Is(x, Py_True) +#endif +#if (PY_VERSION_HEX < 0x030A00B1 || defined(PYPY_VERSION)) && !defined(Py_IsFalse) +# define Py_IsFalse(x) Py_Is(x, Py_False) +#endif + + +// bpo-39573 added Py_SET_TYPE() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_TYPE) +static inline void _Py_SET_TYPE(PyObject *ob, PyTypeObject *type) +{ + ob->ob_type = type; +} +#define Py_SET_TYPE(ob, type) _Py_SET_TYPE(_PyObject_CAST(ob), type) +#endif + + +// bpo-39573 added Py_SET_SIZE() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_SIZE) +static inline void _Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size) +{ + ob->ob_size = size; +} +#define Py_SET_SIZE(ob, size) _Py_SET_SIZE((PyVarObject*)(ob), size) +#endif + + +// bpo-40421 added PyFrame_GetCode() to Python 3.9.0b1 +#if PY_VERSION_HEX < 0x030900B1 || defined(PYPY_VERSION) +static inline PyCodeObject* PyFrame_GetCode(PyFrameObject *frame) +{ + assert(frame != _Py_NULL); + assert(frame->f_code != _Py_NULL); + return _Py_CAST(PyCodeObject*, Py_NewRef(frame->f_code)); +} +#endif + +static inline PyCodeObject* _PyFrame_GetCodeBorrow(PyFrameObject *frame) +{ + PyCodeObject *code = PyFrame_GetCode(frame); + Py_DECREF(code); + return code; +} + + +// bpo-40421 added PyFrame_GetBack() to Python 3.9.0b1 +#if PY_VERSION_HEX < 0x030900B1 && !defined(PYPY_VERSION) +static inline PyFrameObject* PyFrame_GetBack(PyFrameObject *frame) +{ + assert(frame != _Py_NULL); + return _Py_CAST(PyFrameObject*, Py_XNewRef(frame->f_back)); +} +#endif + +#if !defined(PYPY_VERSION) +static inline PyFrameObject* _PyFrame_GetBackBorrow(PyFrameObject *frame) +{ + PyFrameObject *back = PyFrame_GetBack(frame); + Py_XDECREF(back); + return back; +} +#endif + + +// bpo-40421 added PyFrame_GetLocals() to Python 3.11.0a7 +#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION) +static inline PyObject* PyFrame_GetLocals(PyFrameObject *frame) +{ +#if PY_VERSION_HEX >= 0x030400B1 + if (PyFrame_FastToLocalsWithError(frame) < 0) { + return NULL; + } +#else + PyFrame_FastToLocals(frame); +#endif + return Py_NewRef(frame->f_locals); +} +#endif + + +// bpo-40421 added PyFrame_GetGlobals() to Python 3.11.0a7 +#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION) +static inline PyObject* PyFrame_GetGlobals(PyFrameObject *frame) +{ + return Py_NewRef(frame->f_globals); +} +#endif + + +// bpo-40421 added PyFrame_GetBuiltins() to Python 3.11.0a7 +#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION) +static inline PyObject* PyFrame_GetBuiltins(PyFrameObject *frame) +{ + return Py_NewRef(frame->f_builtins); +} +#endif + + +// bpo-40421 added PyFrame_GetLasti() to Python 3.11.0b1 +#if PY_VERSION_HEX < 0x030B00B1 && !defined(PYPY_VERSION) +static inline int PyFrame_GetLasti(PyFrameObject *frame) +{ +#if PY_VERSION_HEX >= 0x030A00A7 + // bpo-27129: Since Python 3.10.0a7, f_lasti is an instruction offset, + // not a bytes offset anymore. Python uses 16-bit "wordcode" (2 bytes) + // instructions. + if (frame->f_lasti < 0) { + return -1; + } + return frame->f_lasti * 2; +#else + return frame->f_lasti; +#endif +} +#endif + + +// gh-91248 added PyFrame_GetVar() to Python 3.12.0a2 +#if PY_VERSION_HEX < 0x030C00A2 && !defined(PYPY_VERSION) +static inline PyObject* PyFrame_GetVar(PyFrameObject *frame, PyObject *name) +{ + PyObject *locals, *value; + + locals = PyFrame_GetLocals(frame); + if (locals == NULL) { + return NULL; + } +#if PY_VERSION_HEX >= 0x03000000 + value = PyDict_GetItemWithError(locals, name); +#else + value = _PyDict_GetItemWithError(locals, name); +#endif + Py_DECREF(locals); + + if (value == NULL) { + if (PyErr_Occurred()) { + return NULL; + } +#if PY_VERSION_HEX >= 0x03000000 + PyErr_Format(PyExc_NameError, "variable %R does not exist", name); +#else + PyErr_SetString(PyExc_NameError, "variable does not exist"); +#endif + return NULL; + } + return Py_NewRef(value); +} +#endif + + +// gh-91248 added PyFrame_GetVarString() to Python 3.12.0a2 +#if PY_VERSION_HEX < 0x030C00A2 && !defined(PYPY_VERSION) +static inline PyObject* +PyFrame_GetVarString(PyFrameObject *frame, const char *name) +{ + PyObject *name_obj, *value; +#if PY_VERSION_HEX >= 0x03000000 + name_obj = PyUnicode_FromString(name); +#else + name_obj = PyString_FromString(name); +#endif + if (name_obj == NULL) { + return NULL; + } + value = PyFrame_GetVar(frame, name_obj); + Py_DECREF(name_obj); + return value; +} +#endif + + +// bpo-39947 added PyThreadState_GetInterpreter() to Python 3.9.0a5 +#if PY_VERSION_HEX < 0x030900A5 || (defined(PYPY_VERSION) && PY_VERSION_HEX < 0x030B0000) +static inline PyInterpreterState * +PyThreadState_GetInterpreter(PyThreadState *tstate) +{ + assert(tstate != _Py_NULL); + return tstate->interp; +} +#endif + + +// bpo-40429 added PyThreadState_GetFrame() to Python 3.9.0b1 +#if PY_VERSION_HEX < 0x030900B1 && !defined(PYPY_VERSION) +static inline PyFrameObject* PyThreadState_GetFrame(PyThreadState *tstate) +{ + assert(tstate != _Py_NULL); + return _Py_CAST(PyFrameObject *, Py_XNewRef(tstate->frame)); +} +#endif + +#if !defined(PYPY_VERSION) +static inline PyFrameObject* +_PyThreadState_GetFrameBorrow(PyThreadState *tstate) +{ + PyFrameObject *frame = PyThreadState_GetFrame(tstate); + Py_XDECREF(frame); + return frame; +} +#endif + + +// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a5 +#if PY_VERSION_HEX < 0x030900A5 || defined(PYPY_VERSION) +static inline PyInterpreterState* PyInterpreterState_Get(void) +{ + PyThreadState *tstate; + PyInterpreterState *interp; + + tstate = PyThreadState_GET(); + if (tstate == _Py_NULL) { + Py_FatalError("GIL released (tstate is NULL)"); + } + interp = tstate->interp; + if (interp == _Py_NULL) { + Py_FatalError("no current interpreter"); + } + return interp; +} +#endif + + +// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a6 +#if 0x030700A1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030900A6 && !defined(PYPY_VERSION) +static inline uint64_t PyThreadState_GetID(PyThreadState *tstate) +{ + assert(tstate != _Py_NULL); + return tstate->id; +} +#endif + +// bpo-43760 added PyThreadState_EnterTracing() to Python 3.11.0a2 +#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION) +static inline void PyThreadState_EnterTracing(PyThreadState *tstate) +{ + tstate->tracing++; +#if PY_VERSION_HEX >= 0x030A00A1 + tstate->cframe->use_tracing = 0; +#else + tstate->use_tracing = 0; +#endif +} +#endif + +// bpo-43760 added PyThreadState_LeaveTracing() to Python 3.11.0a2 +#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION) +static inline void PyThreadState_LeaveTracing(PyThreadState *tstate) +{ + int use_tracing = (tstate->c_tracefunc != _Py_NULL + || tstate->c_profilefunc != _Py_NULL); + tstate->tracing--; +#if PY_VERSION_HEX >= 0x030A00A1 + tstate->cframe->use_tracing = use_tracing; +#else + tstate->use_tracing = use_tracing; +#endif +} +#endif + + +// bpo-37194 added PyObject_CallNoArgs() to Python 3.9.0a1 +// PyObject_CallNoArgs() added to PyPy 3.9.16-v7.3.11 +#if !defined(PyObject_CallNoArgs) && PY_VERSION_HEX < 0x030900A1 +static inline PyObject* PyObject_CallNoArgs(PyObject *func) +{ + return PyObject_CallFunctionObjArgs(func, NULL); +} +#endif + + +// bpo-39245 made PyObject_CallOneArg() public (previously called +// _PyObject_CallOneArg) in Python 3.9.0a4 +// PyObject_CallOneArg() added to PyPy 3.9.16-v7.3.11 +#if !defined(PyObject_CallOneArg) && PY_VERSION_HEX < 0x030900A4 +static inline PyObject* PyObject_CallOneArg(PyObject *func, PyObject *arg) +{ + return PyObject_CallFunctionObjArgs(func, arg, NULL); +} +#endif + + +// bpo-1635741 added PyModule_AddObjectRef() to Python 3.10.0a3 +#if PY_VERSION_HEX < 0x030A00A3 +static inline int +PyModule_AddObjectRef(PyObject *module, const char *name, PyObject *value) +{ + int res; + + if (!value && !PyErr_Occurred()) { + // PyModule_AddObject() raises TypeError in this case + PyErr_SetString(PyExc_SystemError, + "PyModule_AddObjectRef() must be called " + "with an exception raised if value is NULL"); + return -1; + } + + Py_XINCREF(value); + res = PyModule_AddObject(module, name, value); + if (res < 0) { + Py_XDECREF(value); + } + return res; +} +#endif + + +// bpo-40024 added PyModule_AddType() to Python 3.9.0a5 +#if PY_VERSION_HEX < 0x030900A5 +static inline int PyModule_AddType(PyObject *module, PyTypeObject *type) +{ + const char *name, *dot; + + if (PyType_Ready(type) < 0) { + return -1; + } + + // inline _PyType_Name() + name = type->tp_name; + assert(name != _Py_NULL); + dot = strrchr(name, '.'); + if (dot != _Py_NULL) { + name = dot + 1; + } + + return PyModule_AddObjectRef(module, name, _PyObject_CAST(type)); +} +#endif + + +// bpo-40241 added PyObject_GC_IsTracked() to Python 3.9.0a6. +// bpo-4688 added _PyObject_GC_IS_TRACKED() to Python 2.7.0a2. +#if PY_VERSION_HEX < 0x030900A6 && !defined(PYPY_VERSION) +static inline int PyObject_GC_IsTracked(PyObject* obj) +{ + return (PyObject_IS_GC(obj) && _PyObject_GC_IS_TRACKED(obj)); +} +#endif + +// bpo-40241 added PyObject_GC_IsFinalized() to Python 3.9.0a6. +// bpo-18112 added _PyGCHead_FINALIZED() to Python 3.4.0 final. +#if PY_VERSION_HEX < 0x030900A6 && PY_VERSION_HEX >= 0x030400F0 && !defined(PYPY_VERSION) +static inline int PyObject_GC_IsFinalized(PyObject *obj) +{ + PyGC_Head *gc = _Py_CAST(PyGC_Head*, obj) - 1; + return (PyObject_IS_GC(obj) && _PyGCHead_FINALIZED(gc)); +} +#endif + + +// bpo-39573 added Py_IS_TYPE() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_IS_TYPE) +static inline int _Py_IS_TYPE(PyObject *ob, PyTypeObject *type) { + return Py_TYPE(ob) == type; +} +#define Py_IS_TYPE(ob, type) _Py_IS_TYPE(_PyObject_CAST(ob), type) +#endif + + +// bpo-46906 added PyFloat_Pack2() and PyFloat_Unpack2() to Python 3.11a7. +// bpo-11734 added _PyFloat_Pack2() and _PyFloat_Unpack2() to Python 3.6.0b1. +// Python 3.11a2 moved _PyFloat_Pack2() and _PyFloat_Unpack2() to the internal +// C API: Python 3.11a2-3.11a6 versions are not supported. +#if 0x030600B1 <= PY_VERSION_HEX && PY_VERSION_HEX <= 0x030B00A1 && !defined(PYPY_VERSION) +static inline int PyFloat_Pack2(double x, char *p, int le) +{ return _PyFloat_Pack2(x, (unsigned char*)p, le); } + +static inline double PyFloat_Unpack2(const char *p, int le) +{ return _PyFloat_Unpack2((const unsigned char *)p, le); } +#endif + + +// bpo-46906 added PyFloat_Pack4(), PyFloat_Pack8(), PyFloat_Unpack4() and +// PyFloat_Unpack8() to Python 3.11a7. +// Python 3.11a2 moved _PyFloat_Pack4(), _PyFloat_Pack8(), _PyFloat_Unpack4() +// and _PyFloat_Unpack8() to the internal C API: Python 3.11a2-3.11a6 versions +// are not supported. +#if PY_VERSION_HEX <= 0x030B00A1 && !defined(PYPY_VERSION) +static inline int PyFloat_Pack4(double x, char *p, int le) +{ return _PyFloat_Pack4(x, (unsigned char*)p, le); } + +static inline int PyFloat_Pack8(double x, char *p, int le) +{ return _PyFloat_Pack8(x, (unsigned char*)p, le); } + +static inline double PyFloat_Unpack4(const char *p, int le) +{ return _PyFloat_Unpack4((const unsigned char *)p, le); } + +static inline double PyFloat_Unpack8(const char *p, int le) +{ return _PyFloat_Unpack8((const unsigned char *)p, le); } +#endif + + +// gh-92154 added PyCode_GetCode() to Python 3.11.0b1 +#if PY_VERSION_HEX < 0x030B00B1 && !defined(PYPY_VERSION) +static inline PyObject* PyCode_GetCode(PyCodeObject *code) +{ + return Py_NewRef(code->co_code); +} +#endif + + +// gh-95008 added PyCode_GetVarnames() to Python 3.11.0rc1 +#if PY_VERSION_HEX < 0x030B00C1 && !defined(PYPY_VERSION) +static inline PyObject* PyCode_GetVarnames(PyCodeObject *code) +{ + return Py_NewRef(code->co_varnames); +} +#endif + +// gh-95008 added PyCode_GetFreevars() to Python 3.11.0rc1 +#if PY_VERSION_HEX < 0x030B00C1 && !defined(PYPY_VERSION) +static inline PyObject* PyCode_GetFreevars(PyCodeObject *code) +{ + return Py_NewRef(code->co_freevars); +} +#endif + +// gh-95008 added PyCode_GetCellvars() to Python 3.11.0rc1 +#if PY_VERSION_HEX < 0x030B00C1 && !defined(PYPY_VERSION) +static inline PyObject* PyCode_GetCellvars(PyCodeObject *code) +{ + return Py_NewRef(code->co_cellvars); +} +#endif + + +// Py_UNUSED() was added to Python 3.4.0b2. +#if PY_VERSION_HEX < 0x030400B2 && !defined(Py_UNUSED) +# if defined(__GNUC__) || defined(__clang__) +# define Py_UNUSED(name) _unused_ ## name __attribute__((unused)) +# else +# define Py_UNUSED(name) _unused_ ## name +# endif +#endif + + +// gh-105922 added PyImport_AddModuleRef() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A0 +static inline PyObject* PyImport_AddModuleRef(const char *name) +{ + return Py_XNewRef(PyImport_AddModule(name)); +} +#endif + + +// gh-105927 added PyWeakref_GetRef() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D0000 +static inline int PyWeakref_GetRef(PyObject *ref, PyObject **pobj) +{ + PyObject *obj; + if (ref != NULL && !PyWeakref_Check(ref)) { + *pobj = NULL; + PyErr_SetString(PyExc_TypeError, "expected a weakref"); + return -1; + } + obj = PyWeakref_GetObject(ref); + if (obj == NULL) { + // SystemError if ref is NULL + *pobj = NULL; + return -1; + } + if (obj == Py_None) { + *pobj = NULL; + return 0; + } + *pobj = Py_NewRef(obj); + return 1; +} +#endif + + +// bpo-36974 added PY_VECTORCALL_ARGUMENTS_OFFSET to Python 3.8b1 +#ifndef PY_VECTORCALL_ARGUMENTS_OFFSET +# define PY_VECTORCALL_ARGUMENTS_OFFSET (_Py_CAST(size_t, 1) << (8 * sizeof(size_t) - 1)) +#endif + +// bpo-36974 added PyVectorcall_NARGS() to Python 3.8b1 +#if PY_VERSION_HEX < 0x030800B1 +static inline Py_ssize_t PyVectorcall_NARGS(size_t n) +{ + return n & ~PY_VECTORCALL_ARGUMENTS_OFFSET; +} +#endif + + +// gh-105922 added PyObject_Vectorcall() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 +static inline PyObject* +PyObject_Vectorcall(PyObject *callable, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ +#if PY_VERSION_HEX >= 0x030800B1 && !defined(PYPY_VERSION) + // bpo-36974 added _PyObject_Vectorcall() to Python 3.8.0b1 + return _PyObject_Vectorcall(callable, args, nargsf, kwnames); +#else + PyObject *posargs = NULL, *kwargs = NULL; + PyObject *res; + Py_ssize_t nposargs, nkwargs, i; + + if (nargsf != 0 && args == NULL) { + PyErr_BadInternalCall(); + goto error; + } + if (kwnames != NULL && !PyTuple_Check(kwnames)) { + PyErr_BadInternalCall(); + goto error; + } + + nposargs = (Py_ssize_t)PyVectorcall_NARGS(nargsf); + if (kwnames) { + nkwargs = PyTuple_GET_SIZE(kwnames); + } + else { + nkwargs = 0; + } + + posargs = PyTuple_New(nposargs); + if (posargs == NULL) { + goto error; + } + if (nposargs) { + for (i=0; i < nposargs; i++) { + PyTuple_SET_ITEM(posargs, i, Py_NewRef(*args)); + args++; + } + } + + if (nkwargs) { + kwargs = PyDict_New(); + if (kwargs == NULL) { + goto error; + } + + for (i = 0; i < nkwargs; i++) { + PyObject *key = PyTuple_GET_ITEM(kwnames, i); + PyObject *value = *args; + args++; + if (PyDict_SetItem(kwargs, key, value) < 0) { + goto error; + } + } + } + else { + kwargs = NULL; + } + + res = PyObject_Call(callable, posargs, kwargs); + Py_DECREF(posargs); + Py_XDECREF(kwargs); + return res; + +error: + Py_DECREF(posargs); + Py_XDECREF(kwargs); + return NULL; +#endif +} +#endif + + +// gh-106521 added PyObject_GetOptionalAttr() and +// PyObject_GetOptionalAttrString() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyObject_GetOptionalAttr(PyObject *obj, PyObject *attr_name, PyObject **result) +{ + // bpo-32571 added _PyObject_LookupAttr() to Python 3.7.0b1 +#if PY_VERSION_HEX >= 0x030700B1 && !defined(PYPY_VERSION) + return _PyObject_LookupAttr(obj, attr_name, result); +#else + *result = PyObject_GetAttr(obj, attr_name); + if (*result != NULL) { + return 1; + } + if (!PyErr_Occurred()) { + return 0; + } + if (PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); + return 0; + } + return -1; +#endif +} + +static inline int +PyObject_GetOptionalAttrString(PyObject *obj, const char *attr_name, PyObject **result) +{ + PyObject *name_obj; + int rc; +#if PY_VERSION_HEX >= 0x03000000 + name_obj = PyUnicode_FromString(attr_name); +#else + name_obj = PyString_FromString(attr_name); +#endif + if (name_obj == NULL) { + *result = NULL; + return -1; + } + rc = PyObject_GetOptionalAttr(obj, name_obj, result); + Py_DECREF(name_obj); + return rc; +} +#endif + + +// gh-106307 added PyObject_GetOptionalAttr() and +// PyMapping_GetOptionalItemString() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyMapping_GetOptionalItem(PyObject *obj, PyObject *key, PyObject **result) +{ + *result = PyObject_GetItem(obj, key); + if (*result) { + return 1; + } + if (!PyErr_ExceptionMatches(PyExc_KeyError)) { + return -1; + } + PyErr_Clear(); + return 0; +} + +static inline int +PyMapping_GetOptionalItemString(PyObject *obj, const char *key, PyObject **result) +{ + PyObject *key_obj; + int rc; +#if PY_VERSION_HEX >= 0x03000000 + key_obj = PyUnicode_FromString(key); +#else + key_obj = PyString_FromString(key); +#endif + if (key_obj == NULL) { + *result = NULL; + return -1; + } + rc = PyMapping_GetOptionalItem(obj, key_obj, result); + Py_DECREF(key_obj); + return rc; +} +#endif + +// gh-108511 added PyMapping_HasKeyWithError() and +// PyMapping_HasKeyStringWithError() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyMapping_HasKeyWithError(PyObject *obj, PyObject *key) +{ + PyObject *res; + int rc = PyMapping_GetOptionalItem(obj, key, &res); + Py_XDECREF(res); + return rc; +} + +static inline int +PyMapping_HasKeyStringWithError(PyObject *obj, const char *key) +{ + PyObject *res; + int rc = PyMapping_GetOptionalItemString(obj, key, &res); + Py_XDECREF(res); + return rc; +} +#endif + + +// gh-108511 added PyObject_HasAttrWithError() and +// PyObject_HasAttrStringWithError() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyObject_HasAttrWithError(PyObject *obj, PyObject *attr) +{ + PyObject *res; + int rc = PyObject_GetOptionalAttr(obj, attr, &res); + Py_XDECREF(res); + return rc; +} + +static inline int +PyObject_HasAttrStringWithError(PyObject *obj, const char *attr) +{ + PyObject *res; + int rc = PyObject_GetOptionalAttrString(obj, attr, &res); + Py_XDECREF(res); + return rc; +} +#endif + + +// gh-106004 added PyDict_GetItemRef() and PyDict_GetItemStringRef() +// to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyDict_GetItemRef(PyObject *mp, PyObject *key, PyObject **result) +{ +#if PY_VERSION_HEX >= 0x03000000 + PyObject *item = PyDict_GetItemWithError(mp, key); +#else + PyObject *item = _PyDict_GetItemWithError(mp, key); +#endif + if (item != NULL) { + *result = Py_NewRef(item); + return 1; // found + } + if (!PyErr_Occurred()) { + *result = NULL; + return 0; // not found + } + *result = NULL; + return -1; +} + +static inline int +PyDict_GetItemStringRef(PyObject *mp, const char *key, PyObject **result) +{ + int res; +#if PY_VERSION_HEX >= 0x03000000 + PyObject *key_obj = PyUnicode_FromString(key); +#else + PyObject *key_obj = PyString_FromString(key); +#endif + if (key_obj == NULL) { + *result = NULL; + return -1; + } + res = PyDict_GetItemRef(mp, key_obj, result); + Py_DECREF(key_obj); + return res; +} +#endif + + +// gh-106307 added PyModule_Add() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyModule_Add(PyObject *mod, const char *name, PyObject *value) +{ + int res = PyModule_AddObjectRef(mod, name, value); + Py_XDECREF(value); + return res; +} +#endif + + +// gh-108014 added Py_IsFinalizing() to Python 3.13.0a1 +// bpo-1856 added _Py_Finalizing to Python 3.2.1b1. +// _Py_IsFinalizing() was added to PyPy 7.3.0. +#if (0x030201B1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030D00A1) \ + && (!defined(PYPY_VERSION_NUM) || PYPY_VERSION_NUM >= 0x7030000) +static inline int Py_IsFinalizing(void) +{ +#if PY_VERSION_HEX >= 0x030700A1 + // _Py_IsFinalizing() was added to Python 3.7.0a1. + return _Py_IsFinalizing(); +#else + return (_Py_Finalizing != NULL); +#endif +} +#endif + + +// gh-108323 added PyDict_ContainsString() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int PyDict_ContainsString(PyObject *op, const char *key) +{ + PyObject *key_obj = PyUnicode_FromString(key); + if (key_obj == NULL) { + return -1; + } + int res = PyDict_Contains(op, key_obj); + Py_DECREF(key_obj); + return res; +} +#endif + + +// gh-108445 added PyLong_AsInt() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int PyLong_AsInt(PyObject *obj) +{ +#ifdef PYPY_VERSION + long value = PyLong_AsLong(obj); + if (value == -1 && PyErr_Occurred()) { + return -1; + } + if (value < (long)INT_MIN || (long)INT_MAX < value) { + PyErr_SetString(PyExc_OverflowError, + "Python int too large to convert to C int"); + return -1; + } + return (int)value; +#else + return _PyLong_AsInt(obj); +#endif +} +#endif + + +// gh-107073 added PyObject_VisitManagedDict() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg) +{ + PyObject **dict = _PyObject_GetDictPtr(obj); + if (dict == NULL || *dict == NULL) { + return -1; + } + Py_VISIT(*dict); + return 0; +} + +static inline void +PyObject_ClearManagedDict(PyObject *obj) +{ + PyObject **dict = _PyObject_GetDictPtr(obj); + if (dict == NULL || *dict == NULL) { + return; + } + Py_CLEAR(*dict); +} +#endif + +// gh-108867 added PyThreadState_GetUnchecked() to Python 3.13.0a1 +// Python 3.5.2 added _PyThreadState_UncheckedGet(). +#if PY_VERSION_HEX >= 0x03050200 && PY_VERSION_HEX < 0x030D00A1 +static inline PyThreadState* +PyThreadState_GetUnchecked(void) +{ + return _PyThreadState_UncheckedGet(); +} +#endif + +// gh-110289 added PyUnicode_EqualToUTF8() and PyUnicode_EqualToUTF8AndSize() +// to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyUnicode_EqualToUTF8AndSize(PyObject *unicode, const char *str, Py_ssize_t str_len) +{ + Py_ssize_t len; + const void *utf8; + PyObject *exc_type, *exc_value, *exc_tb; + int res; + + // API cannot report errors so save/restore the exception + PyErr_Fetch(&exc_type, &exc_value, &exc_tb); + + // Python 3.3.0a1 added PyUnicode_AsUTF8AndSize() +#if PY_VERSION_HEX >= 0x030300A1 + if (PyUnicode_IS_ASCII(unicode)) { + utf8 = PyUnicode_DATA(unicode); + len = PyUnicode_GET_LENGTH(unicode); + } + else { + utf8 = PyUnicode_AsUTF8AndSize(unicode, &len); + if (utf8 == NULL) { + // Memory allocation failure. The API cannot report error, + // so ignore the exception and return 0. + res = 0; + goto done; + } + } + + if (len != str_len) { + res = 0; + goto done; + } + res = (memcmp(utf8, str, (size_t)len) == 0); +#else + PyObject *bytes = PyUnicode_AsUTF8String(unicode); + if (bytes == NULL) { + // Memory allocation failure. The API cannot report error, + // so ignore the exception and return 0. + res = 0; + goto done; + } + +#if PY_VERSION_HEX >= 0x03000000 + len = PyBytes_GET_SIZE(bytes); + utf8 = PyBytes_AS_STRING(bytes); +#else + len = PyString_GET_SIZE(bytes); + utf8 = PyString_AS_STRING(bytes); +#endif + if (len != str_len) { + Py_DECREF(bytes); + res = 0; + goto done; + } + + res = (memcmp(utf8, str, (size_t)len) == 0); + Py_DECREF(bytes); +#endif + +done: + PyErr_Restore(exc_type, exc_value, exc_tb); + return res; +} + +static inline int +PyUnicode_EqualToUTF8(PyObject *unicode, const char *str) +{ + return PyUnicode_EqualToUTF8AndSize(unicode, str, (Py_ssize_t)strlen(str)); +} +#endif + + +// gh-111138 added PyList_Extend() and PyList_Clear() to Python 3.13.0a2 +#if PY_VERSION_HEX < 0x030D00A2 +static inline int +PyList_Extend(PyObject *list, PyObject *iterable) +{ + return PyList_SetSlice(list, PY_SSIZE_T_MAX, PY_SSIZE_T_MAX, iterable); +} + +static inline int +PyList_Clear(PyObject *list) +{ + return PyList_SetSlice(list, 0, PY_SSIZE_T_MAX, NULL); +} +#endif + +// gh-111262 added PyDict_Pop() and PyDict_PopString() to Python 3.13.0a2 +#if PY_VERSION_HEX < 0x030D00A2 +static inline int +PyDict_Pop(PyObject *dict, PyObject *key, PyObject **result) +{ + PyObject *value; + + if (!PyDict_Check(dict)) { + PyErr_BadInternalCall(); + if (result) { + *result = NULL; + } + return -1; + } + + // bpo-16991 added _PyDict_Pop() to Python 3.5.0b2. + // Python 3.6.0b3 changed _PyDict_Pop() first argument type to PyObject*. + // Python 3.13.0a1 removed _PyDict_Pop(). +#if defined(PYPY_VERSION) || PY_VERSION_HEX < 0x030500b2 || PY_VERSION_HEX >= 0x030D0000 + value = PyObject_CallMethod(dict, "pop", "O", key); +#elif PY_VERSION_HEX < 0x030600b3 + value = _PyDict_Pop(_Py_CAST(PyDictObject*, dict), key, NULL); +#else + value = _PyDict_Pop(dict, key, NULL); +#endif + if (value == NULL) { + if (result) { + *result = NULL; + } + if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_KeyError)) { + return -1; + } + PyErr_Clear(); + return 0; + } + if (result) { + *result = value; + } + else { + Py_DECREF(value); + } + return 1; +} + +static inline int +PyDict_PopString(PyObject *dict, const char *key, PyObject **result) +{ + PyObject *key_obj = PyUnicode_FromString(key); + if (key_obj == NULL) { + if (result != NULL) { + *result = NULL; + } + return -1; + } + + int res = PyDict_Pop(dict, key_obj, result); + Py_DECREF(key_obj); + return res; +} +#endif + + +#if PY_VERSION_HEX < 0x030200A4 +// Python 3.2.0a4 added Py_hash_t type +typedef Py_ssize_t Py_hash_t; +#endif + + +// gh-111545 added Py_HashPointer() to Python 3.13.0a3 +#if PY_VERSION_HEX < 0x030D00A3 +static inline Py_hash_t Py_HashPointer(const void *ptr) +{ +#if PY_VERSION_HEX >= 0x030900A4 && !defined(PYPY_VERSION) + return _Py_HashPointer(ptr); +#else + return _Py_HashPointer(_Py_CAST(void*, ptr)); +#endif +} +#endif + + +// Python 3.13a4 added a PyTime API. +// Use the private API added to Python 3.5. +#if PY_VERSION_HEX < 0x030D00A4 && PY_VERSION_HEX >= 0x03050000 +typedef _PyTime_t PyTime_t; +#define PyTime_MIN _PyTime_MIN +#define PyTime_MAX _PyTime_MAX + +static inline double PyTime_AsSecondsDouble(PyTime_t t) +{ return _PyTime_AsSecondsDouble(t); } + +static inline int PyTime_Monotonic(PyTime_t *result) +{ return _PyTime_GetMonotonicClockWithInfo(result, NULL); } + +static inline int PyTime_Time(PyTime_t *result) +{ return _PyTime_GetSystemClockWithInfo(result, NULL); } + +static inline int PyTime_PerfCounter(PyTime_t *result) +{ +#if PY_VERSION_HEX >= 0x03070000 && !defined(PYPY_VERSION) + return _PyTime_GetPerfCounterWithInfo(result, NULL); +#elif PY_VERSION_HEX >= 0x03070000 + // Call time.perf_counter_ns() and convert Python int object to PyTime_t. + // Cache time.perf_counter_ns() function for best performance. + static PyObject *func = NULL; + if (func == NULL) { + PyObject *mod = PyImport_ImportModule("time"); + if (mod == NULL) { + return -1; + } + + func = PyObject_GetAttrString(mod, "perf_counter_ns"); + Py_DECREF(mod); + if (func == NULL) { + return -1; + } + } + + PyObject *res = PyObject_CallNoArgs(func); + if (res == NULL) { + return -1; + } + long long value = PyLong_AsLongLong(res); + Py_DECREF(res); + + if (value == -1 && PyErr_Occurred()) { + return -1; + } + + Py_BUILD_ASSERT(sizeof(value) >= sizeof(PyTime_t)); + *result = (PyTime_t)value; + return 0; +#else + // Call time.perf_counter() and convert C double to PyTime_t. + // Cache time.perf_counter() function for best performance. + static PyObject *func = NULL; + if (func == NULL) { + PyObject *mod = PyImport_ImportModule("time"); + if (mod == NULL) { + return -1; + } + + func = PyObject_GetAttrString(mod, "perf_counter"); + Py_DECREF(mod); + if (func == NULL) { + return -1; + } + } + + PyObject *res = PyObject_CallNoArgs(func); + if (res == NULL) { + return -1; + } + double d = PyFloat_AsDouble(res); + Py_DECREF(res); + + if (d == -1.0 && PyErr_Occurred()) { + return -1; + } + + // Avoid floor() to avoid having to link to libm + *result = (PyTime_t)(d * 1e9); + return 0; +#endif +} + +#endif + +// gh-111389 added hash constants to Python 3.13.0a5. These constants were +// added first as private macros to Python 3.4.0b1 and PyPy 7.3.8. +#if (!defined(PyHASH_BITS) \ + && ((!defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x030400B1) \ + || (defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03070000 \ + && PYPY_VERSION_NUM >= 0x07030800))) +# define PyHASH_BITS _PyHASH_BITS +# define PyHASH_MODULUS _PyHASH_MODULUS +# define PyHASH_INF _PyHASH_INF +# define PyHASH_IMAG _PyHASH_IMAG +#endif + + +// gh-111545 added Py_GetConstant() and Py_GetConstantBorrowed() +// to Python 3.13.0a6 +#if PY_VERSION_HEX < 0x030D00A6 && !defined(Py_CONSTANT_NONE) + +#define Py_CONSTANT_NONE 0 +#define Py_CONSTANT_FALSE 1 +#define Py_CONSTANT_TRUE 2 +#define Py_CONSTANT_ELLIPSIS 3 +#define Py_CONSTANT_NOT_IMPLEMENTED 4 +#define Py_CONSTANT_ZERO 5 +#define Py_CONSTANT_ONE 6 +#define Py_CONSTANT_EMPTY_STR 7 +#define Py_CONSTANT_EMPTY_BYTES 8 +#define Py_CONSTANT_EMPTY_TUPLE 9 + +static inline PyObject* Py_GetConstant(unsigned int constant_id) +{ + static PyObject* constants[Py_CONSTANT_EMPTY_TUPLE + 1] = {NULL}; + + if (constants[Py_CONSTANT_NONE] == NULL) { + constants[Py_CONSTANT_NONE] = Py_None; + constants[Py_CONSTANT_FALSE] = Py_False; + constants[Py_CONSTANT_TRUE] = Py_True; + constants[Py_CONSTANT_ELLIPSIS] = Py_Ellipsis; + constants[Py_CONSTANT_NOT_IMPLEMENTED] = Py_NotImplemented; + + constants[Py_CONSTANT_ZERO] = PyLong_FromLong(0); + if (constants[Py_CONSTANT_ZERO] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_ONE] = PyLong_FromLong(1); + if (constants[Py_CONSTANT_ONE] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_EMPTY_STR] = PyUnicode_FromStringAndSize("", 0); + if (constants[Py_CONSTANT_EMPTY_STR] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_EMPTY_BYTES] = PyBytes_FromStringAndSize("", 0); + if (constants[Py_CONSTANT_EMPTY_BYTES] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_EMPTY_TUPLE] = PyTuple_New(0); + if (constants[Py_CONSTANT_EMPTY_TUPLE] == NULL) { + goto fatal_error; + } + // goto dance to avoid compiler warnings about Py_FatalError() + goto init_done; + +fatal_error: + // This case should never happen + Py_FatalError("Py_GetConstant() failed to get constants"); + } + +init_done: + if (constant_id <= Py_CONSTANT_EMPTY_TUPLE) { + return Py_NewRef(constants[constant_id]); + } + else { + PyErr_BadInternalCall(); + return NULL; + } +} + +static inline PyObject* Py_GetConstantBorrowed(unsigned int constant_id) +{ + PyObject *obj = Py_GetConstant(constant_id); + Py_XDECREF(obj); + return obj; +} +#endif + + +// gh-114329 added PyList_GetItemRef() to Python 3.13.0a4 +#if PY_VERSION_HEX < 0x030D00A4 +static inline PyObject * +PyList_GetItemRef(PyObject *op, Py_ssize_t index) +{ + PyObject *item = PyList_GetItem(op, index); + Py_XINCREF(item); + return item; +} +#endif + + +// gh-114329 added PyList_GetItemRef() to Python 3.13.0a4 +#if PY_VERSION_HEX < 0x030D00A4 +static inline int +PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value, + PyObject **result) +{ + PyObject *value; + if (PyDict_GetItemRef(d, key, &value) < 0) { + // get error + if (result) { + *result = NULL; + } + return -1; + } + if (value != NULL) { + // present + if (result) { + *result = value; + } + else { + Py_DECREF(value); + } + return 1; + } + + // missing: set the item + if (PyDict_SetItem(d, key, default_value) < 0) { + // set error + if (result) { + *result = NULL; + } + return -1; + } + if (result) { + *result = Py_NewRef(default_value); + } + return 0; +} +#endif + +#if PY_VERSION_HEX < 0x030D00B3 +# define Py_BEGIN_CRITICAL_SECTION(op) { +# define Py_END_CRITICAL_SECTION() } +# define Py_BEGIN_CRITICAL_SECTION2(a, b) { +# define Py_END_CRITICAL_SECTION2() } +#endif + +#if PY_VERSION_HEX < 0x030E0000 && PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION) +typedef struct PyUnicodeWriter PyUnicodeWriter; + +static inline void PyUnicodeWriter_Discard(PyUnicodeWriter *writer) +{ + _PyUnicodeWriter_Dealloc((_PyUnicodeWriter*)writer); + PyMem_Free(writer); +} + +static inline PyUnicodeWriter* PyUnicodeWriter_Create(Py_ssize_t length) +{ + if (length < 0) { + PyErr_SetString(PyExc_ValueError, + "length must be positive"); + return NULL; + } + + const size_t size = sizeof(_PyUnicodeWriter); + PyUnicodeWriter *pub_writer = (PyUnicodeWriter *)PyMem_Malloc(size); + if (pub_writer == _Py_NULL) { + PyErr_NoMemory(); + return _Py_NULL; + } + _PyUnicodeWriter *writer = (_PyUnicodeWriter *)pub_writer; + + _PyUnicodeWriter_Init(writer); + if (_PyUnicodeWriter_Prepare(writer, length, 127) < 0) { + PyUnicodeWriter_Discard(pub_writer); + return NULL; + } + writer->overallocate = 1; + return pub_writer; +} + +static inline PyObject* PyUnicodeWriter_Finish(PyUnicodeWriter *writer) +{ + PyObject *str = _PyUnicodeWriter_Finish((_PyUnicodeWriter*)writer); + assert(((_PyUnicodeWriter*)writer)->buffer == NULL); + PyMem_Free(writer); + return str; +} + +static inline int +PyUnicodeWriter_WriteChar(PyUnicodeWriter *writer, Py_UCS4 ch) +{ + if (ch > 0x10ffff) { + PyErr_SetString(PyExc_ValueError, + "character must be in range(0x110000)"); + return -1; + } + + return _PyUnicodeWriter_WriteChar((_PyUnicodeWriter*)writer, ch); +} + +static inline int +PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj) +{ + PyObject *str = PyObject_Str(obj); + if (str == NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str); + Py_DECREF(str); + return res; +} + +static inline int +PyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, PyObject *obj) +{ + PyObject *str = PyObject_Repr(obj); + if (str == NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str); + Py_DECREF(str); + return res; +} + +static inline int +PyUnicodeWriter_WriteUTF8(PyUnicodeWriter *writer, + const char *str, Py_ssize_t size) +{ + if (size < 0) { + size = (Py_ssize_t)strlen(str); + } + + PyObject *str_obj = PyUnicode_FromStringAndSize(str, size); + if (str_obj == _Py_NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str_obj); + Py_DECREF(str_obj); + return res; +} + +static inline int +PyUnicodeWriter_WriteASCII(PyUnicodeWriter *writer, + const char *str, Py_ssize_t size) +{ + if (size < 0) { + size = (Py_ssize_t)strlen(str); + } + + return _PyUnicodeWriter_WriteASCIIString((_PyUnicodeWriter*)writer, + str, size); +} + +static inline int +PyUnicodeWriter_WriteWideChar(PyUnicodeWriter *writer, + const wchar_t *str, Py_ssize_t size) +{ + if (size < 0) { + size = (Py_ssize_t)wcslen(str); + } + + PyObject *str_obj = PyUnicode_FromWideChar(str, size); + if (str_obj == _Py_NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str_obj); + Py_DECREF(str_obj); + return res; +} + +static inline int +PyUnicodeWriter_WriteSubstring(PyUnicodeWriter *writer, PyObject *str, + Py_ssize_t start, Py_ssize_t end) +{ + if (!PyUnicode_Check(str)) { + PyErr_Format(PyExc_TypeError, "expect str, not %s", + Py_TYPE(str)->tp_name); + return -1; + } + if (start < 0 || start > end) { + PyErr_Format(PyExc_ValueError, "invalid start argument"); + return -1; + } + if (end > PyUnicode_GET_LENGTH(str)) { + PyErr_Format(PyExc_ValueError, "invalid end argument"); + return -1; + } + + return _PyUnicodeWriter_WriteSubstring((_PyUnicodeWriter*)writer, str, + start, end); +} + +static inline int +PyUnicodeWriter_Format(PyUnicodeWriter *writer, const char *format, ...) +{ + va_list vargs; + va_start(vargs, format); + PyObject *str = PyUnicode_FromFormatV(format, vargs); + va_end(vargs); + if (str == _Py_NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str); + Py_DECREF(str); + return res; +} +#endif // PY_VERSION_HEX < 0x030E0000 + +// gh-116560 added PyLong_GetSign() to Python 3.14.0a0 +#if PY_VERSION_HEX < 0x030E00A0 +static inline int PyLong_GetSign(PyObject *obj, int *sign) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expect int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + + *sign = _PyLong_Sign(obj); + return 0; +} +#endif + +// gh-126061 added PyLong_IsPositive/Negative/Zero() to Python in 3.14.0a2 +#if PY_VERSION_HEX < 0x030E00A2 +static inline int PyLong_IsPositive(PyObject *obj) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expected int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + return _PyLong_Sign(obj) == 1; +} + +static inline int PyLong_IsNegative(PyObject *obj) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expected int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + return _PyLong_Sign(obj) == -1; +} + +static inline int PyLong_IsZero(PyObject *obj) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expected int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + return _PyLong_Sign(obj) == 0; +} +#endif + + +// gh-124502 added PyUnicode_Equal() to Python 3.14.0a0 +#if PY_VERSION_HEX < 0x030E00A0 +static inline int PyUnicode_Equal(PyObject *str1, PyObject *str2) +{ + if (!PyUnicode_Check(str1)) { + PyErr_Format(PyExc_TypeError, "first argument must be str, not %s", + Py_TYPE(str1)->tp_name); + return -1; + } + if (!PyUnicode_Check(str2)) { + PyErr_Format(PyExc_TypeError, "second argument must be str, not %s", + Py_TYPE(str2)->tp_name); + return -1; + } + +#if PY_VERSION_HEX >= 0x030d0000 && !defined(PYPY_VERSION) + PyAPI_FUNC(int) _PyUnicode_Equal(PyObject *str1, PyObject *str2); + + return _PyUnicode_Equal(str1, str2); +#elif PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION) + return _PyUnicode_EQ(str1, str2); +#elif PY_VERSION_HEX >= 0x03090000 && defined(PYPY_VERSION) + return _PyUnicode_EQ(str1, str2); +#else + return (PyUnicode_Compare(str1, str2) == 0); +#endif +} +#endif + + +// gh-121645 added PyBytes_Join() to Python 3.14.0a0 +#if PY_VERSION_HEX < 0x030E00A0 +static inline PyObject* PyBytes_Join(PyObject *sep, PyObject *iterable) +{ + return _PyBytes_Join(sep, iterable); +} +#endif + + +#if PY_VERSION_HEX < 0x030E00A0 +static inline Py_hash_t Py_HashBuffer(const void *ptr, Py_ssize_t len) +{ +#if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) + PyAPI_FUNC(Py_hash_t) _Py_HashBytes(const void *src, Py_ssize_t len); + + return _Py_HashBytes(ptr, len); +#else + Py_hash_t hash; + PyObject *bytes = PyBytes_FromStringAndSize((const char*)ptr, len); + if (bytes == NULL) { + return -1; + } + hash = PyObject_Hash(bytes); + Py_DECREF(bytes); + return hash; +#endif +} +#endif + + +#if PY_VERSION_HEX < 0x030E00A0 +static inline int PyIter_NextItem(PyObject *iter, PyObject **item) +{ + iternextfunc tp_iternext; + + assert(iter != NULL); + assert(item != NULL); + + tp_iternext = Py_TYPE(iter)->tp_iternext; + if (tp_iternext == NULL) { + *item = NULL; + PyErr_Format(PyExc_TypeError, "expected an iterator, got '%s'", + Py_TYPE(iter)->tp_name); + return -1; + } + + if ((*item = tp_iternext(iter))) { + return 1; + } + if (!PyErr_Occurred()) { + return 0; + } + if (PyErr_ExceptionMatches(PyExc_StopIteration)) { + PyErr_Clear(); + return 0; + } + return -1; +} +#endif + + +#if PY_VERSION_HEX < 0x030E00A0 +static inline PyObject* PyLong_FromInt32(int32_t value) +{ + Py_BUILD_ASSERT(sizeof(long) >= 4); + return PyLong_FromLong(value); +} + +static inline PyObject* PyLong_FromInt64(int64_t value) +{ + Py_BUILD_ASSERT(sizeof(long long) >= 8); + return PyLong_FromLongLong(value); +} + +static inline PyObject* PyLong_FromUInt32(uint32_t value) +{ + Py_BUILD_ASSERT(sizeof(unsigned long) >= 4); + return PyLong_FromUnsignedLong(value); +} + +static inline PyObject* PyLong_FromUInt64(uint64_t value) +{ + Py_BUILD_ASSERT(sizeof(unsigned long long) >= 8); + return PyLong_FromUnsignedLongLong(value); +} + +static inline int PyLong_AsInt32(PyObject *obj, int32_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(int) == 4); + int value = PyLong_AsInt(obj); + if (value == -1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (int32_t)value; + return 0; +} + +static inline int PyLong_AsInt64(PyObject *obj, int64_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long long) == 8); + long long value = PyLong_AsLongLong(obj); + if (value == -1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (int64_t)value; + return 0; +} + +static inline int PyLong_AsUInt32(PyObject *obj, uint32_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long) >= 4); + unsigned long value = PyLong_AsUnsignedLong(obj); + if (value == (unsigned long)-1 && PyErr_Occurred()) { + return -1; + } +#if SIZEOF_LONG > 4 + if ((unsigned long)UINT32_MAX < value) { + PyErr_SetString(PyExc_OverflowError, + "Python int too large to convert to C uint32_t"); + return -1; + } +#endif + *pvalue = (uint32_t)value; + return 0; +} + +static inline int PyLong_AsUInt64(PyObject *obj, uint64_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long long) == 8); + unsigned long long value = PyLong_AsUnsignedLongLong(obj); + if (value == (unsigned long long)-1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (uint64_t)value; + return 0; +} +#endif + + +// gh-102471 added import and export API for integers to 3.14.0a2. +#if PY_VERSION_HEX < 0x030E00A2 && PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) +// Helpers to access PyLongObject internals. +static inline void +_PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size) +{ +#if PY_VERSION_HEX >= 0x030C0000 + op->long_value.lv_tag = (uintptr_t)(1 - sign) | ((uintptr_t)(size) << 3); +#elif PY_VERSION_HEX >= 0x030900A4 + Py_SET_SIZE(op, sign * size); +#else + Py_SIZE(op) = sign * size; +#endif +} + +static inline Py_ssize_t +_PyLong_DigitCount(const PyLongObject *op) +{ +#if PY_VERSION_HEX >= 0x030C0000 + return (Py_ssize_t)(op->long_value.lv_tag >> 3); +#else + return _PyLong_Sign((PyObject*)op) < 0 ? -Py_SIZE(op) : Py_SIZE(op); +#endif +} + +static inline digit* +_PyLong_GetDigits(const PyLongObject *op) +{ +#if PY_VERSION_HEX >= 0x030C0000 + return (digit*)(op->long_value.ob_digit); +#else + return (digit*)(op->ob_digit); +#endif +} + +typedef struct PyLongLayout { + uint8_t bits_per_digit; + uint8_t digit_size; + int8_t digits_order; + int8_t digit_endianness; +} PyLongLayout; + +typedef struct PyLongExport { + int64_t value; + uint8_t negative; + Py_ssize_t ndigits; + const void *digits; + Py_uintptr_t _reserved; +} PyLongExport; + +typedef struct PyLongWriter PyLongWriter; + +static inline const PyLongLayout* +PyLong_GetNativeLayout(void) +{ + static const PyLongLayout PyLong_LAYOUT = { + PyLong_SHIFT, + sizeof(digit), + -1, // least significant first + PY_LITTLE_ENDIAN ? -1 : 1, + }; + + return &PyLong_LAYOUT; +} + +static inline int +PyLong_Export(PyObject *obj, PyLongExport *export_long) +{ + if (!PyLong_Check(obj)) { + memset(export_long, 0, sizeof(*export_long)); + PyErr_Format(PyExc_TypeError, "expected int, got %s", + Py_TYPE(obj)->tp_name); + return -1; + } + + // Fast-path: try to convert to a int64_t + PyLongObject *self = (PyLongObject*)obj; + int overflow; +#if SIZEOF_LONG == 8 + long value = PyLong_AsLongAndOverflow(obj, &overflow); +#else + // Windows has 32-bit long, so use 64-bit long long instead + long long value = PyLong_AsLongLongAndOverflow(obj, &overflow); +#endif + Py_BUILD_ASSERT(sizeof(value) == sizeof(int64_t)); + // the function cannot fail since obj is a PyLongObject + assert(!(value == -1 && PyErr_Occurred())); + + if (!overflow) { + export_long->value = value; + export_long->negative = 0; + export_long->ndigits = 0; + export_long->digits = 0; + export_long->_reserved = 0; + } + else { + export_long->value = 0; + export_long->negative = _PyLong_Sign(obj) < 0; + export_long->ndigits = _PyLong_DigitCount(self); + if (export_long->ndigits == 0) { + export_long->ndigits = 1; + } + export_long->digits = _PyLong_GetDigits(self); + export_long->_reserved = (Py_uintptr_t)Py_NewRef(obj); + } + return 0; +} + +static inline void +PyLong_FreeExport(PyLongExport *export_long) +{ + PyObject *obj = (PyObject*)export_long->_reserved; + + if (obj) { + export_long->_reserved = 0; + Py_DECREF(obj); + } +} + +static inline PyLongWriter* +PyLongWriter_Create(int negative, Py_ssize_t ndigits, void **digits) +{ + if (ndigits <= 0) { + PyErr_SetString(PyExc_ValueError, "ndigits must be positive"); + return NULL; + } + assert(digits != NULL); + + PyLongObject *obj = _PyLong_New(ndigits); + if (obj == NULL) { + return NULL; + } + _PyLong_SetSignAndDigitCount(obj, negative?-1:1, ndigits); + + *digits = _PyLong_GetDigits(obj); + return (PyLongWriter*)obj; +} + +static inline void +PyLongWriter_Discard(PyLongWriter *writer) +{ + PyLongObject *obj = (PyLongObject *)writer; + + assert(Py_REFCNT(obj) == 1); + Py_DECREF(obj); +} + +static inline PyObject* +PyLongWriter_Finish(PyLongWriter *writer) +{ + PyObject *obj = (PyObject *)writer; + PyLongObject *self = (PyLongObject*)obj; + Py_ssize_t j = _PyLong_DigitCount(self); + Py_ssize_t i = j; + int sign = _PyLong_Sign(obj); + + assert(Py_REFCNT(obj) == 1); + + // Normalize and get singleton if possible + while (i > 0 && _PyLong_GetDigits(self)[i-1] == 0) { + --i; + } + if (i != j) { + if (i == 0) { + sign = 0; + } + _PyLong_SetSignAndDigitCount(self, sign, i); + } + if (i <= 1) { + long val = sign * (long)(_PyLong_GetDigits(self)[0]); + Py_DECREF(obj); + return PyLong_FromLong(val); + } + + return obj; +} +#endif + + +#if PY_VERSION_HEX < 0x030C00A3 +# define Py_T_SHORT T_SHORT +# define Py_T_INT T_INT +# define Py_T_LONG T_LONG +# define Py_T_FLOAT T_FLOAT +# define Py_T_DOUBLE T_DOUBLE +# define Py_T_STRING T_STRING +# define _Py_T_OBJECT T_OBJECT +# define Py_T_CHAR T_CHAR +# define Py_T_BYTE T_BYTE +# define Py_T_UBYTE T_UBYTE +# define Py_T_USHORT T_USHORT +# define Py_T_UINT T_UINT +# define Py_T_ULONG T_ULONG +# define Py_T_STRING_INPLACE T_STRING_INPLACE +# define Py_T_BOOL T_BOOL +# define Py_T_OBJECT_EX T_OBJECT_EX +# define Py_T_LONGLONG T_LONGLONG +# define Py_T_ULONGLONG T_ULONGLONG +# define Py_T_PYSSIZET T_PYSSIZET + +# if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) +# define _Py_T_NONE T_NONE +# endif + +# define Py_READONLY READONLY +# define Py_AUDIT_READ READ_RESTRICTED +# define _Py_WRITE_RESTRICTED PY_WRITE_RESTRICTED +#endif + + +// gh-127350 added Py_fopen() and Py_fclose() to Python 3.14a4 +#if PY_VERSION_HEX < 0x030E00A4 +static inline FILE* Py_fopen(PyObject *path, const char *mode) +{ +#if 0x030400A2 <= PY_VERSION_HEX && !defined(PYPY_VERSION) + PyAPI_FUNC(FILE*) _Py_fopen_obj(PyObject *path, const char *mode); + + return _Py_fopen_obj(path, mode); +#else + FILE *f; + PyObject *bytes; +#if PY_VERSION_HEX >= 0x03000000 + if (!PyUnicode_FSConverter(path, &bytes)) { + return NULL; + } +#else + if (!PyString_Check(path)) { + PyErr_SetString(PyExc_TypeError, "except str"); + return NULL; + } + bytes = Py_NewRef(path); +#endif + const char *path_bytes = PyBytes_AS_STRING(bytes); + + f = fopen(path_bytes, mode); + Py_DECREF(bytes); + + if (f == NULL) { + PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path); + return NULL; + } + return f; +#endif +} + +static inline int Py_fclose(FILE *file) +{ + return fclose(file); +} +#endif + + +#if 0x03090000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030E0000 && !defined(PYPY_VERSION) +static inline PyObject* +PyConfig_Get(const char *name) +{ + typedef enum { + _PyConfig_MEMBER_INT, + _PyConfig_MEMBER_UINT, + _PyConfig_MEMBER_ULONG, + _PyConfig_MEMBER_BOOL, + _PyConfig_MEMBER_WSTR, + _PyConfig_MEMBER_WSTR_OPT, + _PyConfig_MEMBER_WSTR_LIST, + } PyConfigMemberType; + + typedef struct { + const char *name; + size_t offset; + PyConfigMemberType type; + const char *sys_attr; + } PyConfigSpec; + +#define PYTHONCAPI_COMPAT_SPEC(MEMBER, TYPE, sys_attr) \ + {#MEMBER, offsetof(PyConfig, MEMBER), \ + _PyConfig_MEMBER_##TYPE, sys_attr} + + static const PyConfigSpec config_spec[] = { + PYTHONCAPI_COMPAT_SPEC(argv, WSTR_LIST, "argv"), + PYTHONCAPI_COMPAT_SPEC(base_exec_prefix, WSTR_OPT, "base_exec_prefix"), + PYTHONCAPI_COMPAT_SPEC(base_executable, WSTR_OPT, "_base_executable"), + PYTHONCAPI_COMPAT_SPEC(base_prefix, WSTR_OPT, "base_prefix"), + PYTHONCAPI_COMPAT_SPEC(bytes_warning, UINT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(exec_prefix, WSTR_OPT, "exec_prefix"), + PYTHONCAPI_COMPAT_SPEC(executable, WSTR_OPT, "executable"), + PYTHONCAPI_COMPAT_SPEC(inspect, BOOL, _Py_NULL), +#if 0x030C0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(int_max_str_digits, UINT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(interactive, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(module_search_paths, WSTR_LIST, "path"), + PYTHONCAPI_COMPAT_SPEC(optimization_level, UINT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(parser_debug, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(platlibdir, WSTR, "platlibdir"), + PYTHONCAPI_COMPAT_SPEC(prefix, WSTR_OPT, "prefix"), + PYTHONCAPI_COMPAT_SPEC(pycache_prefix, WSTR_OPT, "pycache_prefix"), + PYTHONCAPI_COMPAT_SPEC(quiet, BOOL, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(stdlib_dir, WSTR_OPT, "_stdlib_dir"), +#endif + PYTHONCAPI_COMPAT_SPEC(use_environment, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(verbose, UINT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(warnoptions, WSTR_LIST, "warnoptions"), + PYTHONCAPI_COMPAT_SPEC(write_bytecode, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(xoptions, WSTR_LIST, "_xoptions"), + PYTHONCAPI_COMPAT_SPEC(buffered_stdio, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(check_hash_pycs_mode, WSTR, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(code_debug_ranges, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(configure_c_stdio, BOOL, _Py_NULL), +#if 0x030D0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(cpu_count, INT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(dev_mode, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(dump_refs, BOOL, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(dump_refs_file, WSTR_OPT, _Py_NULL), +#endif +#ifdef Py_GIL_DISABLED + PYTHONCAPI_COMPAT_SPEC(enable_gil, INT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(faulthandler, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(filesystem_encoding, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(filesystem_errors, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(hash_seed, ULONG, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(home, WSTR_OPT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(import_time, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(install_signal_handlers, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(isolated, BOOL, _Py_NULL), +#ifdef MS_WINDOWS + PYTHONCAPI_COMPAT_SPEC(legacy_windows_stdio, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(malloc_stats, BOOL, _Py_NULL), +#if 0x030A0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(orig_argv, WSTR_LIST, "orig_argv"), +#endif + PYTHONCAPI_COMPAT_SPEC(parse_argv, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(pathconfig_warnings, BOOL, _Py_NULL), +#if 0x030C0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(perf_profiling, UINT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(program_name, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(run_command, WSTR_OPT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(run_filename, WSTR_OPT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(run_module, WSTR_OPT, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(safe_path, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(show_ref_count, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(site_import, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(skip_source_first_line, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(stdio_encoding, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(stdio_errors, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(tracemalloc, UINT, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(use_frozen_modules, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(use_hash_seed, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(user_site_directory, BOOL, _Py_NULL), +#if 0x030A0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(warn_default_encoding, BOOL, _Py_NULL), +#endif + }; + +#undef PYTHONCAPI_COMPAT_SPEC + + const PyConfigSpec *spec; + int found = 0; + for (size_t i=0; i < sizeof(config_spec) / sizeof(config_spec[0]); i++) { + spec = &config_spec[i]; + if (strcmp(spec->name, name) == 0) { + found = 1; + break; + } + } + if (found) { + if (spec->sys_attr != NULL) { + PyObject *value = PySys_GetObject(spec->sys_attr); + if (value == NULL) { + PyErr_Format(PyExc_RuntimeError, "lost sys.%s", spec->sys_attr); + return NULL; + } + return Py_NewRef(value); + } + + PyAPI_FUNC(const PyConfig*) _Py_GetConfig(void); + + const PyConfig *config = _Py_GetConfig(); + void *member = (char *)config + spec->offset; + switch (spec->type) { + case _PyConfig_MEMBER_INT: + case _PyConfig_MEMBER_UINT: + { + int value = *(int *)member; + return PyLong_FromLong(value); + } + case _PyConfig_MEMBER_BOOL: + { + int value = *(int *)member; + return PyBool_FromLong(value != 0); + } + case _PyConfig_MEMBER_ULONG: + { + unsigned long value = *(unsigned long *)member; + return PyLong_FromUnsignedLong(value); + } + case _PyConfig_MEMBER_WSTR: + case _PyConfig_MEMBER_WSTR_OPT: + { + wchar_t *wstr = *(wchar_t **)member; + if (wstr != NULL) { + return PyUnicode_FromWideChar(wstr, -1); + } + else { + return Py_NewRef(Py_None); + } + } + case _PyConfig_MEMBER_WSTR_LIST: + { + const PyWideStringList *list = (const PyWideStringList *)member; + PyObject *tuple = PyTuple_New(list->length); + if (tuple == NULL) { + return NULL; + } + + for (Py_ssize_t i = 0; i < list->length; i++) { + PyObject *item = PyUnicode_FromWideChar(list->items[i], -1); + if (item == NULL) { + Py_DECREF(tuple); + return NULL; + } + PyTuple_SET_ITEM(tuple, i, item); + } + return tuple; + } + default: + Py_UNREACHABLE(); + } + } + + PyErr_Format(PyExc_ValueError, "unknown config option name: %s", name); + return NULL; +} + +static inline int +PyConfig_GetInt(const char *name, int *value) +{ + PyObject *obj = PyConfig_Get(name); + if (obj == NULL) { + return -1; + } + + if (!PyLong_Check(obj)) { + Py_DECREF(obj); + PyErr_Format(PyExc_TypeError, "config option %s is not an int", name); + return -1; + } + + int as_int = PyLong_AsInt(obj); + Py_DECREF(obj); + if (as_int == -1 && PyErr_Occurred()) { + PyErr_Format(PyExc_OverflowError, + "config option %s value does not fit into a C int", name); + return -1; + } + + *value = as_int; + return 0; +} +#endif // PY_VERSION_HEX > 0x03090000 && !defined(PYPY_VERSION) + +// gh-133144 added PyUnstable_Object_IsUniquelyReferenced() to Python 3.14.0b1. +// Adapted from _PyObject_IsUniquelyReferenced() implementation. +#if PY_VERSION_HEX < 0x030E00B0 +static inline int PyUnstable_Object_IsUniquelyReferenced(PyObject *obj) +{ +#if !defined(Py_GIL_DISABLED) + return Py_REFCNT(obj) == 1; +#else + // NOTE: the entire ob_ref_shared field must be zero, including flags, to + // ensure that other threads cannot concurrently create new references to + // this object. + return (_Py_IsOwnedByCurrentThread(obj) && + _Py_atomic_load_uint32_relaxed(&obj->ob_ref_local) == 1 && + _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared) == 0); +#endif +} +#endif + + +#if PY_VERSION_HEX < 0x030F0000 +static inline PyObject* +PySys_GetAttrString(const char *name) +{ +#if PY_VERSION_HEX >= 0x03000000 + PyObject *value = Py_XNewRef(PySys_GetObject(name)); +#else + PyObject *value = Py_XNewRef(PySys_GetObject((char*)name)); +#endif + if (value != NULL) { + return value; + } + if (!PyErr_Occurred()) { + PyErr_Format(PyExc_RuntimeError, "lost sys.%s", name); + } + return NULL; +} + +static inline PyObject* +PySys_GetAttr(PyObject *name) +{ +#if PY_VERSION_HEX >= 0x03000000 + const char *name_str = PyUnicode_AsUTF8(name); +#else + const char *name_str = PyString_AsString(name); +#endif + if (name_str == NULL) { + return NULL; + } + + return PySys_GetAttrString(name_str); +} + +static inline int +PySys_GetOptionalAttrString(const char *name, PyObject **value) +{ +#if PY_VERSION_HEX >= 0x03000000 + *value = Py_XNewRef(PySys_GetObject(name)); +#else + *value = Py_XNewRef(PySys_GetObject((char*)name)); +#endif + if (*value != NULL) { + return 1; + } + return 0; +} + +static inline int +PySys_GetOptionalAttr(PyObject *name, PyObject **value) +{ +#if PY_VERSION_HEX >= 0x03000000 + const char *name_str = PyUnicode_AsUTF8(name); +#else + const char *name_str = PyString_AsString(name); +#endif + if (name_str == NULL) { + *value = NULL; + return -1; + } + + return PySys_GetOptionalAttrString(name_str, value); +} +#endif // PY_VERSION_HEX < 0x030F00A1 + + +#if PY_VERSION_HEX < 0x030F00A1 +typedef struct PyBytesWriter { + char small_buffer[256]; + PyObject *obj; + Py_ssize_t size; +} PyBytesWriter; + +static inline Py_ssize_t +_PyBytesWriter_GetAllocated(PyBytesWriter *writer) +{ + if (writer->obj == NULL) { + return sizeof(writer->small_buffer); + } + else { + return PyBytes_GET_SIZE(writer->obj); + } +} + + +static inline int +_PyBytesWriter_Resize_impl(PyBytesWriter *writer, Py_ssize_t size, + int resize) +{ + int overallocate = resize; + assert(size >= 0); + + if (size <= _PyBytesWriter_GetAllocated(writer)) { + return 0; + } + + if (overallocate) { +#ifdef MS_WINDOWS + /* On Windows, overallocate by 50% is the best factor */ + if (size <= (PY_SSIZE_T_MAX - size / 2)) { + size += size / 2; + } +#else + /* On Linux, overallocate by 25% is the best factor */ + if (size <= (PY_SSIZE_T_MAX - size / 4)) { + size += size / 4; + } +#endif + } + + if (writer->obj != NULL) { + if (_PyBytes_Resize(&writer->obj, size)) { + return -1; + } + assert(writer->obj != NULL); + } + else { + writer->obj = PyBytes_FromStringAndSize(NULL, size); + if (writer->obj == NULL) { + return -1; + } + + if (resize) { + assert((size_t)size > sizeof(writer->small_buffer)); + memcpy(PyBytes_AS_STRING(writer->obj), + writer->small_buffer, + sizeof(writer->small_buffer)); + } + } + return 0; +} + +static inline void* +PyBytesWriter_GetData(PyBytesWriter *writer) +{ + if (writer->obj == NULL) { + return writer->small_buffer; + } + else { + return PyBytes_AS_STRING(writer->obj); + } +} + +static inline Py_ssize_t +PyBytesWriter_GetSize(PyBytesWriter *writer) +{ + return writer->size; +} + +static inline void +PyBytesWriter_Discard(PyBytesWriter *writer) +{ + if (writer == NULL) { + return; + } + + Py_XDECREF(writer->obj); + PyMem_Free(writer); +} + +static inline PyBytesWriter* +PyBytesWriter_Create(Py_ssize_t size) +{ + if (size < 0) { + PyErr_SetString(PyExc_ValueError, "size must be >= 0"); + return NULL; + } + + PyBytesWriter *writer = (PyBytesWriter*)PyMem_Malloc(sizeof(PyBytesWriter)); + if (writer == NULL) { + PyErr_NoMemory(); + return NULL; + } + + writer->obj = NULL; + writer->size = 0; + + if (size >= 1) { + if (_PyBytesWriter_Resize_impl(writer, size, 0) < 0) { + PyBytesWriter_Discard(writer); + return NULL; + } + writer->size = size; + } + return writer; +} + +static inline PyObject* +PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size) +{ + PyObject *result; + if (size == 0) { + result = PyBytes_FromStringAndSize("", 0); + } + else if (writer->obj != NULL) { + if (size != PyBytes_GET_SIZE(writer->obj)) { + if (_PyBytes_Resize(&writer->obj, size)) { + goto error; + } + } + result = writer->obj; + writer->obj = NULL; + } + else { + result = PyBytes_FromStringAndSize(writer->small_buffer, size); + } + PyBytesWriter_Discard(writer); + return result; + +error: + PyBytesWriter_Discard(writer); + return NULL; +} + +static inline PyObject* +PyBytesWriter_Finish(PyBytesWriter *writer) +{ + return PyBytesWriter_FinishWithSize(writer, writer->size); +} + +static inline PyObject* +PyBytesWriter_FinishWithPointer(PyBytesWriter *writer, void *buf) +{ + Py_ssize_t size = (char*)buf - (char*)PyBytesWriter_GetData(writer); + if (size < 0 || size > _PyBytesWriter_GetAllocated(writer)) { + PyBytesWriter_Discard(writer); + PyErr_SetString(PyExc_ValueError, "invalid end pointer"); + return NULL; + } + + return PyBytesWriter_FinishWithSize(writer, size); +} + +static inline int +PyBytesWriter_Resize(PyBytesWriter *writer, Py_ssize_t size) +{ + if (size < 0) { + PyErr_SetString(PyExc_ValueError, "size must be >= 0"); + return -1; + } + if (_PyBytesWriter_Resize_impl(writer, size, 1) < 0) { + return -1; + } + writer->size = size; + return 0; +} + +static inline int +PyBytesWriter_Grow(PyBytesWriter *writer, Py_ssize_t size) +{ + if (size < 0 && writer->size + size < 0) { + PyErr_SetString(PyExc_ValueError, "invalid size"); + return -1; + } + if (size > PY_SSIZE_T_MAX - writer->size) { + PyErr_NoMemory(); + return -1; + } + size = writer->size + size; + + if (_PyBytesWriter_Resize_impl(writer, size, 1) < 0) { + return -1; + } + writer->size = size; + return 0; +} + +static inline void* +PyBytesWriter_GrowAndUpdatePointer(PyBytesWriter *writer, + Py_ssize_t size, void *buf) +{ + Py_ssize_t pos = (char*)buf - (char*)PyBytesWriter_GetData(writer); + if (PyBytesWriter_Grow(writer, size) < 0) { + return NULL; + } + return (char*)PyBytesWriter_GetData(writer) + pos; +} + +static inline int +PyBytesWriter_WriteBytes(PyBytesWriter *writer, + const void *bytes, Py_ssize_t size) +{ + if (size < 0) { + size_t len = strlen((const char*)bytes); + if (len > (size_t)PY_SSIZE_T_MAX) { + PyErr_NoMemory(); + return -1; + } + size = (Py_ssize_t)len; + } + + Py_ssize_t pos = writer->size; + if (PyBytesWriter_Grow(writer, size) < 0) { + return -1; + } + char *buf = (char*)PyBytesWriter_GetData(writer); + memcpy(buf + pos, bytes, (size_t)size); + return 0; +} + +static inline int +PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...) + Py_GCC_ATTRIBUTE((format(printf, 2, 3))); + +static inline int +PyBytesWriter_Format(PyBytesWriter *writer, const char *format, ...) +{ + va_list vargs; + va_start(vargs, format); + PyObject *str = PyBytes_FromFormatV(format, vargs); + va_end(vargs); + + if (str == NULL) { + return -1; + } + int res = PyBytesWriter_WriteBytes(writer, + PyBytes_AS_STRING(str), + PyBytes_GET_SIZE(str)); + Py_DECREF(str); + return res; +} +#endif // PY_VERSION_HEX < 0x030F00A1 + + +#ifdef __cplusplus +} +#endif +#endif // PYTHONCAPI_COMPAT diff --git a/asyncpg/record/pythoncapi_compat_extras.h b/asyncpg/record/pythoncapi_compat_extras.h new file mode 100644 index 00000000..3db97665 --- /dev/null +++ b/asyncpg/record/pythoncapi_compat_extras.h @@ -0,0 +1,72 @@ +#ifndef PYTHONCAPI_COMPAT_EXTRAS +#define PYTHONCAPI_COMPAT_EXTRAS + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +// Python 3.11.0a6 added PyType_GetModuleByDef() to Python.h +#if PY_VERSION_HEX < 0x030b00A6 +PyObject * +PyType_GetModuleByDef(PyTypeObject *type, PyModuleDef *def) +{ + assert(PyType_Check(type)); + + if (!PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { + // type_ready_mro() ensures that no heap type is + // contained in a static type MRO. + goto error; + } + else { + PyHeapTypeObject *ht = (PyHeapTypeObject*)type; + PyObject *module = ht->ht_module; + if (module && PyModule_GetDef(module) == def) { + return module; + } + } + + PyObject *res = NULL; + PyObject *mro = type->tp_mro; + // The type must be ready + assert(mro != NULL); + assert(PyTuple_Check(mro)); + // mro_invoke() ensures that the type MRO cannot be empty. + assert(PyTuple_GET_SIZE(mro) >= 1); + // Also, the first item in the MRO is the type itself, which + // we already checked above. We skip it in the loop. + assert(PyTuple_GET_ITEM(mro, 0) == (PyObject *)type); + + Py_ssize_t n = PyTuple_GET_SIZE(mro); + for (Py_ssize_t i = 1; i < n; i++) { + PyObject *super = PyTuple_GET_ITEM(mro, i); + if (!PyType_HasFeature((PyTypeObject *)super, Py_TPFLAGS_HEAPTYPE)) { + // Static types in the MRO need to be skipped + continue; + } + + PyHeapTypeObject *ht = (PyHeapTypeObject*)super; + PyObject *module = ht->ht_module; + if (module && PyModule_GetDef(module) == def) { + res = module; + break; + } + } + + if (res != NULL) { + return res; + } +error: + PyErr_Format( + PyExc_TypeError, + "PyType_GetModuleByDef: No superclass of '%s' has the given module", + type->tp_name); + return NULL; +} +#endif + +#ifdef __cplusplus +} +#endif +#endif // PYTHONCAPI_COMPAT_EXTRAS diff --git a/asyncpg/record/recordobj.c b/asyncpg/record/recordobj.c new file mode 100644 index 00000000..2b5ea5ff --- /dev/null +++ b/asyncpg/record/recordobj.c @@ -0,0 +1,1281 @@ +/* Big parts of this file are copied (with modifications) from + CPython/Objects/tupleobject.c. + + Portions Copyright (c) PSF (and other CPython copyright holders). + Portions Copyright (c) 2016-present MagicStack Inc. + License: PSFL v2; see CPython/LICENSE for details. +*/ + +#include +#include +#include "pythoncapi_compat.h" +#include "pythoncapi_compat_extras.h" + +#include "recordobj.h" + +#ifndef _PyCFunction_CAST +#define _PyCFunction_CAST(func) ((PyCFunction)(void (*)(void))(func)) +#endif + +static size_t ApgRecord_MAXSIZE = + (((size_t)PY_SSIZE_T_MAX - sizeof(ApgRecordObject) - sizeof(PyObject *)) / + sizeof(PyObject *)); + +/* Largest record to save on free list */ +#define ApgRecord_MAXSAVESIZE 20 + +/* Maximum number of records of each size to save */ +#define ApgRecord_MAXFREELIST 2000 + +typedef struct { + ApgRecordObject *freelist[ApgRecord_MAXSAVESIZE]; + int numfree[ApgRecord_MAXSAVESIZE]; +} record_freelist_state; + +typedef struct { + PyTypeObject *ApgRecord_Type; + PyTypeObject *ApgRecordDesc_Type; + PyTypeObject *ApgRecordIter_Type; + PyTypeObject *ApgRecordItems_Type; + + Py_tss_t freelist_key; // TSS key for per-thread record_freelist_state +} record_module_state; + +static inline record_module_state * +get_module_state(PyObject *module) +{ + void *state = PyModule_GetState(module); + if (state == NULL) { + PyErr_SetString(PyExc_SystemError, "failed to get record module state"); + return NULL; + } + return (record_module_state *)state; +} + +static inline record_module_state * +get_module_state_from_type(PyTypeObject *type) +{ + void *state = PyType_GetModuleState(type); + if (state != NULL) { + return (record_module_state *)state; + } + + PyErr_Format(PyExc_SystemError, "could not get record module state from '%.100s'", + type->tp_name); + return NULL; +} + +static struct PyModuleDef _recordmodule; + +static inline record_module_state * +find_module_state_by_def(PyTypeObject *type) +{ + PyObject *mod = PyType_GetModuleByDef(type, &_recordmodule); + if (mod == NULL) + return NULL; + return get_module_state(mod); +} + +static inline record_freelist_state * +get_freelist_state(record_module_state *state) +{ + record_freelist_state *freelist; + + freelist = (record_freelist_state *)PyThread_tss_get(&state->freelist_key); + if (freelist == NULL) { + freelist = (record_freelist_state *)PyMem_Calloc( + 1, sizeof(record_freelist_state)); + if (freelist == NULL) { + PyErr_NoMemory(); + return NULL; + } + if (PyThread_tss_set(&state->freelist_key, (void *)freelist) != 0) { + PyMem_Free(freelist); + PyErr_SetString( + PyExc_SystemError, "failed to set thread-specific data"); + return NULL; + } + } + return freelist; +} + +PyObject * +make_record(PyTypeObject *type, PyObject *desc, Py_ssize_t size, + record_module_state *state) +{ + ApgRecordObject *o; + Py_ssize_t i; + int need_gc_track = 0; + + if (size < 0 || desc == NULL || + Py_TYPE(desc) != state->ApgRecordDesc_Type) { + PyErr_BadInternalCall(); + return NULL; + } + + if (type == state->ApgRecord_Type) { + record_freelist_state *freelist = NULL; + + if (size < ApgRecord_MAXSAVESIZE) { + freelist = get_freelist_state(state); + if (freelist != NULL && freelist->freelist[size] != NULL) { + o = freelist->freelist[size]; + freelist->freelist[size] = (ApgRecordObject *)o->ob_item[0]; + freelist->numfree[size]--; + _Py_NewReference((PyObject *)o); + } + else { + freelist = NULL; + } + } + + if (freelist == NULL) { + if ((size_t)size > ApgRecord_MAXSIZE) { + return PyErr_NoMemory(); + } + o = PyObject_GC_NewVar(ApgRecordObject, state->ApgRecord_Type, size); + if (o == NULL) { + return NULL; + } + } + + need_gc_track = 1; + } + else { + assert(PyType_IsSubtype(type, state->ApgRecord_Type)); + + if ((size_t)size > ApgRecord_MAXSIZE) { + return PyErr_NoMemory(); + } + o = (ApgRecordObject *)type->tp_alloc(type, size); + if (!PyObject_GC_IsTracked((PyObject *)o)) { + PyErr_SetString(PyExc_TypeError, "record subclass is not tracked by GC"); + return NULL; + } + } + + for (i = 0; i < size; i++) { + o->ob_item[i] = NULL; + } + + Py_INCREF(desc); + o->desc = (ApgRecordDescObject *)desc; + o->self_hash = -1; + if (need_gc_track) { + PyObject_GC_Track(o); + } + return (PyObject *)o; +} + +static void +record_dealloc(PyObject *self) +{ + ApgRecordObject *o = (ApgRecordObject *)self; + Py_ssize_t i; + Py_ssize_t len = Py_SIZE(o); + record_module_state *state; + + state = find_module_state_by_def(Py_TYPE(o)); + if (state == NULL) { + return; + } + + PyObject_GC_UnTrack(o); + + o->self_hash = -1; + + Py_CLEAR(o->desc); + + Py_TRASHCAN_BEGIN(o, record_dealloc) + + i = len; + while (--i >= 0) { + Py_XDECREF(o->ob_item[i]); + } + + if (len < ApgRecord_MAXSAVESIZE && Py_TYPE(o) == state->ApgRecord_Type) { + record_freelist_state *freelist = get_freelist_state(state); + if (freelist != NULL && freelist->numfree[len] < ApgRecord_MAXFREELIST) { + o->ob_item[0] = (PyObject *)freelist->freelist[len]; + freelist->numfree[len]++; + freelist->freelist[len] = o; + } + else { + Py_TYPE(o)->tp_free((PyObject *)o); + } + } + else { + Py_TYPE(o)->tp_free((PyObject *)o); + } + + Py_TRASHCAN_END +} + +static int +record_traverse(PyObject *self, visitproc visit, void *arg) +{ + ApgRecordObject *o = (ApgRecordObject *)self; + for (Py_ssize_t i = Py_SIZE(o); --i >= 0;) { + Py_VISIT(o->ob_item[i]); + } + return 0; +} + +/* Below are the official constants from the xxHash specification. Optimizing + compilers should emit a single "rotate" instruction for the + _PyTuple_HASH_XXROTATE() expansion. If that doesn't happen for some important + platform, the macro could be changed to expand to a platform-specific rotate + spelling instead. +*/ +#if SIZEOF_PY_UHASH_T > 4 +#define _ApgRecord_HASH_XXPRIME_1 ((Py_uhash_t)11400714785074694791ULL) +#define _ApgRecord_HASH_XXPRIME_2 ((Py_uhash_t)14029467366897019727ULL) +#define _ApgRecord_HASH_XXPRIME_5 ((Py_uhash_t)2870177450012600261ULL) +#define _ApgRecord_HASH_XXROTATE(x) ((x << 31) | (x >> 33)) /* Rotate left 31 bits */ +#else +#define _ApgRecord_HASH_XXPRIME_1 ((Py_uhash_t)2654435761UL) +#define _ApgRecord_HASH_XXPRIME_2 ((Py_uhash_t)2246822519UL) +#define _ApgRecord_HASH_XXPRIME_5 ((Py_uhash_t)374761393UL) +#define _ApgRecord_HASH_XXROTATE(x) ((x << 13) | (x >> 19)) /* Rotate left 13 bits */ +#endif + +static Py_hash_t +record_hash(PyObject *op) +{ + ApgRecordObject *v = (ApgRecordObject *)op; + Py_uhash_t acc; + Py_ssize_t len = Py_SIZE(v); + PyObject **item = v->ob_item; + acc = _ApgRecord_HASH_XXPRIME_5; + for (Py_ssize_t i = 0; i < len; i++) { + Py_uhash_t lane = (Py_uhash_t)PyObject_Hash(item[i]); + if (lane == (Py_uhash_t)-1) { + return -1; + } + acc += lane * _ApgRecord_HASH_XXPRIME_2; + acc = _ApgRecord_HASH_XXROTATE(acc); + acc *= _ApgRecord_HASH_XXPRIME_1; + } + + /* Add input length, mangled to keep the historical value of hash(()). */ + acc += (Py_uhash_t)len ^ (_ApgRecord_HASH_XXPRIME_5 ^ 3527539UL); + + if (acc == (Py_uhash_t)-1) { + acc = 1546275796; + } + + return (Py_hash_t)acc; +} + +static Py_ssize_t +record_length(PyObject *self) +{ + ApgRecordObject *a = (ApgRecordObject *)self; + return Py_SIZE(a); +} + +static int +record_contains(PyObject *self, PyObject *el) +{ + ApgRecordObject *a = (ApgRecordObject *)self; + if (a->desc == NULL || a->desc->keys == NULL) { + return 0; + } + return PySequence_Contains(a->desc->keys, el); +} + +static PyObject * +record_item(ApgRecordObject *op, Py_ssize_t i) +{ + ApgRecordObject *a = (ApgRecordObject *)op; + if (i < 0 || i >= Py_SIZE(a)) { + PyErr_SetString(PyExc_IndexError, "record index out of range"); + return NULL; + } + return Py_NewRef(a->ob_item[i]); +} + +static PyObject * +record_richcompare(PyObject *v, PyObject *w, int op) +{ + Py_ssize_t i; + Py_ssize_t vlen, wlen; + int v_is_tuple = 0; + int w_is_tuple = 0; + int v_is_record = 0; + int w_is_record = 0; + int comp; + PyTypeObject *v_type = Py_TYPE(v); + PyTypeObject *w_type = Py_TYPE(w); + + record_module_state *state; + + state = find_module_state_by_def(v_type); + if (state == NULL) { + PyErr_Clear(); + state = find_module_state_by_def(w_type); + } + if (PyTuple_Check(v)) { + v_is_tuple = 1; + } + else if (v_type == state->ApgRecord_Type) { + v_is_record = 1; + } + else if (!PyObject_TypeCheck(v, state->ApgRecord_Type)) { + Py_RETURN_NOTIMPLEMENTED; + } + + if (PyTuple_Check(w)) { + w_is_tuple = 1; + } + else if (w_type == state->ApgRecord_Type) { + w_is_record = 1; + } + else if (!PyObject_TypeCheck(w, state->ApgRecord_Type)) { + Py_RETURN_NOTIMPLEMENTED; + } + +#define V_ITEM(i) \ + (v_is_tuple ? PyTuple_GET_ITEM(v, i) \ + : (v_is_record ? ApgRecord_GET_ITEM(v, i) : PySequence_GetItem(v, i))) +#define W_ITEM(i) \ + (w_is_tuple ? PyTuple_GET_ITEM(w, i) \ + : (w_is_record ? ApgRecord_GET_ITEM(w, i) : PySequence_GetItem(w, i))) + + vlen = Py_SIZE(v); + wlen = Py_SIZE(w); + + if (op == Py_EQ && vlen != wlen) { + /* Checking if v == w, but len(v) != len(w): return False */ + Py_RETURN_FALSE; + } + + if (op == Py_NE && vlen != wlen) { + /* Checking if v != w, and len(v) != len(w): return True */ + Py_RETURN_TRUE; + } + + /* Search for the first index where items are different. + * Note that because tuples are immutable, it's safe to reuse + * vlen and wlen across the comparison calls. + */ + for (i = 0; i < vlen && i < wlen; i++) { + comp = PyObject_RichCompareBool(V_ITEM(i), W_ITEM(i), Py_EQ); + if (comp < 0) { + return NULL; + } + if (!comp) { + break; + } + } + + if (i >= vlen || i >= wlen) { + /* No more items to compare -- compare sizes */ + int cmp; + switch (op) { + case Py_LT: + cmp = vlen < wlen; + break; + case Py_LE: + cmp = vlen <= wlen; + break; + case Py_EQ: + cmp = vlen == wlen; + break; + case Py_NE: + cmp = vlen != wlen; + break; + case Py_GT: + cmp = vlen > wlen; + break; + case Py_GE: + cmp = vlen >= wlen; + break; + default: + return NULL; /* cannot happen */ + } + if (cmp) { + Py_RETURN_TRUE; + } + else { + Py_RETURN_FALSE; + } + } + + /* We have an item that differs -- shortcuts for EQ/NE */ + if (op == Py_EQ) { + Py_RETURN_FALSE; + } + if (op == Py_NE) { + Py_RETURN_TRUE; + } + + /* Compare the final item again using the proper operator */ + return PyObject_RichCompare(V_ITEM(i), W_ITEM(i), op); + +#undef V_ITEM +#undef W_ITEM +} + +typedef enum item_by_name_result { + APG_ITEM_FOUND = 0, + APG_ERROR = -1, + APG_ITEM_NOT_FOUND = -2 +} item_by_name_result_t; + +/* Lookup a record value by its name. Return 0 on success, -2 if the + * value was not found (with KeyError set), and -1 on all other errors. + */ +static item_by_name_result_t +record_item_by_name(ApgRecordObject *o, PyObject *item, PyObject **result) +{ + PyObject *mapped; + PyObject *val; + Py_ssize_t i; + + mapped = PyObject_GetItem(o->desc->mapping, item); + if (mapped == NULL) { + goto noitem; + } + + if (!PyIndex_Check(mapped)) { + Py_DECREF(mapped); + goto error; + } + + i = PyNumber_AsSsize_t(mapped, PyExc_IndexError); + Py_DECREF(mapped); + + if (i < 0) { + if (PyErr_Occurred()) + PyErr_Clear(); + goto error; + } + + val = record_item(o, i); + if (val == NULL) { + PyErr_Clear(); + goto error; + } + + *result = val; + + return APG_ITEM_FOUND; + +noitem: + PyErr_SetObject(PyExc_KeyError, item); + return APG_ITEM_NOT_FOUND; + +error: + PyErr_SetString(PyExc_RuntimeError, "invalid record descriptor"); + return APG_ERROR; +} + +static PyObject * +record_subscript(PyObject *op, PyObject *item) +{ + ApgRecordObject *self = (ApgRecordObject *)op; + + if (PyIndex_Check(item)) { + Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError); + if (i == -1 && PyErr_Occurred()) + return NULL; + if (i < 0) { + i += Py_SIZE(self); + } + return record_item(self, i); + } + else if (PySlice_Check(item)) { + Py_ssize_t start, stop, step, cur, slicelength, i; + PyObject *it; + PyObject **src, **dest; + + if (PySlice_Unpack(item, &start, &stop, &step) < 0) { + return NULL; + } + slicelength = PySlice_AdjustIndices(Py_SIZE(self), &start, &stop, step); + + if (slicelength <= 0) { + return PyTuple_New(0); + } + else if (start == 0 && step == 1 && slicelength == Py_SIZE(self) && + PyTuple_CheckExact(self)) { + return Py_NewRef(self); + } + else { + PyTupleObject *result = (PyTupleObject *)PyTuple_New(slicelength); + if (!result) + return NULL; + + src = self->ob_item; + dest = result->ob_item; + for (cur = start, i = 0; i < slicelength; cur += step, i++) { + it = Py_NewRef(src[cur]); + dest[i] = it; + } + + return (PyObject *)result; + } + } + else { + PyObject *result; + + if (record_item_by_name(self, item, &result) < 0) + return NULL; + else + return result; + } +} + +static const char * +get_typename(PyTypeObject *type) +{ + assert(type->tp_name != NULL); + const char *s = strrchr(type->tp_name, '.'); + if (s == NULL) { + s = type->tp_name; + } + else { + s++; + } + return s; +} + +static PyObject * +record_repr(PyObject *self) +{ + ApgRecordObject *v = (ApgRecordObject *)self; + Py_ssize_t i, n; + PyObject *keys_iter; + PyUnicodeWriter *writer; + + n = Py_SIZE(v); + if (n == 0) { + return PyUnicode_FromFormat("<%s>", get_typename(Py_TYPE(v))); + } + + keys_iter = PyObject_GetIter(v->desc->keys); + if (keys_iter == NULL) { + return NULL; + } + + i = Py_ReprEnter((PyObject *)v); + if (i != 0) { + Py_DECREF(keys_iter); + if (i > 0) { + return PyUnicode_FromFormat("<%s ...>", get_typename(Py_TYPE(v))); + } + return NULL; + } + writer = PyUnicodeWriter_Create(12); /* */ + + if (PyUnicodeWriter_Format(writer, "<%s ", get_typename(Py_TYPE(v))) < 0) { + goto error; + } + + for (i = 0; i < n; ++i) { + int res; + PyObject *key; + + if (i > 0) + if (PyUnicodeWriter_WriteChar(writer, ' ') < 0) + goto error; + + key = PyIter_Next(keys_iter); + if (key == NULL) { + PyErr_SetString(PyExc_RuntimeError, "invalid record mapping"); + goto error; + } + + res = PyUnicodeWriter_WriteStr(writer, key); + Py_DECREF(key); + if (res < 0) + goto error; + + if (PyUnicodeWriter_WriteChar(writer, '=') < 0) + goto error; + + if (Py_EnterRecursiveCall(" while getting the repr of a record")) + goto error; + res = PyUnicodeWriter_WriteRepr(writer, v->ob_item[i]); + Py_LeaveRecursiveCall(); + if (res < 0) + goto error; + } + + if (PyUnicodeWriter_WriteChar(writer, '>') < 0) + goto error; + + Py_DECREF(keys_iter); + Py_ReprLeave((PyObject *)v); + return PyUnicodeWriter_Finish(writer); + +error: + Py_DECREF(keys_iter); + PyUnicodeWriter_Discard(writer); + Py_ReprLeave((PyObject *)v); + return NULL; +} + +static PyObject * +record_new_iter(ApgRecordObject *, const record_module_state *); + +static PyObject * +record_iter(PyObject *seq) +{ + ApgRecordObject *r = (ApgRecordObject *)seq; + record_module_state *state; + + state = find_module_state_by_def(Py_TYPE(seq)); + if (state == NULL) { + return NULL; + } + + return record_new_iter(r, state); +} + +static PyObject * +record_values(PyObject *self, PyTypeObject *defcls, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ + ApgRecordObject *r = (ApgRecordObject *)self; + record_module_state *state = get_module_state_from_type(defcls); + + if (state == NULL) + return NULL; + + return record_new_iter(r, state); +} + +static PyObject * +record_keys(PyObject *self, PyTypeObject *defcls, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ + ApgRecordObject *r = (ApgRecordObject *)self; + return PyObject_GetIter(r->desc->keys); +} + +static PyObject * +record_new_items_iter(ApgRecordObject *, const record_module_state *); + +static PyObject * +record_items(PyObject *self, PyTypeObject *defcls, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ + ApgRecordObject *r = (ApgRecordObject *)self; + record_module_state *state = get_module_state_from_type(defcls); + + if (state == NULL) + return NULL; + + return record_new_items_iter(r, state); +} + +static PyObject * +record_get(PyObject *self, PyTypeObject *defcls, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); + PyObject *key; + PyObject *defval = Py_None; + PyObject *val = NULL; + int res; + + if (nargs == 2) { + key = args[0]; + defval = args[1]; + } else if (nargs == 1) { + key = args[0]; + } else { + PyErr_Format(PyExc_TypeError, + "Record.get() expected 1 or 2 arguments, got %zd", + nargs); + } + + if (kwnames != NULL && PyTuple_GET_SIZE(kwnames) != 0) { + PyErr_SetString(PyExc_TypeError, "Record.get() takes no keyword arguments"); + return NULL; + } + + res = record_item_by_name((ApgRecordObject *)self, key, &val); + if (res == APG_ITEM_NOT_FOUND) { + PyErr_Clear(); + Py_INCREF(defval); + val = defval; + } + + return val; +} + +static PyObject * +record_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + record_module_state *state; + + state = get_module_state_from_type(type); + if (state == NULL) { + return NULL; + } + + if (type == state->ApgRecord_Type) { + PyErr_Format(PyExc_TypeError, "cannot create '%.100s' instances", type->tp_name); + return NULL; + } + + /* For subclasses, use the default allocation */ + return type->tp_alloc(type, 0); +} + +static PyMethodDef record_methods[] = { + {"values", _PyCFunction_CAST(record_values), METH_METHOD | METH_FASTCALL | METH_KEYWORDS}, + {"keys", _PyCFunction_CAST(record_keys), METH_METHOD | METH_FASTCALL | METH_KEYWORDS}, + {"items", _PyCFunction_CAST(record_items), METH_METHOD | METH_FASTCALL | METH_KEYWORDS}, + {"get", _PyCFunction_CAST(record_get), METH_METHOD | METH_FASTCALL | METH_KEYWORDS}, + {NULL, NULL} /* sentinel */ +}; + +static PyType_Slot ApgRecord_TypeSlots[] = { + {Py_tp_dealloc, record_dealloc}, + {Py_tp_repr, record_repr}, + {Py_tp_hash, record_hash}, + {Py_tp_getattro, PyObject_GenericGetAttr}, + {Py_tp_traverse, record_traverse}, + {Py_tp_richcompare, record_richcompare}, + {Py_tp_iter, record_iter}, + {Py_tp_methods, record_methods}, + {Py_tp_new, record_new}, + {Py_tp_free, PyObject_GC_Del}, + {Py_sq_length, record_length}, + {Py_sq_item, record_item}, + {Py_sq_contains, record_contains}, + {Py_mp_length, record_length}, + {Py_mp_subscript, record_subscript}, + {0, NULL}, +}; + +#ifndef Py_TPFLAGS_IMMUTABLETYPE +#define Py_TPFLAGS_IMMUTABLETYPE 0 +#endif + +static PyType_Spec ApgRecord_TypeSpec = { + .name = "asyncpg.record.Record", + .basicsize = sizeof(ApgRecordObject) - sizeof(PyObject *), + .itemsize = sizeof(PyObject *), + .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_IMMUTABLETYPE), + .slots = ApgRecord_TypeSlots, +}; + +/* Record Iterator */ + +typedef struct { + PyObject_HEAD Py_ssize_t it_index; + ApgRecordObject *it_seq; /* Set to NULL when iterator is exhausted */ +} ApgRecordIterObject; + +static void +record_iter_dealloc(ApgRecordIterObject *it) +{ + PyObject_GC_UnTrack(it); + Py_CLEAR(it->it_seq); + PyObject_GC_Del(it); +} + +static int +record_iter_traverse(ApgRecordIterObject *it, visitproc visit, void *arg) +{ + Py_VISIT(it->it_seq); + return 0; +} + +static PyObject * +record_iter_next(ApgRecordIterObject *it) +{ + ApgRecordObject *seq; + PyObject *item; + + assert(it != NULL); + seq = it->it_seq; + if (seq == NULL) + return NULL; + + if (it->it_index < Py_SIZE(seq)) { + item = ApgRecord_GET_ITEM(seq, it->it_index); + ++it->it_index; + Py_INCREF(item); + return item; + } + + it->it_seq = NULL; + Py_DECREF(seq); + return NULL; +} + +static PyObject * +record_iter_len(ApgRecordIterObject *it) +{ + Py_ssize_t len = 0; + if (it->it_seq) { + len = Py_SIZE(it->it_seq) - it->it_index; + } + return PyLong_FromSsize_t(len); +} + +PyDoc_STRVAR(record_iter_len_doc, "Private method returning an estimate of len(list(it))."); + +static PyMethodDef record_iter_methods[] = { + {"__length_hint__", (PyCFunction)record_iter_len, METH_NOARGS, record_iter_len_doc}, + {NULL, NULL} /* sentinel */ +}; + +static PyType_Slot ApgRecordIter_TypeSlots[] = { + {Py_tp_dealloc, (destructor)record_iter_dealloc}, + {Py_tp_getattro, PyObject_GenericGetAttr}, + {Py_tp_traverse, (traverseproc)record_iter_traverse}, + {Py_tp_iter, PyObject_SelfIter}, + {Py_tp_iternext, (iternextfunc)record_iter_next}, + {Py_tp_methods, record_iter_methods}, + {0, NULL}, +}; + +static PyType_Spec ApgRecordIter_TypeSpec = { + .name = "asyncpg.record.RecordIterator", + .basicsize = sizeof(ApgRecordIterObject), + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .slots = ApgRecordIter_TypeSlots, +}; + +static PyObject * +record_new_iter(ApgRecordObject *r, const record_module_state *state) +{ + ApgRecordIterObject *it; + it = PyObject_GC_New(ApgRecordIterObject, state->ApgRecordIter_Type); + if (it == NULL) + return NULL; + it->it_index = 0; + Py_INCREF(r); + it->it_seq = r; + PyObject_GC_Track(it); + return (PyObject *)it; +} + +/* Record Items Iterator */ + +typedef struct { + PyObject_HEAD Py_ssize_t it_index; + PyObject *it_key_iter; + ApgRecordObject *it_seq; /* Set to NULL when iterator is exhausted */ +} ApgRecordItemsObject; + +static void +record_items_dealloc(ApgRecordItemsObject *it) +{ + PyObject_GC_UnTrack(it); + Py_CLEAR(it->it_key_iter); + Py_CLEAR(it->it_seq); + PyObject_GC_Del(it); +} + +static int +record_items_traverse(ApgRecordItemsObject *it, visitproc visit, void *arg) +{ + Py_VISIT(it->it_key_iter); + Py_VISIT(it->it_seq); + return 0; +} + +static PyObject * +record_items_next(ApgRecordItemsObject *it) +{ + ApgRecordObject *seq; + PyObject *key; + PyObject *val; + PyObject *tup; + + assert(it != NULL); + seq = it->it_seq; + if (seq == NULL) { + return NULL; + } + assert(it->it_key_iter != NULL); + + key = PyIter_Next(it->it_key_iter); + if (key == NULL) { + /* likely it_key_iter had less items than seq has values */ + goto exhausted; + } + + if (it->it_index < Py_SIZE(seq)) { + val = ApgRecord_GET_ITEM(seq, it->it_index); + ++it->it_index; + Py_INCREF(val); + } + else { + /* it_key_iter had more items than seq has values */ + Py_DECREF(key); + goto exhausted; + } + + tup = PyTuple_New(2); + if (tup == NULL) { + Py_DECREF(val); + Py_DECREF(key); + goto exhausted; + } + + PyTuple_SET_ITEM(tup, 0, key); + PyTuple_SET_ITEM(tup, 1, val); + return tup; + +exhausted: + Py_CLEAR(it->it_key_iter); + Py_CLEAR(it->it_seq); + return NULL; +} + +static PyObject * +record_items_len(ApgRecordItemsObject *it) +{ + Py_ssize_t len = 0; + if (it->it_seq) { + len = Py_SIZE(it->it_seq) - it->it_index; + } + return PyLong_FromSsize_t(len); +} + +PyDoc_STRVAR(record_items_len_doc, "Private method returning an estimate of len(list(it()))."); + +static PyMethodDef record_items_methods[] = { + {"__length_hint__", (PyCFunction)record_items_len, METH_NOARGS, record_items_len_doc}, + {NULL, NULL} /* sentinel */ +}; + +static PyType_Slot ApgRecordItems_TypeSlots[] = { + {Py_tp_dealloc, (destructor)record_items_dealloc}, + {Py_tp_getattro, PyObject_GenericGetAttr}, + {Py_tp_traverse, (traverseproc)record_items_traverse}, + {Py_tp_iter, PyObject_SelfIter}, + {Py_tp_iternext, (iternextfunc)record_items_next}, + {Py_tp_methods, record_items_methods}, + {0, NULL}, +}; + +static PyType_Spec ApgRecordItems_TypeSpec = { + .name = "asyncpg.record.RecordItemsIterator", + .basicsize = sizeof(ApgRecordItemsObject), + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .slots = ApgRecordItems_TypeSlots, +}; + +static PyObject * +record_new_items_iter(ApgRecordObject *r, const record_module_state *state) +{ + ApgRecordItemsObject *it; + PyObject *key_iter; + + key_iter = PyObject_GetIter(r->desc->keys); + if (key_iter == NULL) + return NULL; + + it = PyObject_GC_New(ApgRecordItemsObject, state->ApgRecordItems_Type); + if (it == NULL) { + Py_DECREF(key_iter); + return NULL; + } + + it->it_key_iter = key_iter; + it->it_index = 0; + Py_INCREF(r); + it->it_seq = r; + PyObject_GC_Track(it); + + return (PyObject *)it; +} + +/* ----------------- */ + +static void +record_desc_dealloc(ApgRecordDescObject *o) +{ + PyObject_GC_UnTrack(o); + Py_CLEAR(o->mapping); + Py_CLEAR(o->keys); + PyObject_GC_Del(o); +} + +static int +record_desc_traverse(ApgRecordDescObject *o, visitproc visit, void *arg) +{ + Py_VISIT(o->mapping); + Py_VISIT(o->keys); + return 0; +} + +static PyObject * +record_desc_vectorcall(PyObject *type, PyObject *const *args, size_t nargsf, + PyObject *kwnames) +{ + PyObject *mapping; + PyObject *keys; + ApgRecordDescObject *o; + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); + + if (kwnames != NULL && PyTuple_GET_SIZE(kwnames) != 0) { + PyErr_SetString(PyExc_TypeError, "RecordDescriptor() takes no keyword arguments"); + return NULL; + } + + if (nargs != 2) { + PyErr_Format(PyExc_TypeError, + "RecordDescriptor() takes exactly 2 arguments (%zd given)", nargs); + return NULL; + } + + mapping = args[0]; + keys = args[1]; + + if (!PyTuple_CheckExact(keys)) { + PyErr_SetString(PyExc_TypeError, "keys must be a tuple"); + return NULL; + } + + o = PyObject_GC_New(ApgRecordDescObject, (PyTypeObject *)type); + if (o == NULL) { + return NULL; + } + + Py_INCREF(mapping); + o->mapping = mapping; + + Py_INCREF(keys); + o->keys = keys; + + PyObject_GC_Track(o); + return (PyObject *)o; +} + +/* Fallback wrapper for when there is no vectorcall support */ +static PyObject * +record_desc_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + PyObject *const *args_array; + size_t nargsf; + PyObject *kwnames = NULL; + + if (kwargs != NULL && PyDict_GET_SIZE(kwargs) != 0) { + PyErr_SetString(PyExc_TypeError, + "RecordDescriptor() takes no keyword arguments"); + return NULL; + } + + if (!PyTuple_Check(args)) { + PyErr_SetString(PyExc_TypeError, + "args must be a tuple"); + return NULL; + } + + nargsf = (size_t)PyTuple_GET_SIZE(args); + args_array = &PyTuple_GET_ITEM(args, 0); + + return record_desc_vectorcall((PyObject *)type, args_array, nargsf, kwnames); +} + +static PyObject * +record_desc_make_record(PyObject *desc, PyTypeObject *desc_type, + PyObject *const *args, Py_ssize_t nargs, + PyObject *kwnames) +{ + PyObject *type_obj; + Py_ssize_t size; + record_module_state *state = get_module_state_from_type(desc_type); + + if (state == NULL) { + return NULL; + } + + if (nargs != 2) { + PyErr_Format(PyExc_TypeError, + "RecordDescriptor.make_record() takes exactly 2 arguments (%zd given)", + nargs); + return NULL; + } + + if (kwnames != NULL && PyTuple_GET_SIZE(kwnames) != 0) { + PyErr_SetString(PyExc_TypeError, + "RecordDescriptor.make_record() takes no keyword arguments"); + return NULL; + } + + type_obj = args[0]; + size = PyLong_AsSsize_t(args[1]); + if (size == -1 && PyErr_Occurred()) { + return NULL; + } + + if (!PyType_Check(type_obj)) { + PyErr_SetString(PyExc_TypeError, + "RecordDescriptor.make_record(): first argument must be a type"); + return NULL; + } + + return make_record((PyTypeObject *)type_obj, desc, size, state); +} + +static PyMethodDef record_desc_methods[] = { + {"make_record", _PyCFunction_CAST(record_desc_make_record), + METH_FASTCALL | METH_METHOD | METH_KEYWORDS}, + {NULL, NULL} /* sentinel */ +}; + +static PyType_Slot ApgRecordDesc_TypeSlots[] = { +#ifdef Py_tp_vectorcall + {Py_tp_vectorcall, (vectorcallfunc)record_desc_vectorcall}, +#endif + {Py_tp_new, (newfunc)record_desc_new}, + {Py_tp_dealloc, (destructor)record_desc_dealloc}, + {Py_tp_getattro, PyObject_GenericGetAttr}, + {Py_tp_traverse, (traverseproc)record_desc_traverse}, + {Py_tp_methods, record_desc_methods}, + {0, NULL}, +}; + +static PyType_Spec ApgRecordDesc_TypeSpec = { + .name = "asyncpg.record.RecordDescriptor", + .basicsize = sizeof(ApgRecordDescObject), + .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_IMMUTABLETYPE, + .slots = ApgRecordDesc_TypeSlots, +}; + +/* + * Module init + */ + +static PyMethodDef record_module_methods[] = {{NULL, NULL, 0, NULL}}; + +static int +record_module_exec(PyObject *module) +{ + record_module_state *state = get_module_state(module); + if (state == NULL) { + return -1; + } + + if (PyThread_tss_create(&state->freelist_key) != 0) { + PyErr_SetString( + PyExc_SystemError, + "failed to create TSS key for record freelist"); + return -1; + } + +#define CREATE_TYPE(m, tp, spec) \ + do { \ + tp = (PyTypeObject *)PyType_FromModuleAndSpec(m, spec, NULL); \ + if (tp == NULL) \ + goto error; \ + if (PyModule_AddType(m, tp) < 0) \ + goto error; \ + } while (0) + + CREATE_TYPE(module, state->ApgRecord_Type, &ApgRecord_TypeSpec); + CREATE_TYPE(module, state->ApgRecordDesc_Type, &ApgRecordDesc_TypeSpec); + CREATE_TYPE(module, state->ApgRecordIter_Type, &ApgRecordIter_TypeSpec); + CREATE_TYPE(module, state->ApgRecordItems_Type, &ApgRecordItems_TypeSpec); + +#undef CREATE_TYPE + + return 0; + +error: + Py_CLEAR(state->ApgRecord_Type); + Py_CLEAR(state->ApgRecordDesc_Type); + Py_CLEAR(state->ApgRecordIter_Type); + Py_CLEAR(state->ApgRecordItems_Type); + return -1; +} + +static int +record_module_traverse(PyObject *module, visitproc visit, void *arg) +{ + record_module_state *state = get_module_state(module); + if (state == NULL) { + return 0; + } + + Py_VISIT(state->ApgRecord_Type); + Py_VISIT(state->ApgRecordDesc_Type); + Py_VISIT(state->ApgRecordIter_Type); + Py_VISIT(state->ApgRecordItems_Type); + + return 0; +} + +static int +record_module_clear(PyObject *module) +{ + record_module_state *state = get_module_state(module); + if (state == NULL) { + return 0; + } + + if (PyThread_tss_is_created(&state->freelist_key)) { + record_freelist_state *freelist = + (record_freelist_state *)PyThread_tss_get(&state->freelist_key); + if (freelist != NULL) { + for (int i = 0; i < ApgRecord_MAXSAVESIZE; i++) { + ApgRecordObject *op = freelist->freelist[i]; + while (op != NULL) { + ApgRecordObject *next = (ApgRecordObject *)(op->ob_item[0]); + PyObject_GC_Del(op); + op = next; + } + freelist->freelist[i] = NULL; + freelist->numfree[i] = 0; + } + PyMem_Free(freelist); + PyThread_tss_set(&state->freelist_key, NULL); + } + + PyThread_tss_delete(&state->freelist_key); + } + + Py_CLEAR(state->ApgRecord_Type); + Py_CLEAR(state->ApgRecordDesc_Type); + Py_CLEAR(state->ApgRecordIter_Type); + Py_CLEAR(state->ApgRecordItems_Type); + + return 0; +} + +static void +record_module_free(void *module) +{ + record_module_clear((PyObject *)module); +} + +static PyModuleDef_Slot record_module_slots[] = { + {Py_mod_exec, record_module_exec}, +#ifdef Py_mod_multiple_interpreters + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, +#endif +#ifdef Py_mod_gil + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, +#endif + {0, NULL}, +}; + +static struct PyModuleDef _recordmodule = { + PyModuleDef_HEAD_INIT, + .m_name = "asyncpg.record", + .m_size = sizeof(record_module_state), + .m_methods = record_module_methods, + .m_slots = record_module_slots, + .m_traverse = record_module_traverse, + .m_clear = record_module_clear, + .m_free = record_module_free, +}; + +PyMODINIT_FUNC +PyInit_record(void) +{ + return PyModuleDef_Init(&_recordmodule); +} diff --git a/asyncpg/record/recordobj.h b/asyncpg/record/recordobj.h new file mode 100644 index 00000000..78caffcc --- /dev/null +++ b/asyncpg/record/recordobj.h @@ -0,0 +1,33 @@ +#ifndef APG_RECORDOBJ_H +#define APG_RECORDOBJ_H + +#include + + +typedef struct { + PyObject_HEAD + PyObject *mapping; + PyObject *keys; +} ApgRecordDescObject; + + +typedef struct { + PyObject_VAR_HEAD + Py_hash_t self_hash; + ApgRecordDescObject *desc; + PyObject *ob_item[1]; + + /* ob_item contains space for 'ob_size' elements. + * Items must normally not be NULL, except during construction when + * the record is not yet visible outside the function that builds it. + */ +} ApgRecordObject; + + +#define ApgRecord_SET_ITEM(op, i, v) \ + (((ApgRecordObject *)(op))->ob_item[i] = v) + +#define ApgRecord_GET_ITEM(op, i) \ + (((ApgRecordObject *)(op))->ob_item[i]) + +#endif diff --git a/pyproject.toml b/pyproject.toml index 131a7372..7ee648cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,9 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Free Threading :: 2 - Beta", "Topic :: Database :: Front-Ends", ] dependencies = [ @@ -45,21 +47,22 @@ test = [ 'flake8~=6.1', 'flake8-pyi~=24.1.0', 'distro~=1.9.0', - 'uvloop>=0.15.3; platform_system != "Windows" and python_version < "3.14.0"', + 'uvloop>=0.22.1; platform_system != "Windows" and python_version < "3.15.0"', 'gssapi; platform_system == "Linux"', 'k5test; platform_system == "Linux"', 'sspilib; platform_system == "Windows"', 'mypy~=1.8.0', + 'pytest', ] docs = [ - 'Sphinx~=8.1.3', + 'Sphinx~=7.4', 'sphinx_rtd_theme>=1.2.2', ] [build-system] requires = [ "setuptools>=77.0.3", - "Cython(>=0.29.24,<4.0.0)" + "Cython(>=3.1.0,<4.0.0)" ] build-backend = "setuptools.build_meta" diff --git a/setup.py b/setup.py index 29e9d612..3b40a7fe 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ from setuptools.command import build_ext as setuptools_build_ext -CYTHON_DEPENDENCY = 'Cython(>=0.29.24,<4.0.0)' +CYTHON_DEPENDENCY = 'Cython(>=3.1.0,<4.0.0)' CFLAGS = ['-O2'] LDFLAGS = [] @@ -128,17 +128,26 @@ def initialize_options(self): super(build_ext, self).initialize_options() + defines = [ + "CYTHON_USE_MODULE_STATE", + "CYTHON_PEP489_MULTI_PHASE_INIT", + "CYTHON_USE_TYPE_SPECS", + ] + if os.environ.get('ASYNCPG_DEBUG'): self.cython_always = True self.cython_annotate = True self.cython_directives = "linetrace=True" - self.define = 'PG_DEBUG,CYTHON_TRACE,CYTHON_TRACE_NOGIL' self.debug = True + + defines += ["PG_DEBUG", "CYTHON_TRACE", "CYTHON_TRACE_NOGIL"] else: self.cython_always = False self.cython_annotate = None self.cython_directives = None + self.define = ",".join(defines) + def finalize_options(self): # finalize_options() may be called multiple times on the # same command object, so make sure not to override previously @@ -201,6 +210,8 @@ def finalize_options(self): directives = { 'language_level': '3', + 'freethreading_compatible': 'True', + 'subinterpreters_compatible': 'own_gil', } if self.cython_directives: @@ -231,7 +242,7 @@ def finalize_options(self): setup_requires.append(CYTHON_DEPENDENCY) -setuptools.setup( +_ = setuptools.setup( version=VERSION, ext_modules=[ setuptools.extension.Extension( @@ -240,10 +251,16 @@ def finalize_options(self): extra_compile_args=CFLAGS, extra_link_args=LDFLAGS), + setuptools.extension.Extension( + "asyncpg.record", + ["asyncpg/record/recordobj.c"], + include_dirs=['asyncpg/record/'], + extra_compile_args=CFLAGS, + extra_link_args=LDFLAGS), + setuptools.extension.Extension( "asyncpg.protocol.protocol", - ["asyncpg/protocol/record/recordobj.c", - "asyncpg/protocol/protocol.pyx"], + ["asyncpg/protocol/protocol.pyx"], include_dirs=['asyncpg/pgproto/'], extra_compile_args=CFLAGS, extra_link_args=LDFLAGS), diff --git a/tests/test_record.py b/tests/test_record.py index ef9388bb..0a8b7125 100644 --- a/tests/test_record.py +++ b/tests/test_record.py @@ -151,7 +151,7 @@ def test_record_values(self): r = Record(R_AB, (42, 43)) vv = r.values() self.assertEqual(tuple(vv), (42, 43)) - self.assertTrue(repr(vv).startswith(' +# +# This module is part of asyncpg and is released under +# the Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0 + + +import sys +import unittest + + +@unittest.skipIf( + sys.version_info < (3, 14), + "Subinterpreter support requires Python 3.14+", +) +class TestSubinterpreters(unittest.TestCase): + def setUp(self) -> None: + from concurrent import interpreters + self.interpreters = interpreters + + def test_record_module_loads_in_subinterpreter(self) -> None: + interp = self.interpreters.create() + + try: + code = """ +import asyncpg.record as record +assert record.Record is not None +""" + interp.exec(code) + finally: + interp.close() + + def test_record_module_state_isolation(self) -> None: + import asyncpg.record + + main_record_id = id(asyncpg.record.Record) + + interp = self.interpreters.create() + + try: + code = f""" +import asyncpg.record as record + +sub_record_id = id(record.Record) +main_id = {main_record_id} + +assert sub_record_id != main_id, ( + f"Record type objects are the same: {{sub_record_id}} == {{main_id}}. " + "This indicates shared global state." +) +""" + interp.exec(code) + finally: + interp.close() + + +if __name__ == "__main__": + unittest.main()