diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 2457650df..79d79d925 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -174,7 +174,9 @@ jobs: strategy: matrix: pyver: - - 3.13-freethreading + - 3.14t + - 3.14 + - 3.13t - 3.13 - 3.12 - 3.11 @@ -191,9 +193,9 @@ jobs: - os: windows no-extensions: Y - os: macos - pyver: 3.13-freethreading # this is still tested within cibuildwheel + pyver: 3.13t # this is still tested within cibuildwheel - os: windows - pyver: 3.13-freethreading # this is still tested within cibuildwheel + pyver: 3.13t # this is still tested within cibuildwheel - no-extensions: Y debug: Y include: @@ -237,18 +239,10 @@ jobs: path: dist - name: Setup Python ${{ matrix.pyver }} - if: >- - !endsWith(matrix.pyver, '-freethreading') uses: actions/setup-python@v5 with: python-version: ${{ matrix.pyver }} allow-prereleases: true - - name: Setup Python ${{ matrix.pyver }} - if: endsWith(matrix.pyver, '-freethreading') - uses: deadsnakes/action@v3.2.0 - with: - python-version: 3.13-dev - nogil: true - name: Compute runtime Python version id: python-install run: | diff --git a/MANIFEST.in b/MANIFEST.in index 43625782e..6ffcbced8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -12,8 +12,10 @@ graft requirements graft tests global-exclude *.pyc include multidict/*.c +include multidict/__init__.pxd +include multidict/multidict_api.h +include multidict/_multilib/*.h exclude multidict/_multidict.html exclude multidict/*.so exclude multidict/*.pyd -exclude multidict/*.pyd prune docs/_build diff --git a/docs/multidict.rst b/docs/multidict.rst index 8326dafa4..e11fd743e 100644 --- a/docs/multidict.rst +++ b/docs/multidict.rst @@ -533,6 +533,14 @@ The library is also shipped with a C-API, the header files can be compiled using :returns: A Capsule Containing the Multidict CAPI :retval NULL: if object fails to import +.. c:function:: PyObject* MultiDict_New(MultiDict_CAPI* api, int prealloc_size) + + Creates a Multidict of a pre-allocated size + + :param api: Python Capsule Pointer + :param prealloc_size: the number of elements to allocate on the heap + :retval NULL: if an exception was raised + .. c:function:: PyTypeObject* MultiDict_GetType(MultiDict_CAPI* api) @@ -562,7 +570,7 @@ The library is also shipped with a C-API, the header files can be compiled using :param key: The key of the entry to add :param value: The value of the entry to add :retval 0: on success - :reval -1: on failure + :retval -1: on failure .. c:function:: int MultiDict_Clear(MultiDict_CAPI* api, PyObject* self) @@ -574,7 +582,7 @@ The library is also shipped with a C-API, the header files can be compiled using :retval -1: on failure and will raise :exc:`TypeError` if :class:`MultiDict` Type is incorrect -.. c:function:: PyObject* Multidict_SetDefault(MultiDict_CAPI* api, PyObject* self, PyObject* key, PyObject* _default) +.. c:function:: PyObject* MultiDict_SetDefault(MultiDict_CAPI* api, PyObject* self, PyObject* key, PyObject* _default) If key is in the dictionary its the first value. If not, insert key with a value of default and return default. @@ -586,7 +594,7 @@ The library is also shipped with a C-API, the header files can be compiled using :returns: the default object on success :retval NULL: on failure -.. c:function:: int MutliDict_Del(MultiDict_CAPI* api, PyObject* self, PyObject* key) +.. c:function:: int MultiDict_Del(MultiDict_CAPI* api, PyObject* self, PyObject* key) Remove all items where key is equal to key from d. @@ -615,6 +623,20 @@ The library is also shipped with a C-API, the header files can be compiled using :retval 0: if false, :retval -1: if failure had occurred + +.. c:function:: PyObject* MultiDict_GetAll(MultiDict_CAPI* api, PyObject* self, PyObject* key, PyObject** result) + + Return the all values corresponding to *key* if *key* is in the + dictionary, else *NULL*. + + :param api: Python Capsule Pointer + :param self: the :class:`MultiDict` object + :param key: the key to get one item from + :param result: the object to attached the obtained object to. + :retval 1: on success + :retval 0: on failure (No exceptions are raised) + :retval -1: on :exc:`TypeError` raised + .. c:function:: PyObject* MultiDict_GetOne(MultiDict_CAPI* api, PyObject* self, PyObject* key, PyObject** result) Return the **first** value for *key* if *key* is in the @@ -678,7 +700,7 @@ The library is also shipped with a C-API, the header files can be compiled using :param api: Python Capsule Pointer :param self: the :class:`MultiDict` object - :param other: a multidict object to update corresponding object with + :param other: a :class:`MultiDict`, :class:`CIMultiDict`, :class:`MultiDictProxy` or :class:`CIMultiDictProxy` object to update corresponding object with :param op: :c:enum:`UpdateOp` operation for extending, updating, or merging values. :retval 0: on success :retval -1: on failure @@ -707,3 +729,369 @@ The library is also shipped with a C-API, the header files can be compiled using :param op: :c:enum:`UpdateOp` operation for extending, updating, or merging values. :retval 0: on success :retval -1: on failure + + +.. c:function:: PyObject* MultiDictProxy_New(MultiDict_CAPI* api, PyObject* md) + + creates a :class:`MultiDictProxy` from any other Multidict object + making an immutable version of it. + + :param api: Python Capsule Pointer + :param md: the :class:`MultiDict` object to make immutable + :returns: a :class:`MultiDictProxy` on success + :retval NULL: on failure and raises :exc:`TypeError` + + +.. c:function:: int MultiDictProxy_Contains(MultiDict_CAPI* api, PyObject* self, PyObject* key) + + Determines if a certain key exists a :class:`MultiDictProxy` object + + :param api: Python Capsule Pointer + :param self: the :class:`MultiDictProxy` object + :param key: the key to look for + :retval 1: if true + :retval 0: if false, + :retval -1: if failure had occurred + +.. c:function:: PyObject* MultiDictProxy_GetAll(MultiDict_CAPI* api, PyObject* self, PyObject* key, PyObject** result) + + Return the all values corresponding to *key* if *key* is in the + dictionary, else *NULL*. + + :param api: Python Capsule Pointer + :param self: the :class:`MultiDictProxy` object + :param key: the key to get one item from + :param result: the object to attached the obtained object to. + :retval 1: on success + :retval 0: on failure (No exceptions are raised) + :retval -1: on :exc:`TypeError` raised + +.. c:function:: PyObject* MultiDictProxy_GetOne(MultiDict_CAPI* api, PyObject* self, PyObject* key, PyObject** result) + + Return the **first** value for *key* if *key* is in the + dictionary, else *NULL*. + + :param api: Python Capsule Pointer + :param self: the :class:`MultiDictProxy` object + :param key: the key to get one item from + :param result: the object to attached the obtained object to. + :retval 1: on success + :retval 0: on failure (No exceptions are raised) + :retval -1: on :exc:`TypeError` raised + +.. c:function:: PyTypeObject* MultiDictProxy_GetType(MultiDict_CAPI* api) + + Obtains the :class:`MultiDictProxy` TypeObject. + + :param api: Python Capsule Pointer + :returns: return A CPython `PyTypeObject` is returned as a pointer + :retval NULL: if an exception was raised + +.. c:function:: PyObject* CIMultiDict_New(MultiDict_CAPI* api, int prealloc_size) + + Creates a :class:`CIMultiDict` of a pre-allocated size + + :param api: Python Capsule Pointer + :param prealloc_size: the number of elements to allocate on the heap + :retval NULL: if an exception was raised + + +.. c:function:: PyTypeObject* CIMultiDict_GetType(MultiDict_CAPI* api) + + Obtains the :class:`CIMultiDict` TypeObject. + + :param api: Python Capsule Pointer + :returns: return A CPython `PyTypeObject` is returned as a pointer + :retval NULL: if an exception was raised + + +.. c:function:: int CIMultiDict_CheckExact(MultiDict_CAPI* api, PyObject* op) + + Checks if :class:`CIMultiDict` object type matches exactly. + + :param api: Python Capsule Pointer + :param op: The Object to check + :retval 1: if true + :retval 0: if false + :retval -1: if exception was raised + +.. c:function:: int CIMultiDict_Add(MultiDict_CAPI* api, PyObject* self, PyObject* key, PyObject* value) + + Adds a new entry to the :class:`CIMultiDict` object. + + :param api: Python Capsule Pointer + :param self: the Multidict object + :param key: The key of the entry to add + :param value: The value of the entry to add + :retval 0: on success + :retval -1: on failure + +.. c:function:: int CIMultiDict_Clear(MultiDict_CAPI* api, PyObject* self) + + Clears a :class:`CIMultiDict` object and removes all it's entries. + + :param api: Python Capsule Pointer + :param self: the :class:`CIMultiDict` object + :retval 0: if success + :retval -1: on failure and will raise :exc:`TypeError` if :class:`CIMultiDict` Type is incorrect + + +.. c:function:: PyObject* CIMultiDict_SetDefault(MultiDict_CAPI* api, PyObject* self, PyObject* key, PyObject* _default) + + If key is in the dictionary its the first value. + If not, insert key with a value of default and return default. + + :param api: Python Capsule Pointer + :param self: the :class:`CIMultiDict` object + :param key: the key to insert + :param _default: the default value to have inserted + :returns: the default object on success + :retval NULL: on failure + +.. c:function:: int CIMutliDict_Del(MultiDict_CAPI* api, PyObject* self, PyObject* key) + + Remove all items where key is equal to key from d. + + :param api: Python Capsule Pointer + :param self: the :class:`CIMultiDict` object + :param key: the key to be removed + :retval 0: on success, + :retval -1: on failure followed by raising either :exc:`TypeError` or :exc:`KeyError` if key is not in the map. + +.. c:function:: uint64_t CIMultiDict_Version(MultiDict_CAPI* api, PyObject* self) + + Return a version of given :class:`CIMultiDict` object + + :param api: Python Capsule Pointer + :param self: the :class:`CIMultiDict` object + :returns: the version flag of the object, otherwise 0 on failure + +.. c:function:: int CIMultiDict_Contains(MultiDict_CAPI* api, PyObject* self, PyObject* key) + + Determines if a certain key exists a multidict object + + :param api: Python Capsule Pointer + :param self: the :class:`CIMultiDict` object + :param key: the key to look for + :retval 1: if true + :retval 0: if false, + :retval -1: if failure had occurred + + +.. c:function:: PyObject* CIMultiDict_GetAll(MultiDict_CAPI* api, PyObject* self, PyObject* key, PyObject** result) + + Return the all values corresponding to *key* if *key* is in the + dictionary, else *NULL*. + + :param api: Python Capsule Pointer + :param self: the :class:`CIMultiDict` object + :param key: the key to get one item from + :param result: the object to attached the obtained object to. + :retval 1: on success + :retval 0: on failure (No exceptions are raised) + :retval -1: on :exc:`TypeError` raised + +.. c:function:: PyObject* CIMultiDict_GetOne(MultiDict_CAPI* api, PyObject* self, PyObject* key, PyObject** result) + + Return the **first** value for *key* if *key* is in the + dictionary, else *NULL*. + + :param api: Python Capsule Pointer + :param self: the :class:`CIMultiDict` object + :param key: the key to get one item from + :param result: the object to attached the obtained object to. + :retval 1: on success + :retval 0: on failure (No exceptions are raised) + :retval -1: on :exc:`TypeError` raised + +.. c:function:: PyObject* CIMultiDict_PopOne(MultiDict_CAPI* api, PyObject* self, PyObject* key) + + Remove and return a value from the dictionary. + + :param api: Python Capsule Pointer + :param self: the :class:`CIMultiDict` object + :param key: the key to remove + :returns: corresponding value on success or None + :retval NULL: on failure and raises :exc:`TypeError` + +.. c:function:: PyObject* CIMultiDict_PopAll(MultiDict_CAPI* api, PyObject* self, PyObject* key) + + Pops all related objects corresponding to `key` + + :param api: Python Capsule Pointer + :param self: the :class:`CIMultiDict` object + :param key: the key to pop all of + :returns: :class:`list` object on success + :retval NULL: on error and raises either :exc:`KeyError` or :exc:`TypeError` + + +.. c:function:: PyObject* CIMultiDict_PopItem(MultiDict_CAPI* api, PyObject* self) + + Remove and return an arbitrary ``(key, value)`` pair from the + dictionary. + + :param api: Python Capsule Pointer + :param self: the :class:`CIMultiDict` object + :returns: an arbitrary tuple on success + :retval NULL: on error along with :exc:`TypeError` or :exc:`KeyError` raised + + +.. c:function:: int CIMultiDict_Replace(MultiDict_CAPI* api, PyObject* self, PyObject* key, PyObject* value) + + Replaces a set object with another object + + :param api: Python Capsule Pointer + :param self: the :class:`CIMultiDict` object + :param key: the key to lookup for replacement + :param value: the value to replace with + :retval 0: on success + :retval -1: on failure and raises :exc:`TypeError` + + +.. c:function:: int CIMultiDict_UpdateFromMultiDict(MultiDict_CAPI* api, PyObject* self, PyObject* other, UpdateOp op) + + Updates Multidict object using another MultiDict Object + + :param api: Python Capsule Pointer + :param self: the :class:`CIMultiDict` object + :param other: a :class:`MultiDict`, :class:`CIMultiDict`, :class:`MultiDictProxy` or :class:`CIMultiDictProxy` object to update corresponding object with + :param op: :c:enum:`UpdateOp` operation for extending, updating, or merging values. + :retval 0: on success + :retval -1: on failure + + + +.. c:function:: int CIMultiDict_UpdateFromDict(MultiDict_CAPI* api, PyObject* self, PyObject* kwds, UpdateOp op) + + Updates Multidict object using another Dictionary Object + + :param api: Python Capsule Pointer + :param self: the :class:`CIMultiDict` object + :param kwds: the keywords or Dictionary object to merge + :param op: :c:enum:`UpdateOp` operation for extending, updating, or merging values. + :retval 0: on success + :retval -1: on failure + + +.. c:function:: int CIMultiDict_UpdateFromSequence(MultiDict_CAPI* api, PyObject* self, PyObject* seq, UpdateOp op) + + Updates Multidict object using a sequence object + + :param api: Python Capsule Pointer + :param self: the :class:`CIMultiDict` object + :param seq: the sequence to merge with. + :param op: :c:enum:`UpdateOp` operation for extending, updating, or merging values. + :retval 0: on success + :retval -1: on failure + + +.. c:function:: PyObject* CIMultiDictProxy_New(MultiDict_CAPI* api, PyObject* md) + + creates a :class:`CIMultiDictProxy` from any other Multidict object + making an immutable version of it. + + :param api: Python Capsule Pointer + :param md: the :class:`CIMultiDict` object to make immutable + :returns: a :class:`CIMultiDictProxy` on success + :retval NULL: on failure and raises :exc:`TypeError` + + +.. c:function:: int CIMultiDictProxy_Contains(MultiDict_CAPI* api, PyObject* self, PyObject* key) + + Determines if a certain key exists a :class:`CIMultiDictProxy` object + + :param api: Python Capsule Pointer + :param self: the :class:`CIMultiDictProxy` object + :param key: the key to look for + :retval 1: if true + :retval 0: if false, + :retval -1: if failure had occurred + +.. c:function:: PyObject* CIMultiDictProxy_GetAll(MultiDict_CAPI* api, PyObject* self, PyObject* key, PyObject** result) + + Return the all values corresponding to *key* if *key* is in the + dictionary, else *NULL*. + + :param api: Python Capsule Pointer + :param self: the :class:`CIMultiDictProxy` object + :param key: the key to get one item from + :param result: the object to attached the obtained object to. + :retval 1: on success + :retval 0: on failure (No exceptions are raised) + :retval -1: on :exc:`TypeError` raised + +.. c:function:: PyObject* CIMultiDictProxy_GetOne(MultiDict_CAPI* api, PyObject* self, PyObject* key, PyObject** result) + + Return the **first** value for *key* if *key* is in the + dictionary, else *NULL*. + + :param api: Python Capsule Pointer + :param self: the :class:`CIMultiDictProxy` object + :param key: the key to get one item from + :param result: the object to attached the obtained object to. + :retval 1: on success + :retval 0: on failure (No exceptions are raised) + :retval -1: on :exc:`TypeError` raised + +.. c:function:: PyTypeObject* CIMultiDictProxy_GetType(MultiDict_CAPI* api) + + Obtains the :class:`CIMultiDictProxy` TypeObject. + + :param api: Python Capsule Pointer + :returns: return A CPython `PyTypeObject` is returned as a pointer + :retval NULL: if an exception was raised + +.. c:function:: PyObject* IStr_FromUnicode(MultiDict_CAPI* api, PyObject* str) + + Creates a :class:`istr` object from a string object. + + :param api: Python Capsule Pointer + :param str: the string object + :returns: A :class:`istr` object on success + :retval NULL: if an exception was raised + +.. c:function:: PyObject* IStr_FromStringAndSize(MultiDict_CAPI* api, const char* str, Py_ssize_t size) + + Creates a :class:`istr` object from an array of characters or c buffer. + + :param api: Python Capsule Pointer + :param str: the array of characters + :param size: the number of characters to use + :returns: A :class:`istr` object on success + :retval NULL: if an exception was raised + +.. c:function:: PyObject* IStr_FromString(MultiDict_CAPI* api, const char* str) + + Create a :class:`istr` object from an array of characters or c buffer. + + :param api: Python Capsule Pointer + :param str: the array of characters + :returns: A :class:`istr` object on success + :retval NULL: if an exception was raised + +.. c:function:: PyTypeObject* IStr_GetType(MultiDict_CAPI* api) + + Obtains the :class:`istr` TypeObject. + + :param api: Python Capsule Pointer + :returns: return A CPython `PyTypeObject` is returned as a pointer + :retval NULL: if an exception was raised + +.. c:function:: PyObject* MultiDictIter_New(MultiDict_CAPI* api, PyObject* self) + + Creates a new iterator from :class:`MultiDict`, :class:`CIMultiDict`, :class:`MultiDictProxy` or :class:`CIMultiDictProxy` + + :param api: Python Capsule Pointer + :param self: A :class:`MultiDict`, :class:`CIMultiDict`, :class:`MultiDictProxy` or :class:`CIMultiDictProxy` object to iterate over + :returns: A new iterable object + :retval NULL: if an exception was raised + +.. c:function:: int MultiDictIter_Next(MultiDict_CAPI* api, PyObject* self, PyObject** key, PyObject** value) + + Obtains the next key and value from the iterable object. + + :param api: Python Capsule Pointer + :param self: an iterable object created from :c:func:`MultiDictIter_New` + :retval 1: iterable has more items + :retval 0: the iterable object is done + :retval -1: if an exception was raised diff --git a/multidict/__init__.pxd b/multidict/__init__.pxd new file mode 100644 index 000000000..6de4cb428 --- /dev/null +++ b/multidict/__init__.pxd @@ -0,0 +1,460 @@ +# cython: language_level = 3, freethreading_compatible=True +from cpython.object cimport PyObject, PyTypeObject +from libc.stdint cimport uint64_t + +# WARNING: THIS FILE IS AUTOGENERATED DO NOT EDIT!!! +# YOUR CHANGES WILL GET OVERWRITTEN AND YOUR POOR EDITS WILL BE GONE!!! +# GENERATED ON: 2025-07-18 22:30:40.701692+00:00 +# COMPILIED BY: anonymous + +cdef extern from "multidict_api.h": + """ +/* Extra Data comes from multidict.__init__.pxd */ +/* Ensure we can obtain the Functions we wish to utilize */ +#define MULTIDICT_IMPL +MultiDict_CAPI* __cython_multidict_api; + +// Redefinitions incase required by cython... +// Don't want size calculations to get screwed up... + +#if PY_VERSION_HEX >= 0x030c00f0 +#define __MANAGED_WEAKREFS +#endif + +typedef struct _htkeys { + uint8_t log2_size; + uint8_t log2_index_bytes; + Py_ssize_t usable; + Py_ssize_t nentries; + char indices[]; +} htkeys_t; + +typedef struct { + PyObject_HEAD +#ifndef __MANAGED_WEAKREFS + PyObject *weaklist; +#endif + // we can ignore state however we already know it's + // a size of 4/8 depending on 32/64 bit... + void *state; + Py_ssize_t used; + uint64_t version; + bool is_ci; + htkeys_t *keys; +} MultiDictObject; + +typedef struct { + PyObject_HEAD +#ifndef __MANAGED_WEAKREFS + PyObject *weaklist; +#endif + MultiDictObject *md; +} MultiDictProxyObject; + +typedef struct { + PyUnicodeObject str; + PyObject *canonical; + void *state; +} istrobject; + +int multidict_import(){ + __cython_multidict_api = MultiDict_Import(); + return __cython_multidict_api != NULL; +} + """ + # NOTE: Important that you import this + # After you've c-imported multidict + int multidict_import() except 0 + # Predefined objects from istr & multidict + ctypedef struct MultiDictObject: + pass + + ctypedef struct MultiDictProxyObject: + pass + + ctypedef struct istrobject: + pass + + ctypedef class multidict._multidict.istr [object istrobject, check_size ignore]: + pass + + ctypedef class multidict._multidict.MultiDict [object MultiDictObject, check_size ignore]: + pass + + ctypedef class multidict._multidict.CIMultiDict [object MultiDictObject, check_size ignore]: + pass + + ctypedef class multidict._multidict.MultiDictProxy [object MultiDictProxyObject, check_size ignore]: + pass + + ctypedef class multidict._multidict.CIMultiDictProxy [object MultiDictProxyObject, check_size ignore]: + pass + + + + enum _UpdateOp: + Extend = 0 + Update = 1 + Merge = 2 + + ctypedef _UpdateOp UpdateOp + + struct MultiDict_CAPI: + PyTypeObject * (*MultiDict_GetType)(void * state) + PyObject * (*MultiDict_New)(void * state, int prealloc_size) + int (*MultiDict_Add)(void * state, object self, object key, object value) + int (*MultiDict_Clear)(void * state, object self) + int (*MultiDict_SetDefault)(void * state, object self, object key, object default_, PyObject ** result) + int (*MultiDict_Del)(void * state, object self, object key) + uint64_t (*MultiDict_Version)(void * state, object self) + int (*MultiDict_Contains)(void * state, object self, object key) + int (*MultiDict_GetOne)(void * state, object self, object key, PyObject ** result) + int (*MultiDict_GetAll)(void * state, object self, object key, PyObject ** result) + int (*MultiDict_PopOne)(void * state, object self, object key, PyObject ** result) + int (*MultiDict_PopAll)(void * state, object self, object key, PyObject ** result) + PyObject * (*MultiDict_PopItem)(void * state, object self) + int (*MultiDict_Replace)(void * state, object self, object key, object value) + int (*MultiDict_UpdateFromMultiDict)(void * state, object self, object other, UpdateOp op) + int (*MultiDict_UpdateFromDict)(void * state, object self, object kwds, UpdateOp op) + int (*MultiDict_UpdateFromSequence)(void * state, object self, object kwds, UpdateOp op) + PyObject * (*MultiDictProxy_New)(void * state, object md) + int (*MultiDictProxy_Contains)(void * state, object self, object key) + int (*MultiDictProxy_GetAll)(void * state, object self, object key, PyObject ** result) + int (*MultiDictProxy_GetOne)(void * state, object self, object key, PyObject ** result) + PyTypeObject * (*MultiDictProxy_GetType)(void * state) + PyObject * (*IStr_FromUnicode)(void * state, object str) + PyObject * (*IStr_FromStringAndSize)(void * state, const char * str, Py_ssize_t size) + PyObject * (*IStr_FromString)(void * state, const char * str) + PyTypeObject * (*IStr_GetType)(void * state) + PyTypeObject * (*CIMultiDict_GetType)(void * state) + PyObject * (*CIMultiDict_New)(void * state, int prealloc_size) + int (*CIMultiDict_Add)(void * state, object self, object key, object value) + int (*CIMultiDict_Clear)(void * state, object self) + int (*CIMultiDict_SetDefault)(void * state, object self, object key, object default_, PyObject ** result) + int (*CIMultiDict_Del)(void * state, object self, object key) + uint64_t (*CIMultiDict_Version)(void * state, object self) + int (*CIMultiDict_Contains)(void * state, object self, object key) + int (*CIMultiDict_GetOne)(void * state, object self, object key, PyObject ** result) + int (*CIMultiDict_GetAll)(void * state, object self, object key, PyObject ** result) + int (*CIMultiDict_PopOne)(void * state, object self, object key, PyObject ** result) + int (*CIMultiDict_PopAll)(void * state, object self, object key, PyObject ** result) + PyObject * (*CIMultiDict_PopItem)(void * state, object self) + int (*CIMultiDict_Replace)(void * state, object self, object key, object value) + int (*CIMultiDict_UpdateFromMultiDict)(void * state, object self, object other, UpdateOp op) + int (*CIMultiDict_UpdateFromDict)(void * state, object self, object kwds, UpdateOp op) + int (*CIMultiDict_UpdateFromSequence)(void * state, object self, object kwds, UpdateOp op) + PyObject * (*CIMultiDictProxy_New)(void * state, object md) + int (*CIMultiDictProxy_Contains)(void * state, object self, object key) + int (*CIMultiDictProxy_GetAll)(void * state, object self, object key, PyObject ** result) + int (*CIMultiDictProxy_GetOne)(void * state, object self, object key, PyObject ** result) + PyTypeObject * (*CIMultiDictProxy_GetType)(void * state) + PyObject * (*MultiDictIter_New)(void * state_, object self) + int (*MultiDictIter_Next)(void * state_, object self, PyObject ** key, PyObject ** value) + + + MultiDict_CAPI * C_MultiDict_Import "MultiDict_Import"() except NULL + + PyTypeObject * C_MultiDict_GetType "MultiDict_GetType"(MultiDict_CAPI * api) except NULL + + int C_MultiDict_CheckExact "MultiDict_CheckExact"(MultiDict_CAPI * api, object op) except -1 + + int C_MultiDict_Check "MultiDict_Check"(MultiDict_CAPI * api, object op) except -1 + + PyObject * C_MultiDict_New "MultiDict_New"(MultiDict_CAPI * api, int prealloc_size) except NULL + + int C_MultiDict_Add "MultiDict_Add"(MultiDict_CAPI * api, object self, object key, object value) except -1 + + int C_MultiDict_Clear "MultiDict_Clear"(MultiDict_CAPI * api, object self) except -1 + + int C_MultiDict_SetDefault "MultiDict_SetDefault"(MultiDict_CAPI * api, object self, object key, object default_, PyObject ** result) except -1 + + int C_MutliDict_Del "MutliDict_Del"(MultiDict_CAPI * api, object self, object key) except -1 + + uint64_t C_MultiDict_Version "MultiDict_Version"(MultiDict_CAPI * api, object self) + + int C_MultiDict_Contains "MultiDict_Contains"(MultiDict_CAPI * api, object self, object key) except -1 + + int C_MultiDict_GetOne "MultiDict_GetOne"(MultiDict_CAPI * api, object self, object key, PyObject ** result) except -1 + + int C_MultiDict_GetAll "MultiDict_GetAll"(MultiDict_CAPI * api, object self, object key, PyObject ** result) except -1 + + int C_MultiDict_PopOne "MultiDict_PopOne"(MultiDict_CAPI * api, object self, object key, PyObject ** result) except -1 + + int C_MultiDict_PopAll "MultiDict_PopAll"(MultiDict_CAPI * api, object self, object key, PyObject ** result) except -1 + + PyObject * C_MultiDict_PopItem "MultiDict_PopItem"(MultiDict_CAPI * api, object self) except NULL + + int C_MultiDict_Replace "MultiDict_Replace"(MultiDict_CAPI * api, object self, object key, object value) except -1 + + int C_MultiDict_UpdateFromMultiDict "MultiDict_UpdateFromMultiDict"(MultiDict_CAPI * api, object self, object other, UpdateOp op) except -1 + + int C_MultiDict_UpdateFromDict "MultiDict_UpdateFromDict"(MultiDict_CAPI * api, object self, object other, UpdateOp op) except -1 + + int C_MultiDict_UpdateFromSequence "MultiDict_UpdateFromSequence"(MultiDict_CAPI * api, object self, object seq, UpdateOp op) except -1 + + PyObject * C_MultiDictProxy_New "MultiDictProxy_New"(MultiDict_CAPI * api, object md) except NULL + + int C_MultiDictProxy_CheckExact "MultiDictProxy_CheckExact"(MultiDict_CAPI * api, object op) except -1 + + int C_MultiDictProxy_Check "MultiDictProxy_Check"(MultiDict_CAPI * api, object op) except -1 + + int C_MultiDictProxy_Contains "MultiDictProxy_Contains"(MultiDict_CAPI * api, object self, object key) except -1 + + int C_MultiDictProxy_GetAll "MultiDictProxy_GetAll"(MultiDict_CAPI * api, object self, object key, PyObject ** result) except -1 + + int C_MultiDictProxy_GetOne "MultiDictProxy_GetOne"(MultiDict_CAPI * api, object self, object key, PyObject ** result) except -1 + + PyTypeObject * C_MultiDictProxy_GetType "MultiDictProxy_GetType"(MultiDict_CAPI * api) except NULL + + int C_IStr_CheckExact "IStr_CheckExact"(MultiDict_CAPI * api, object op) except -1 + + int C_IStr_Check "IStr_Check"(MultiDict_CAPI * api, object op) except -1 + + PyObject * C_IStr_FromUnicode "IStr_FromUnicode"(MultiDict_CAPI * api, object str) except NULL + + PyObject * C_IStr_FromStringAndSize "IStr_FromStringAndSize"(MultiDict_CAPI * api, const char * str, Py_ssize_t size) except NULL + + PyObject * C_IStr_FromString "IStr_FromString"(MultiDict_CAPI * api, const char * str) except NULL + + PyTypeObject * C_IStr_GetType "IStr_GetType"(MultiDict_CAPI * api) except NULL + + PyTypeObject * C_CIMultiDict_GetType "CIMultiDict_GetType"(MultiDict_CAPI * api) except NULL + + int C_CIMultiDict_CheckExact "CIMultiDict_CheckExact"(MultiDict_CAPI * api, object op) except -1 + + int C_CIMultiDict_Check "CIMultiDict_Check"(MultiDict_CAPI * api, object op) except -1 + + PyObject * C_CIMultiDict_New "CIMultiDict_New"(MultiDict_CAPI * api, int prealloc_size) except NULL + + int C_CIMultiDict_Add "CIMultiDict_Add"(MultiDict_CAPI * api, object self, object key, object value) except -1 + + int C_CIMultiDict_Clear "CIMultiDict_Clear"(MultiDict_CAPI * api, object self) except -1 + + int C_CIMultiDict_SetDefault "CIMultiDict_SetDefault"(MultiDict_CAPI * api, object self, object key, object default_, PyObject ** result) except -1 + + int C_CIMutliDict_Del "CIMutliDict_Del"(MultiDict_CAPI * api, object self, object key) except -1 + + uint64_t C_CIMultiDict_Version "CIMultiDict_Version"(MultiDict_CAPI * api, object self) + + int C_CIMultiDict_Contains "CIMultiDict_Contains"(MultiDict_CAPI * api, object self, object key) except -1 + + int C_CIMultiDict_GetOne "CIMultiDict_GetOne"(MultiDict_CAPI * api, object self, object key, PyObject ** result) except -1 + + int C_CIMultiDict_GetAll "CIMultiDict_GetAll"(MultiDict_CAPI * api, object self, object key, PyObject ** result) except -1 + + int C_CIMultiDict_PopOne "CIMultiDict_PopOne"(MultiDict_CAPI * api, object self, object key, PyObject ** result) except -1 + + int C_CIMultiDict_PopAll "CIMultiDict_PopAll"(MultiDict_CAPI * api, object self, object key, PyObject ** result) except -1 + + PyObject * C_CIMultiDict_PopItem "CIMultiDict_PopItem"(MultiDict_CAPI * api, object self) except NULL + + int C_CIMultiDict_Replace "CIMultiDict_Replace"(MultiDict_CAPI * api, object self, object key, object value) except -1 + + int C_CIMultiDict_UpdateFromMultiDict "CIMultiDict_UpdateFromMultiDict"(MultiDict_CAPI * api, object self, object other, UpdateOp op) except -1 + + int C_CIMultiDict_UpdateFromDict "CIMultiDict_UpdateFromDict"(MultiDict_CAPI * api, object self, object other, UpdateOp op) except -1 + + int C_CIMultiDict_UpdateFromSequence "CIMultiDict_UpdateFromSequence"(MultiDict_CAPI * api, object self, object seq, UpdateOp op) except -1 + + PyObject * C_CIMultiDictProxy_New "CIMultiDictProxy_New"(MultiDict_CAPI * api, object md) except NULL + + int C_CIMultiDictProxy_CheckExact "CIMultiDictProxy_CheckExact"(MultiDict_CAPI * api, object op) except -1 + + int C_CIMultiDictProxy_Check "CIMultiDictProxy_Check"(MultiDict_CAPI * api, object op) except -1 + + int C_CIMultiDictProxy_Contains "CIMultiDictProxy_Contains"(MultiDict_CAPI * api, object self, object key) except -1 + + int C_CIMultiDictProxy_GetAll "CIMultiDictProxy_GetAll"(MultiDict_CAPI * api, object self, object key, PyObject ** result) except -1 + + int C_CIMultiDictProxy_GetOne "CIMultiDictProxy_GetOne"(MultiDict_CAPI * api, object self, object key, PyObject ** result) except -1 + + PyTypeObject * C_CIMultiDictProxy_GetType "CIMultiDictProxy_GetType"(MultiDict_CAPI * api) except NULL + + PyObject * C_MultiDictIter_New "MultiDictIter_New"(MultiDict_CAPI * api, object self) except NULL + + int C_MultiDictIter_Next "MultiDictIter_Next"(MultiDict_CAPI * api, object self, PyObject ** key, PyObject ** value) except -1 + MultiDict_CAPI* __cython_multidict_api + +# === Cython API === +cdef inline MultiDict_CAPI * MultiDict_Import(): + return C_MultiDict_Import() + +cdef inline type MultiDict_GetType(): + return C_MultiDict_GetType(__cython_multidict_api) + +cdef inline int MultiDict_CheckExact(object op): + return C_MultiDict_CheckExact(__cython_multidict_api, op) + +cdef inline int MultiDict_Check(object op): + return C_MultiDict_Check(__cython_multidict_api, op) + +cdef inline MultiDict MultiDict_New(int prealloc_size): + return C_MultiDict_New(__cython_multidict_api, prealloc_size) + +cdef inline int MultiDict_Add(object self, object key, object value): + return C_MultiDict_Add(__cython_multidict_api, self, key, value) + +cdef inline int MultiDict_Clear(object self): + return C_MultiDict_Clear(__cython_multidict_api, self) + +cdef inline int MultiDict_SetDefault(object self, object key, object default_, PyObject ** result): + return C_MultiDict_SetDefault(__cython_multidict_api, self, key, default_, result) + +cdef inline int MutliDict_Del(object self, object key): + return C_MutliDict_Del(__cython_multidict_api, self, key) + +cdef inline uint64_t MultiDict_Version(object self): + return C_MultiDict_Version(__cython_multidict_api, self) + +cdef inline int MultiDict_Contains(object self, object key): + return C_MultiDict_Contains(__cython_multidict_api, self, key) + +cdef inline int MultiDict_GetOne(object self, object key, PyObject ** result): + return C_MultiDict_GetOne(__cython_multidict_api, self, key, result) + +cdef inline int MultiDict_GetAll(object self, object key, PyObject ** result): + return C_MultiDict_GetAll(__cython_multidict_api, self, key, result) + +cdef inline int MultiDict_PopOne(object self, object key, PyObject ** result): + return C_MultiDict_PopOne(__cython_multidict_api, self, key, result) + +cdef inline int MultiDict_PopAll(object self, object key, PyObject ** result): + return C_MultiDict_PopAll(__cython_multidict_api, self, key, result) + +cdef inline tuple MultiDict_PopItem(object self): + return C_MultiDict_PopItem(__cython_multidict_api, self) + +cdef inline int MultiDict_Replace(object self, object key, object value): + return C_MultiDict_Replace(__cython_multidict_api, self, key, value) + +cdef inline int MultiDict_UpdateFromMultiDict(object self, object other, UpdateOp op): + return C_MultiDict_UpdateFromMultiDict(__cython_multidict_api, self, other, op) + +cdef inline int MultiDict_UpdateFromDict(object self, object other, UpdateOp op): + return C_MultiDict_UpdateFromDict(__cython_multidict_api, self, other, op) + +cdef inline int MultiDict_UpdateFromSequence(object self, object seq, UpdateOp op): + return C_MultiDict_UpdateFromSequence(__cython_multidict_api, self, seq, op) + +cdef inline MultiDictProxy MultiDictProxy_New(object md): + return C_MultiDictProxy_New(__cython_multidict_api, md) + +cdef inline int MultiDictProxy_CheckExact(object op): + return C_MultiDictProxy_CheckExact(__cython_multidict_api, op) + +cdef inline int MultiDictProxy_Check(object op): + return C_MultiDictProxy_Check(__cython_multidict_api, op) + +cdef inline int MultiDictProxy_Contains(object self, object key): + return C_MultiDictProxy_Contains(__cython_multidict_api, self, key) + +cdef inline int MultiDictProxy_GetAll(object self, object key, PyObject ** result): + return C_MultiDictProxy_GetAll(__cython_multidict_api, self, key, result) + +cdef inline int MultiDictProxy_GetOne(object self, object key, PyObject ** result): + return C_MultiDictProxy_GetOne(__cython_multidict_api, self, key, result) + +cdef inline type MultiDictProxy_GetType(): + return C_MultiDictProxy_GetType(__cython_multidict_api) + +cdef inline int IStr_CheckExact(object op): + return C_IStr_CheckExact(__cython_multidict_api, op) + +cdef inline int IStr_Check(object op): + return C_IStr_Check(__cython_multidict_api, op) + +cdef inline istr IStr_FromUnicode(object str): + return C_IStr_FromUnicode(__cython_multidict_api, str) + +cdef inline istr IStr_FromStringAndSize(const char * str, Py_ssize_t size): + return C_IStr_FromStringAndSize(__cython_multidict_api, str, size) + +cdef inline istr IStr_FromString(const char * str): + return C_IStr_FromString(__cython_multidict_api, str) + +cdef inline type IStr_GetType(): + return C_IStr_GetType(__cython_multidict_api) + +cdef inline type CIMultiDict_GetType(): + return C_CIMultiDict_GetType(__cython_multidict_api) + +cdef inline int CIMultiDict_CheckExact(object op): + return C_CIMultiDict_CheckExact(__cython_multidict_api, op) + +cdef inline int CIMultiDict_Check(object op): + return C_CIMultiDict_Check(__cython_multidict_api, op) + +cdef inline CIMultiDict CIMultiDict_New(int prealloc_size): + return C_CIMultiDict_New(__cython_multidict_api, prealloc_size) + +cdef inline int CIMultiDict_Add(object self, object key, object value): + return C_CIMultiDict_Add(__cython_multidict_api, self, key, value) + +cdef inline int CIMultiDict_Clear(object self): + return C_CIMultiDict_Clear(__cython_multidict_api, self) + +cdef inline int CIMultiDict_SetDefault(object self, object key, object default_, PyObject ** result): + return C_CIMultiDict_SetDefault(__cython_multidict_api, self, key, default_, result) + +cdef inline int CIMutliDict_Del(object self, object key): + return C_CIMutliDict_Del(__cython_multidict_api, self, key) + +cdef inline uint64_t CIMultiDict_Version(object self): + return C_CIMultiDict_Version(__cython_multidict_api, self) + +cdef inline int CIMultiDict_Contains(object self, object key): + return C_CIMultiDict_Contains(__cython_multidict_api, self, key) + +cdef inline int CIMultiDict_GetOne(object self, object key, PyObject ** result): + return C_CIMultiDict_GetOne(__cython_multidict_api, self, key, result) + +cdef inline int CIMultiDict_GetAll(object self, object key, PyObject ** result): + return C_CIMultiDict_GetAll(__cython_multidict_api, self, key, result) + +cdef inline int CIMultiDict_PopOne(object self, object key, PyObject ** result): + return C_CIMultiDict_PopOne(__cython_multidict_api, self, key, result) + +cdef inline int CIMultiDict_PopAll(object self, object key, PyObject ** result): + return C_CIMultiDict_PopAll(__cython_multidict_api, self, key, result) + +cdef inline tuple CIMultiDict_PopItem(object self): + return C_CIMultiDict_PopItem(__cython_multidict_api, self) + +cdef inline int CIMultiDict_Replace(object self, object key, object value): + return C_CIMultiDict_Replace(__cython_multidict_api, self, key, value) + +cdef inline int CIMultiDict_UpdateFromMultiDict(object self, object other, UpdateOp op): + return C_CIMultiDict_UpdateFromMultiDict(__cython_multidict_api, self, other, op) + +cdef inline int CIMultiDict_UpdateFromDict(object self, object other, UpdateOp op): + return C_CIMultiDict_UpdateFromDict(__cython_multidict_api, self, other, op) + +cdef inline int CIMultiDict_UpdateFromSequence(object self, object seq, UpdateOp op): + return C_CIMultiDict_UpdateFromSequence(__cython_multidict_api, self, seq, op) + +cdef inline CIMultiDictProxy CIMultiDictProxy_New(object md): + return C_CIMultiDictProxy_New(__cython_multidict_api, md) + +cdef inline int CIMultiDictProxy_CheckExact(object op): + return C_CIMultiDictProxy_CheckExact(__cython_multidict_api, op) + +cdef inline int CIMultiDictProxy_Check(object op): + return C_CIMultiDictProxy_Check(__cython_multidict_api, op) + +cdef inline int CIMultiDictProxy_Contains(object self, object key): + return C_CIMultiDictProxy_Contains(__cython_multidict_api, self, key) + +cdef inline int CIMultiDictProxy_GetAll(object self, object key, PyObject ** result): + return C_CIMultiDictProxy_GetAll(__cython_multidict_api, self, key, result) + +cdef inline int CIMultiDictProxy_GetOne(object self, object key, PyObject ** result): + return C_CIMultiDictProxy_GetOne(__cython_multidict_api, self, key, result) + +cdef inline type CIMultiDictProxy_GetType(): + return C_CIMultiDictProxy_GetType(__cython_multidict_api) + +cdef inline object MultiDictIter_New(object self): + return C_MultiDictIter_New(__cython_multidict_api, self) + +cdef inline int MultiDictIter_Next(object self, PyObject ** key, PyObject ** value): + return C_MultiDictIter_Next(__cython_multidict_api, self, key, value) diff --git a/multidict/_multilib/capsule.h b/multidict/_multilib/capsule.h index 16c29e136..a318b6de9 100644 --- a/multidict/_multilib/capsule.h +++ b/multidict/_multilib/capsule.h @@ -10,6 +10,7 @@ extern "C" { #include "../multidict_api.h" #include "dict.h" #include "hashtable.h" +#include "iter.h" #include "state.h" // NOTE: MACROS WITH '__' ARE INTERNAL METHODS, @@ -23,6 +24,45 @@ extern "C" { return ON_FAIL; \ } +#define __MULTIDICTPROXY_VALIDATION_CHECK(SELF, STATE, ON_FAIL) \ + if (MultiDictProxy_Check(((mod_state*)STATE), (SELF)) <= 0) { \ + PyErr_Format(PyExc_TypeError, \ + #SELF " should be a MultiDictProxy instance not %s", \ + Py_TYPE(SELF)->tp_name); \ + return ON_FAIL; \ + } + +#define __MULTIDICTPROXY_GET_MD(SELF) ((MultiDictProxyObject*)SELF)->md + +#define __ANYMULTIDICT_VALIDATION_CHECK(SELF, STATE, ON_FAIL) \ + if (AnyMultiDict_Check(((mod_state*)STATE), (SELF)) <= 0) { \ + PyErr_Format(PyExc_TypeError, \ + #SELF \ + " should be a CIMultiDict or MultiDict instance not %s", \ + Py_TYPE(SELF)->tp_name); \ + return ON_FAIL; \ + } + +#define __CIMULTIDICT_VALIDATION_CHECK(SELF, STATE, ON_FAIL) \ + if (CIMultiDict_Check(((mod_state*)STATE), (SELF)) <= 0) { \ + PyErr_Format(PyExc_TypeError, \ + #SELF " should be a CIMultiDict instance not %s", \ + Py_TYPE(SELF)->tp_name); \ + return ON_FAIL; \ + } + +#define __CIMULTIDICTPROXY_VALIDATION_CHECK(SELF, STATE, ON_FAIL) \ + if (CIMultiDictProxy_Check(((mod_state*)STATE), (SELF)) <= 0) { \ + PyErr_Format(PyExc_TypeError, \ + #SELF " should be a CIMultiDictProxy instance not %s", \ + Py_TYPE(SELF)->tp_name); \ + return ON_FAIL; \ + } + +#define __CIMULTIDICTPROXY_GET_MD(SELF) ((MultiDictProxyObject*)SELF)->md + +/* ================= MultiDict ================= */ + static PyTypeObject* MultiDict_GetType(void* state_) { @@ -44,7 +84,6 @@ MultiDict_New(void* state_, int prealloc_size) Py_CLEAR(md); return NULL; } - PyObject_GC_Track(md); return (PyObject*)md; } @@ -151,10 +190,22 @@ static int MultiDict_UpdateFromMultiDict(void* state_, PyObject* self, PyObject* other, UpdateOp op) { + mod_state* state = (mod_state*)state_; + MultiDictObject* md; __MULTIDICT_VALIDATION_CHECK(self, state_, -1); - __MULTIDICT_VALIDATION_CHECK(other, state_, -1); - int ret = - md_update_from_ht((MultiDictObject*)self, (MultiDictObject*)other, op); + if (MultiDict_Check(state, other) || CIMultiDict_Check(state, other)) { + md = (MultiDictObject*)other; + } else if (MultiDictProxy_Check(state, other) || + CIMultiDictProxy_Check(state, other)) { + md = __MULTIDICTPROXY_GET_MD(other); + } else { + PyErr_Format(PyExc_TypeError, + "Expected MultiDict, CIMultiDict, MultiDictProxy" + " or CIMultiDictProxy type object not %s", + Py_TYPE(other)->tp_name); + return -1; + } + int ret = md_update_from_ht((MultiDictObject*)self, md, op); if (op != Extend) { md_post_update((MultiDictObject*)self); } @@ -191,6 +242,431 @@ MultiDict_UpdateFromSequence(void* state_, PyObject* self, PyObject* seq, return ret; } +/* ================= MultiDictProxy ================= */ + +static PyObject* +MultiDictProxy_New(void* state_, PyObject* md) +{ + // This is meant to be a more optimized version of + // multidict_proxy_tp_init(...) + + mod_state* state = (mod_state*)state_; + PyObject* self = + state->MultiDictProxyType->tp_alloc(state->MultiDictProxyType, 0); + if (self == NULL) { + return NULL; + } + if (!AnyMultiDictProxy_Check(((mod_state*)state_), md) && + !AnyMultiDict_Check(state, md)) { + PyErr_Format(PyExc_TypeError, + "md requires MultiDict or MultiDictProxy instance, " + "not ", + Py_TYPE(md)->tp_name); + goto fail; + } + MultiDictObject* md_object; + if (AnyMultiDictProxy_Check(state, md)) { + md_object = ((MultiDictProxyObject*)md)->md; + } else { + md_object = (MultiDictObject*)md; + } + Py_INCREF(md_object); + ((MultiDictProxyObject*)self)->md = md_object; + return self; +fail: + Py_XDECREF(self); + return NULL; +} + +static int +MultiDictProxy_Contains(void* state_, PyObject* self, PyObject* key) +{ + __MULTIDICTPROXY_VALIDATION_CHECK(self, state_, -1); + return md_contains(__MULTIDICTPROXY_GET_MD(self), key, NULL); +} + +static int +MultiDictProxy_GetAll(void* state_, PyObject* self, PyObject* key, + PyObject** result) +{ + __MULTIDICTPROXY_VALIDATION_CHECK(self, state_, -1); + return md_get_all(__MULTIDICTPROXY_GET_MD(self), key, result); +} + +static int +MultiDictProxy_GetOne(void* state_, PyObject* self, PyObject* key, + PyObject** result) +{ + __MULTIDICTPROXY_VALIDATION_CHECK(self, state_, -1); + return md_get_one(__MULTIDICTPROXY_GET_MD(self), key, result); +} + +static PyTypeObject* +MultiDictProxy_GetType(void* state_) +{ + mod_state* state = (mod_state*)state_; + return (PyTypeObject*)Py_NewRef(state->MultiDictProxyType); +} + +/* ================= istr ================= */ + +static PyObject* +IStr_FromUnicode(void* state_, PyObject* str) +{ + if (!PyUnicode_Check(str)) { + PyErr_Format(PyExc_TypeError, + "str argument should be a str type object not \"%s\"", + Py_TYPE(str)->tp_name); + return NULL; + } + + mod_state* state = (mod_state*)state_; + PyObject* canonical = PyObject_CallMethodNoArgs(str, state->str_lower); + if (!canonical) { + return NULL; + }; + return IStr_New(state, str, canonical); +} + +// TODO: These comments should belong in the documentation portion as well... +// Anybody using a bytes/buffer object or another C datatype should be able to +// convert to istr without issue so making a IStr_FromStringAndSize function +// made sense. + +// However in cython if your using a straight up static string, use +// IStr_FromUnicode because the cython-compiler should immediately reconize and +// know what to do with it. + +static PyObject* +IStr_FromStringAndSize(void* state_, const char* str, Py_ssize_t size) +{ + // Better done sooner than sorry... + if (size == PY_SSIZE_T_MAX) { + PyErr_NoMemory(); + return NULL; + } + + PyUnicodeWriter* lc_writer = PyUnicodeWriter_Create(size); + if (!lc_writer) { + return NULL; + } + + PyObject* str_obj = PyUnicode_FromKindAndData( + PyUnicode_1BYTE_KIND, (unsigned char*)str, size); + if (!str_obj) { + goto fail; + } + + for (Py_ssize_t i = 0; i < size; i++) { + // This operation of lowercasing letters comes from llhttp + // (more specifially autogenerated from llparse) + // it is a slightly optimized version of C's tolower function + unsigned char c = (unsigned char)(str[i]); + if (PyUnicodeWriter_WriteChar( + lc_writer, (c) >= 'A' && (c) <= 'Z' ? (c | 0x20) : (c)) < 0) { + Py_XDECREF(str_obj); + goto fail; + } + } + PyObject* canonical = PyUnicodeWriter_Finish(lc_writer); + return IStr_New((mod_state*)state_, str_obj, canonical); + +fail: + PyUnicodeWriter_Discard(lc_writer); + return NULL; +} + +static PyObject* +IStr_FromString(void* state_, const char* str) +{ + return IStr_FromStringAndSize(state_, str, (Py_ssize_t)strlen(str)); +} + +static PyTypeObject* +IStr_GetType(void* state_) +{ + mod_state* state = (mod_state*)state_; + return (PyTypeObject*)Py_NewRef(state->IStrType); +} + +/* ================= CIMultiDict ================= */ + +static PyTypeObject* +CIMultiDict_GetType(void* state_) +{ + mod_state* state = (mod_state*)state_; + return (PyTypeObject*)Py_NewRef(state->CIMultiDictType); +} + +static PyObject* +CIMultiDict_New(void* state_, int prealloc_size) +{ + mod_state* state = (mod_state*)state_; + MultiDictObject* md = (MultiDictObject*)state->CIMultiDictType->tp_alloc( + state->CIMultiDictType, 0); + + if (md == NULL) { + return NULL; + } + if (md_init(md, state, true, prealloc_size) < 0) { + Py_CLEAR(md); + return NULL; + } + return (PyObject*)md; +} + +static int +CIMultiDict_Add(void* state_, PyObject* self, PyObject* key, PyObject* value) +{ + __CIMULTIDICT_VALIDATION_CHECK(self, state_, -1); + return md_add((MultiDictObject*)self, key, value); +} + +static int +CIMultiDict_Clear(void* state_, PyObject* self) +{ + // TODO: Macro for repeated steps being done? + __CIMULTIDICT_VALIDATION_CHECK(self, state_, -1); + return md_clear((MultiDictObject*)self); +} + +static int +CIMultiDict_SetDefault(void* state_, PyObject* self, PyObject* key, + PyObject* value, PyObject** result) +{ + __CIMULTIDICT_VALIDATION_CHECK(self, state_, -1); + return md_set_default((MultiDictObject*)self, key, value, result); +} + +static int +CIMultiDict_Del(void* state_, PyObject* self, PyObject* key) +{ + __CIMULTIDICT_VALIDATION_CHECK(self, state_, -1); + return md_del((MultiDictObject*)self, key); +} + +static uint64_t +CIMultiDict_Version(void* state_, PyObject* self) +{ + __CIMULTIDICT_VALIDATION_CHECK(self, state_, 0); + return md_version((MultiDictObject*)self); +} + +static int +CIMultiDict_Contains(void* state_, PyObject* self, PyObject* key) +{ + __CIMULTIDICT_VALIDATION_CHECK(self, state_, -1); + return md_contains((MultiDictObject*)self, key, NULL); +} + +static int +CIMultiDict_GetOne(void* state_, PyObject* self, PyObject* key, + PyObject** result) +{ + __CIMULTIDICT_VALIDATION_CHECK(self, state_, -1); + return md_get_one((MultiDictObject*)self, key, result); +} + +static int +CIMultiDict_GetAll(void* state_, PyObject* self, PyObject* key, + PyObject** result) +{ + __CIMULTIDICT_VALIDATION_CHECK(self, state_, -1); + return md_get_all((MultiDictObject*)self, key, result); +} + +static int +CIMultiDict_PopOne(void* state_, PyObject* self, PyObject* key, + PyObject** result) +{ + __CIMULTIDICT_VALIDATION_CHECK(self, state_, -1); + return md_pop_one((MultiDictObject*)self, key, result); +} + +static int +CIMultiDict_PopAll(void* state_, PyObject* self, PyObject* key, + PyObject** result) +{ + __CIMULTIDICT_VALIDATION_CHECK(self, state_, -1); + return md_pop_all((MultiDictObject*)self, key, result); +} + +static PyObject* +CIMultiDict_PopItem(void* state_, PyObject* self) +{ + __CIMULTIDICT_VALIDATION_CHECK(self, state_, NULL); + return md_pop_item((MultiDictObject*)self); +} + +static int +CIMultiDict_Replace(void* state_, PyObject* self, PyObject* key, + PyObject* value) +{ + __CIMULTIDICT_VALIDATION_CHECK(self, state_, -1); + return md_replace((MultiDictObject*)self, key, value); +} + +static int +CIMultiDict_UpdateFromMultiDict(void* state_, PyObject* self, PyObject* other, + UpdateOp op) +{ + mod_state* state = (mod_state*)state_; + MultiDictObject* md; + __CIMULTIDICT_VALIDATION_CHECK(self, state_, -1); + if (MultiDict_Check(state, other) || CIMultiDict_Check(state, other)) { + md = (MultiDictObject*)other; + } else if (MultiDictProxy_Check(state, other) || + CIMultiDictProxy_Check(state, other)) { + md = __MULTIDICTPROXY_GET_MD(other); + } else { + PyErr_Format(PyExc_TypeError, + "Expected MultiDict, CIMultiDict, MultiDictProxy" + " or CIMultiDictProxy type object not %s", + Py_TYPE(other)->tp_name); + return -1; + } + + int ret = md_update_from_ht((MultiDictObject*)self, md, op); + if (op != Extend) { + md_post_update((MultiDictObject*)self); + } + return ret; +} + +static int +CIMultiDict_UpdateFromDict(void* state_, PyObject* self, PyObject* other, + UpdateOp op) +{ + __CIMULTIDICT_VALIDATION_CHECK(self, state_, -1); + if (PyDict_CheckExact(other) <= 0) { + PyErr_Format(PyExc_TypeError, + "other should be a CIMultiDict instance not %s", + Py_TYPE(other)->tp_name); + return -1; + } + int ret = md_update_from_dict((MultiDictObject*)self, other, op); + if (op != Extend) { + md_post_update((MultiDictObject*)self); + } + return ret; +} + +static int +CIMultiDict_UpdateFromSequence(void* state_, PyObject* self, PyObject* seq, + UpdateOp op) +{ + __CIMULTIDICT_VALIDATION_CHECK(self, state_, -1); + int ret = md_update_from_seq((MultiDictObject*)self, seq, op); + if (op != Extend) { + md_post_update((MultiDictObject*)self); + } + return ret; +} + +/* ================= CIMultiDictProxy ================= */ + +static PyObject* +CIMultiDictProxy_New(void* state_, PyObject* md) +{ + mod_state* state = (mod_state*)state_; + PyObject* self = + state->CIMultiDictProxyType->tp_alloc(state->CIMultiDictProxyType, 0); + if (self == NULL) { + return NULL; + } + if (!CIMultiDictProxy_Check(((mod_state*)state_), md) && + !CIMultiDict_Check(state, md)) { + PyErr_Format(PyExc_TypeError, + "md requires CIMultiDict or CIMultiDictProxy instance, " + "not ", + Py_TYPE(md)->tp_name); + goto fail; + } + MultiDictObject* md_object; + if (CIMultiDictProxy_Check(state, md)) { + md_object = ((MultiDictProxyObject*)md)->md; + } else { + md_object = (MultiDictObject*)md; + } + Py_INCREF(md_object); + ((MultiDictProxyObject*)self)->md = md_object; + return self; +fail: + Py_XDECREF(self); + return NULL; +} + +static int +CIMultiDictProxy_Contains(void* state_, PyObject* self, PyObject* key) +{ + __CIMULTIDICTPROXY_VALIDATION_CHECK(self, state_, -1); + return md_contains(__CIMULTIDICTPROXY_GET_MD(self), key, NULL); +} + +static int +CIMultiDictProxy_GetAll(void* state_, PyObject* self, PyObject* key, + PyObject** result) +{ + __CIMULTIDICTPROXY_VALIDATION_CHECK(self, state_, -1); + return md_get_all(__CIMULTIDICTPROXY_GET_MD(self), key, result); +} + +static int +CIMultiDictProxy_GetOne(void* state_, PyObject* self, PyObject* key, + PyObject** result) +{ + __CIMULTIDICTPROXY_VALIDATION_CHECK(self, state_, -1); + return md_get_one(__CIMULTIDICTPROXY_GET_MD(self), key, result); +} + +static PyTypeObject* +CIMultiDictProxy_GetType(void* state_) +{ + mod_state* state = (mod_state*)state_; + return (PyTypeObject*)Py_NewRef(state->CIMultiDictProxyType); +} + +/* ================== MultiDictIter ================== */ + +// Creates a new iterator from MultiDict, CIMultiDict +// MutliDictProxy & CIMultiDictProxy +static PyObject* +MultiDictIter_New(void* state_, PyObject* self) +{ + mod_state* state = (mod_state*)state_; + MultiDictObject* md; + if (MultiDict_Check(state, self) || CIMultiDict_Check(state, self)) { + md = (MultiDictObject*)self; + } else if (MultiDictProxy_Check(state, self) || + CIMultiDictProxy_Check(state, self)) { + md = __MULTIDICTPROXY_GET_MD(self); + } else { + PyErr_Format(PyExc_TypeError, + "Expected MultiDict, CIMultiDict, MultiDictProxy" + " or CIMultiDictProxy type object not %s", + Py_TYPE(self)->tp_name); + return NULL; + } + return multidict_items_iter_new(md); +} + +static int +MultiDictIter_Next(void* state_, PyObject* self, PyObject** key, + PyObject** value) +{ + mod_state* state = (mod_state*)state_; + if (!Py_IS_TYPE(self, state->ItemsIterType)) { + PyErr_Format(PyExc_TypeError, + "Expected A MultiDict itemsiter type not %s", + Py_TYPE(self)->tp_name); + return -1; + } + MultidictIter* iter = (MultidictIter*)self; + return md_next(iter->md, &iter->current, NULL, key, value); +} + +/* =================== Capsule ==================== */ + static void capsule_free(MultiDict_CAPI* capi) { @@ -232,6 +708,44 @@ new_capsule(mod_state* state) capi->MultiDict_UpdateFromDict = MultiDict_UpdateFromDict; capi->MultiDict_UpdateFromSequence = MultiDict_UpdateFromSequence; + capi->MultiDictProxy_New = MultiDictProxy_New; + capi->MultiDictProxy_Contains = MultiDictProxy_Contains; + capi->MultiDictProxy_GetAll = MultiDictProxy_GetAll; + capi->MultiDictProxy_GetOne = MultiDictProxy_GetOne; + capi->MultiDictProxy_GetType = MultiDictProxy_GetType; + + capi->IStr_FromUnicode = IStr_FromUnicode; + capi->IStr_FromStringAndSize = IStr_FromStringAndSize; + capi->IStr_FromString = IStr_FromString; + capi->IStr_GetType = IStr_GetType; + + capi->CIMultiDict_GetType = CIMultiDict_GetType; + capi->CIMultiDict_New = CIMultiDict_New; + capi->CIMultiDict_Add = CIMultiDict_Add; + capi->CIMultiDict_Clear = CIMultiDict_Clear; + capi->CIMultiDict_SetDefault = CIMultiDict_SetDefault; + capi->CIMultiDict_Del = CIMultiDict_Del; + capi->CIMultiDict_Version = CIMultiDict_Version; + capi->CIMultiDict_Contains = CIMultiDict_Contains; + capi->CIMultiDict_GetOne = CIMultiDict_GetOne; + capi->CIMultiDict_GetAll = CIMultiDict_GetAll; + capi->CIMultiDict_PopOne = CIMultiDict_PopOne; + capi->CIMultiDict_PopAll = CIMultiDict_PopAll; + capi->CIMultiDict_PopItem = CIMultiDict_PopItem; + capi->CIMultiDict_Replace = CIMultiDict_Replace; + capi->CIMultiDict_UpdateFromMultiDict = CIMultiDict_UpdateFromMultiDict; + capi->CIMultiDict_UpdateFromDict = CIMultiDict_UpdateFromDict; + capi->CIMultiDict_UpdateFromSequence = CIMultiDict_UpdateFromSequence; + + capi->CIMultiDictProxy_New = CIMultiDictProxy_New; + capi->CIMultiDictProxy_Contains = CIMultiDictProxy_Contains; + capi->CIMultiDictProxy_GetAll = CIMultiDictProxy_GetAll; + capi->CIMultiDictProxy_GetOne = CIMultiDictProxy_GetOne; + capi->CIMultiDictProxy_GetType = CIMultiDictProxy_GetType; + + capi->MultiDictIter_New = MultiDictIter_New; + capi->MultiDictIter_Next = MultiDictIter_Next; + PyObject* ret = PyCapsule_New(capi, MultiDict_CAPSULE_NAME, capsule_destructor); if (ret == NULL) { diff --git a/multidict/_testcapi.c b/multidict/_testcapi.c new file mode 100644 index 000000000..daec30ba4 --- /dev/null +++ b/multidict/_testcapi.c @@ -0,0 +1,758 @@ +#include +#include + +#include "_multilib/pythoncapi_compat.h" +#include "multidict_api.h" + +/* In order to ensure it compiles originally this was going to be a seperate + * library the problem is hacking it in is on a completely different level and + * there is no Rational way to inject a sperate module to the workflow. and + * introduces a new can of worms when it needs to be tested with pytest you + * should not use or interact with this module normally + */ + +typedef struct { + MultiDict_CAPI *capi; +} mod_state; + +static inline mod_state * +get_mod_state(PyObject *mod) +{ + mod_state *state = (mod_state *)PyModule_GetState(mod); + assert(state != NULL); + return state; +} + +static inline MultiDict_CAPI * +get_capi(PyObject *mod) +{ + return get_mod_state(mod)->capi; +} + +static int +check_nargs(const char *name, Py_ssize_t nargs, Py_ssize_t required) +{ + if (nargs != required) { + PyErr_Format(PyExc_TypeError, + "%s should be called with %d arguments, got %d", + name, + required, + nargs); + return -1; + } + return 0; +} + +// Took the most repetative part and put it right here to help +// you can get rid of this comment before the pr is finished, +// Using a function was less confusing here than a macro - Vizonex + +static PyObject * +handle_result(int ret, PyObject *result) +{ + if (ret < 0) { + return NULL; + } + // Test if we missed + if (ret == 0) { + return PyTuple_Pack(2, Py_None, Py_False); + } + assert(result != NULL); + PyObject *val = PyBool_FromLong(ret); + if (val == NULL) { + Py_CLEAR(result); + return NULL; + } + return PyTuple_Pack(2, result, val); +} + +static PyObject * +handle_result_pair(int ret, PyObject *key, PyObject *value) +{ + if (ret < 0) { + return NULL; + } + // Test if we missed + if (ret == 0) { + return PyTuple_Pack(3, Py_None, Py_None, Py_False); + } + assert(key != NULL || value != NULL); + return PyTuple_Pack(3, key, value, Py_True); +} + +/* module functions */ + +static PyObject * +md_type(PyObject *self, PyObject *unused) +{ + return Py_NewRef(MultiDict_GetType(get_capi(self))); +} + +static PyObject * +md_new(PyObject *self, PyObject *arg) +{ + long prealloc_size = PyLong_AsLong(arg); + if (prealloc_size < 0) { + if (!PyErr_Occurred()) { + PyErr_SetString(PyExc_ValueError, + "Negative prealloc_size is not allowed"); + } + return NULL; + } + return MultiDict_New(get_capi(self), prealloc_size); +} + +static PyObject * +md_add(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("md_add", nargs, 3) < 0) { + return NULL; + } + if (MultiDict_Add(get_capi(self), args[0], args[1], args[2]) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * +md_clear(PyObject *self, PyObject *arg) +{ + if (MultiDict_Clear(get_capi(self), arg) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * +md_setdefault(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("md_setdefault", nargs, 3) < 0) { + return NULL; + } + PyObject *result = NULL; + int ret = MultiDict_SetDefault( + get_capi(self), args[0], args[1], args[2], &result); + return handle_result(ret, result); +} + +static PyObject * +md_del(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + // handle this check first so that there's an immediate exit + // rather than waiting for the state to be obtained + if (check_nargs("md_del", nargs, 2) < 0) { + return NULL; + } + if ((MutliDict_Del(get_capi(self), args[0], args[1])) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * +md_version(PyObject *self, PyObject *arg) +{ + return PyLong_FromUnsignedLongLong(MultiDict_Version(get_capi(self), arg)); +} + +static PyObject * +md_contains(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("md_contains", nargs, 2) < 0) { + return NULL; + } + int ret = MultiDict_Contains(get_capi(self), args[0], args[1]); + if (ret == -1) { + return NULL; + } + return PyBool_FromLong(ret); +} + +static PyObject * +md_getone(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("md_getone", nargs, 2) < 0) { + return NULL; + } + PyObject *result = NULL; + int ret = MultiDict_GetOne(get_capi(self), args[0], args[1], &result); + return handle_result(ret, result); +} + +static PyObject * +md_getall(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("md_getall", nargs, 2) < 0) { + return NULL; + } + PyObject *result = NULL; + int ret = MultiDict_GetAll(get_capi(self), args[0], args[1], &result); + return handle_result(ret, result); +} + +static PyObject * +md_popone(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("md_popone", nargs, 2) < 0) { + return NULL; + } + PyObject *result = NULL; + int ret = MultiDict_PopOne(get_capi(self), args[0], args[1], &result); + return handle_result(ret, result); +} + +static PyObject * +md_popall(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("md_popall", nargs, 2) < 0) { + return NULL; + } + PyObject *result = NULL; + int ret = MultiDict_PopAll(get_capi(self), args[0], args[1], &result); + return handle_result(ret, result); +} + +static PyObject * +md_popitem(PyObject *self, PyObject *arg) +{ + PyObject *REF = MultiDict_PopItem(get_capi(self), arg); + if (REF != NULL) { + Py_INCREF(REF); + } + return REF; +} + +static PyObject * +md_replace(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("md_replace", nargs, 3) < 0) { + return NULL; + } + + if (MultiDict_Replace(get_capi(self), args[0], args[1], args[2]) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * +md_update_from_md(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("md_update_from_md", nargs, 3) < 0) { + return NULL; + } + + if (MultiDict_UpdateFromMultiDict( + get_capi(self), args[0], args[1], PyLong_AsLong(args[2])) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * +md_update_from_dict(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("md_update_from_dict", nargs, 3) < 0) { + return NULL; + } + + if (MultiDict_UpdateFromDict( + get_capi(self), args[0], args[1], PyLong_AsLong(args[2])) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * +md_update_from_seq(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("md_update_from_seq", nargs, 3) < 0) { + return NULL; + } + if (MultiDict_UpdateFromSequence( + get_capi(self), args[0], args[1], PyLong_AsLong(args[2]))) { + return NULL; + }; + Py_RETURN_NONE; +} + +static PyObject * +md_proxy_new(PyObject *self, PyObject *arg) +{ + return MultiDictProxy_New(get_capi(self), arg); +} + +static PyObject * +md_proxy_contains(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("md_proxy_contains", nargs, 2) < 0) { + return NULL; + } + int ret = MultiDictProxy_Contains(get_capi(self), args[0], args[1]); + if (ret == -1) { + return NULL; + } + return PyBool_FromLong(ret); +} + +static PyObject * +md_proxy_getall(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("md_proxy_getall", nargs, 2) < 0) { + return NULL; + } + PyObject *result = NULL; + int ret = MultiDictProxy_GetAll(get_capi(self), args[0], args[1], &result); + return handle_result(ret, result); +} + +static PyObject * +md_proxy_getone(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("md_proxy_getone", nargs, 2) < 0) { + return NULL; + } + PyObject *result = NULL; + int ret = MultiDictProxy_GetOne(get_capi(self), args[0], args[1], &result); + return handle_result(ret, result); +} + +static PyObject * +md_proxy_type(PyObject *self, PyObject *unused) +{ + return Py_NewRef(MultiDictProxy_GetType(get_capi(self))); +} + +static PyObject * +istr_from_unicode(PyObject *self, PyObject *str) +{ + return IStr_FromUnicode(get_capi(self), str); +} + +static PyObject * +istr_from_string_and_size(PyObject *self, PyObject *const *args, + Py_ssize_t nargs) +{ + if (check_nargs("istr_from_string_and_size", nargs, 2) < 0) { + return NULL; + } + + Py_ssize_t size = PyLong_AsSsize_t(args[1]); + if (size < 0) { + return NULL; + } + + // we should be able to test this as a PyBuffer + Py_buffer view; + if (PyObject_GetBuffer(args[0], &view, PyBUF_SIMPLE) < 0) { + return NULL; + } + + PyObject *istr = + IStr_FromStringAndSize(get_capi(self), (const char *)view.buf, size); + PyBuffer_Release(&view); + return istr; +} + +static PyObject * +istr_from_string(PyObject *self, PyObject *buffer) +{ + Py_buffer view; + if (PyObject_GetBuffer(buffer, &view, PyBUF_SIMPLE) < 0) { + return NULL; + } + + PyObject *ret = IStr_FromString(get_capi(self), (const char *)view.buf); + PyBuffer_Release(&view); + return ret; +} + +static PyObject * +istr_get_type(PyObject *self, PyObject *unused) +{ + return Py_NewRef(IStr_GetType(get_capi(self))); +} + +static PyObject * +ci_md_type(PyObject *self, PyObject *unused) +{ + return Py_NewRef(CIMultiDict_GetType(get_capi(self))); +} + +static PyObject * +ci_md_new(PyObject *self, PyObject *arg) +{ + long prealloc_size = PyLong_AsLong(arg); + if (prealloc_size < 0) { + if (!PyErr_Occurred()) { + PyErr_SetString(PyExc_ValueError, + "Negative prealloc_size is not allowed"); + } + return NULL; + } + return CIMultiDict_New(get_capi(self), prealloc_size); +} + +static PyObject * +ci_md_add(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("ci_md_add", nargs, 3) < 0) { + return NULL; + } + if (CIMultiDict_Add(get_capi(self), args[0], args[1], args[2]) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * +ci_md_clear(PyObject *self, PyObject *arg) +{ + if (CIMultiDict_Clear(get_capi(self), arg) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * +ci_md_setdefault(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("ci_md_setdefault", nargs, 3) < 0) { + return NULL; + } + PyObject *result = NULL; + int ret = CIMultiDict_SetDefault( + get_capi(self), args[0], args[1], args[2], &result); + return handle_result(ret, result); +} + +static PyObject * +ci_md_del(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + // handle this check first so that there's an immediate exit + // rather than waiting for the state to be obtained + if (check_nargs("ci_md_del", nargs, 2) < 0) { + return NULL; + } + if ((MutliDict_Del(get_capi(self), args[0], args[1])) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * +ci_md_version(PyObject *self, PyObject *arg) +{ + return PyLong_FromUnsignedLongLong( + CIMultiDict_Version(get_capi(self), arg)); +} + +static PyObject * +ci_md_contains(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("ci_md_contains", nargs, 2) < 0) { + return NULL; + } + int ret = CIMultiDict_Contains(get_capi(self), args[0], args[1]); + if (ret == -1) { + return NULL; + } + return PyBool_FromLong(ret); +} + +static PyObject * +ci_md_getone(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("ci_md_getone", nargs, 2) < 0) { + return NULL; + } + PyObject *result = NULL; + int ret = CIMultiDict_GetOne(get_capi(self), args[0], args[1], &result); + return handle_result(ret, result); +} + +static PyObject * +ci_md_getall(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("ci_md_getall", nargs, 2) < 0) { + return NULL; + } + PyObject *result = NULL; + int ret = CIMultiDict_GetAll(get_capi(self), args[0], args[1], &result); + return handle_result(ret, result); +} + +static PyObject * +ci_md_popone(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("ci_md_popone", nargs, 2) < 0) { + return NULL; + } + PyObject *result = NULL; + int ret = CIMultiDict_PopOne(get_capi(self), args[0], args[1], &result); + return handle_result(ret, result); +} + +static PyObject * +ci_md_popall(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("ci_md_popall", nargs, 2) < 0) { + return NULL; + } + PyObject *result = NULL; + int ret = CIMultiDict_PopAll(get_capi(self), args[0], args[1], &result); + return handle_result(ret, result); +} + +static PyObject * +ci_md_popitem(PyObject *self, PyObject *arg) +{ + PyObject *REF = CIMultiDict_PopItem(get_capi(self), arg); + if (REF != NULL) { + Py_INCREF(REF); + } + return REF; +} + +static PyObject * +ci_md_replace(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("ci_md_replace", nargs, 3) < 0) { + return NULL; + } + + if (CIMultiDict_Replace(get_capi(self), args[0], args[1], args[2]) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * +ci_md_update_from_md(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("ci_md_update_from_md", nargs, 3) < 0) { + return NULL; + } + + if (CIMultiDict_UpdateFromMultiDict( + get_capi(self), args[0], args[1], PyLong_AsLong(args[2])) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * +ci_md_update_from_dict(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("ci_md_update_from_dict", nargs, 3) < 0) { + return NULL; + } + + if (CIMultiDict_UpdateFromDict( + get_capi(self), args[0], args[1], PyLong_AsLong(args[2])) < 0) { + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * +ci_md_update_from_seq(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("ci_md_update_from_seq", nargs, 3) < 0) { + return NULL; + } + if (CIMultiDict_UpdateFromSequence( + get_capi(self), args[0], args[1], PyLong_AsLong(args[2]))) { + return NULL; + }; + Py_RETURN_NONE; +} + +static PyObject * +ci_md_proxy_new(PyObject *self, PyObject *arg) +{ + return CIMultiDictProxy_New(get_capi(self), arg); +} + +static PyObject * +ci_md_proxy_contains(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("ci_md_proxy_contains", nargs, 2) < 0) { + return NULL; + } + int ret = CIMultiDictProxy_Contains(get_capi(self), args[0], args[1]); + if (ret == -1) { + return NULL; + } + return PyBool_FromLong(ret); +} + +static PyObject * +ci_md_proxy_getall(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("ci_md_proxy_getall", nargs, 2) < 0) { + return NULL; + } + PyObject *result = NULL; + int ret = + CIMultiDictProxy_GetAll(get_capi(self), args[0], args[1], &result); + return handle_result(ret, result); +} + +static PyObject * +ci_md_proxy_getone(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (check_nargs("ci_md_proxy_getone", nargs, 2) < 0) { + return NULL; + } + PyObject *result = NULL; + int ret = + CIMultiDictProxy_GetOne(get_capi(self), args[0], args[1], &result); + return handle_result(ret, result); +} + +static PyObject * +ci_md_proxy_type(PyObject *self, PyObject *unused) +{ + return Py_NewRef(CIMultiDictProxy_GetType(get_capi(self))); +} + +/* MultiDictIter */ + +static PyObject * +md_iter_new(PyObject *self, PyObject *any_md) +{ + return MultiDictIter_New(get_capi(self), any_md); +} + +static PyObject * +md_iter_next(PyObject *self, PyObject *md_iter) +{ + PyObject *key = NULL, *value = NULL; + int ret = MultiDictIter_Next(get_capi(self), md_iter, &key, &value); + return handle_result_pair(ret, key, value); +} + +/* module slots */ + +static int +module_traverse(PyObject *mod, visitproc visit, void *arg) +{ + return 0; +} + +static int +module_clear(PyObject *mod) +{ + return 0; +} + +static void +module_free(void *mod) +{ + (void)module_clear((PyObject *)mod); +} + +static PyMethodDef module_methods[] = { + {"md_type", (PyCFunction)md_type, METH_NOARGS}, + {"md_new", (PyCFunction)md_new, METH_O}, + {"md_add", (PyCFunction)md_add, METH_FASTCALL}, + {"md_clear", (PyCFunction)md_clear, METH_O}, + {"md_setdefault", (PyCFunction)md_setdefault, METH_FASTCALL}, + {"md_del", (PyCFunction)md_del, METH_FASTCALL}, + {"md_version", (PyCFunction)md_version, METH_O}, + {"md_contains", (PyCFunction)md_contains, METH_FASTCALL}, + {"md_getone", (PyCFunction)md_getone, METH_FASTCALL}, + {"md_getall", (PyCFunction)md_getall, METH_FASTCALL}, + {"md_popone", (PyCFunction)md_popone, METH_FASTCALL}, + {"md_popall", (PyCFunction)md_popall, METH_FASTCALL}, + {"md_popitem", (PyCFunction)md_popitem, METH_O}, + {"md_replace", (PyCFunction)md_replace, METH_FASTCALL}, + {"md_update_from_md", (PyCFunction)md_update_from_md, METH_FASTCALL}, + {"md_update_from_dict", (PyCFunction)md_update_from_dict, METH_FASTCALL}, + {"md_update_from_seq", (PyCFunction)md_update_from_seq, METH_FASTCALL}, + + {"md_proxy_new", (PyCFunction)md_proxy_new, METH_O}, + {"md_proxy_type", (PyCFunction)md_proxy_type, METH_NOARGS}, + {"md_proxy_contains", (PyCFunction)md_proxy_contains, METH_FASTCALL}, + {"md_proxy_getall", (PyCFunction)md_proxy_getall, METH_FASTCALL}, + {"md_proxy_getone", (PyCFunction)md_proxy_getone, METH_FASTCALL}, + + {"istr_from_unicode", (PyCFunction)istr_from_unicode, METH_O}, + {"istr_from_string", (PyCFunction)istr_from_string, METH_O}, + {"istr_from_string_and_size", + (PyCFunction)istr_from_string_and_size, + METH_FASTCALL}, + {"istr_get_type", (PyCFunction)istr_get_type, METH_NOARGS}, + + {"ci_md_type", (PyCFunction)ci_md_type, METH_NOARGS}, + {"ci_md_new", (PyCFunction)ci_md_new, METH_O}, + {"ci_md_add", (PyCFunction)ci_md_add, METH_FASTCALL}, + {"ci_md_clear", (PyCFunction)ci_md_clear, METH_O}, + {"ci_md_setdefault", (PyCFunction)ci_md_setdefault, METH_FASTCALL}, + {"ci_md_del", (PyCFunction)ci_md_del, METH_FASTCALL}, + {"ci_md_version", (PyCFunction)ci_md_version, METH_O}, + {"ci_md_contains", (PyCFunction)ci_md_contains, METH_FASTCALL}, + {"ci_md_getone", (PyCFunction)ci_md_getone, METH_FASTCALL}, + {"ci_md_getall", (PyCFunction)ci_md_getall, METH_FASTCALL}, + {"ci_md_popone", (PyCFunction)ci_md_popone, METH_FASTCALL}, + {"ci_md_popall", (PyCFunction)ci_md_popall, METH_FASTCALL}, + {"ci_md_popitem", (PyCFunction)ci_md_popitem, METH_O}, + {"ci_md_replace", (PyCFunction)ci_md_replace, METH_FASTCALL}, + {"ci_md_update_from_md", (PyCFunction)ci_md_update_from_md, METH_FASTCALL}, + {"ci_md_update_from_dict", + (PyCFunction)ci_md_update_from_dict, + METH_FASTCALL}, + {"ci_md_update_from_seq", + (PyCFunction)ci_md_update_from_seq, + METH_FASTCALL}, + + {"ci_md_proxy_new", (PyCFunction)ci_md_proxy_new, METH_O}, + {"ci_md_proxy_type", (PyCFunction)ci_md_proxy_type, METH_NOARGS}, + {"ci_md_proxy_contains", (PyCFunction)ci_md_proxy_contains, METH_FASTCALL}, + {"ci_md_proxy_getall", (PyCFunction)ci_md_proxy_getall, METH_FASTCALL}, + {"ci_md_proxy_getone", (PyCFunction)ci_md_proxy_getone, METH_FASTCALL}, + + {"md_iter_new", (PyCFunction)md_iter_new, METH_O}, + {"md_iter_next", (PyCFunction)md_iter_next, METH_O}, + {NULL, NULL} /* sentinel */ +}; + +static int +module_exec(PyObject *mod) +{ + mod_state *state = get_mod_state(mod); + state->capi = MultiDict_Import(); + if (state->capi == NULL) { + return -1; + } + return 0; +} + +static struct PyModuleDef_Slot module_slots[] = { + {Py_mod_exec, module_exec}, +#if PY_VERSION_HEX >= 0x030c00f0 + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, +#endif +#if PY_VERSION_HEX >= 0x030d00f0 + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, +#endif + {0, NULL}, +}; + +static PyModuleDef api_module = { + .m_base = PyModuleDef_HEAD_INIT, + .m_name = "_testcapi", + .m_size = sizeof(mod_state), + .m_methods = module_methods, + .m_slots = module_slots, + .m_traverse = module_traverse, + .m_clear = module_clear, + .m_free = (freefunc)module_free, +}; + +PyMODINIT_FUNC +PyInit__testcapi(void) +{ + return PyModuleDef_Init(&api_module); +} diff --git a/multidict/multidict_api.h b/multidict/multidict_api.h index 88a707b6c..a5ef8d4d1 100644 --- a/multidict/multidict_api.h +++ b/multidict/multidict_api.h @@ -37,6 +37,7 @@ typedef struct { PyObject* (*MultiDict_New)(void* state, int prealloc_size); int (*MultiDict_Add)(void* state, PyObject* self, PyObject* key, PyObject* value); + int (*MultiDict_Clear)(void* state, PyObject* self); int (*MultiDict_SetDefault)(void* state, PyObject* self, PyObject* key, @@ -46,13 +47,13 @@ typedef struct { uint64_t (*MultiDict_Version)(void* state, PyObject* self); int (*MultiDict_Contains)(void* state, PyObject* self, PyObject* key); - int (*MultiDict_GetOne)(void* state_, PyObject* self, PyObject* key, + int (*MultiDict_GetOne)(void* state, PyObject* self, PyObject* key, PyObject** result); - int (*MultiDict_GetAll)(void* state_, PyObject* self, PyObject* key, + int (*MultiDict_GetAll)(void* state, PyObject* self, PyObject* key, PyObject** result); - int (*MultiDict_PopOne)(void* state_, PyObject* self, PyObject* key, + int (*MultiDict_PopOne)(void* state, PyObject* self, PyObject* key, PyObject** result); - int (*MultiDict_PopAll)(void* state_, PyObject* self, PyObject* key, + int (*MultiDict_PopAll)(void* state, PyObject* self, PyObject* key, PyObject** result); PyObject* (*MultiDict_PopItem)(void* state, PyObject* self); int (*MultiDict_Replace)(void* state, PyObject* self, PyObject* key, @@ -64,31 +65,81 @@ typedef struct { int (*MultiDict_UpdateFromSequence)(void* state, PyObject* self, PyObject* kwds, UpdateOp op); + PyObject* (*MultiDictProxy_New)(void* state, PyObject* md); + int (*MultiDictProxy_Contains)(void* state, PyObject* self, PyObject* key); + int (*MultiDictProxy_GetAll)(void* state, PyObject* self, PyObject* key, + PyObject** result); + int (*MultiDictProxy_GetOne)(void* state, PyObject* self, PyObject* key, + PyObject** result); + PyTypeObject* (*MultiDictProxy_GetType)(void* state); + + PyObject* (*IStr_FromUnicode)(void* state, PyObject* str); + PyObject* (*IStr_FromStringAndSize)(void* state, const char* str, + Py_ssize_t size); + PyObject* (*IStr_FromString)(void* state, const char* str); + PyTypeObject* (*IStr_GetType)(void* state); + + PyTypeObject* (*CIMultiDict_GetType)(void* state); + + PyObject* (*CIMultiDict_New)(void* state, int prealloc_size); + int (*CIMultiDict_Add)(void* state, PyObject* self, PyObject* key, + PyObject* value); + int (*CIMultiDict_Clear)(void* state, PyObject* self); + + int (*CIMultiDict_SetDefault)(void* state, PyObject* self, PyObject* key, + PyObject* default_, PyObject** result); + + int (*CIMultiDict_Del)(void* state, PyObject* self, PyObject* key); + uint64_t (*CIMultiDict_Version)(void* state, PyObject* self); + + int (*CIMultiDict_Contains)(void* state, PyObject* self, PyObject* key); + int (*CIMultiDict_GetOne)(void* state, PyObject* self, PyObject* key, + PyObject** result); + int (*CIMultiDict_GetAll)(void* state, PyObject* self, PyObject* key, + PyObject** result); + int (*CIMultiDict_PopOne)(void* state, PyObject* self, PyObject* key, + PyObject** result); + int (*CIMultiDict_PopAll)(void* state, PyObject* self, PyObject* key, + PyObject** result); + PyObject* (*CIMultiDict_PopItem)(void* state, PyObject* self); + int (*CIMultiDict_Replace)(void* state, PyObject* self, PyObject* key, + PyObject* value); + int (*CIMultiDict_UpdateFromMultiDict)(void* state, PyObject* self, + PyObject* other, UpdateOp op); + int (*CIMultiDict_UpdateFromDict)(void* state, PyObject* self, + PyObject* kwds, UpdateOp op); + int (*CIMultiDict_UpdateFromSequence)(void* state, PyObject* self, + PyObject* kwds, UpdateOp op); + + PyObject* (*CIMultiDictProxy_New)(void* state, PyObject* md); + int (*CIMultiDictProxy_Contains)(void* state, PyObject* self, + PyObject* key); + int (*CIMultiDictProxy_GetAll)(void* state, PyObject* self, PyObject* key, + PyObject** result); + int (*CIMultiDictProxy_GetOne)(void* state, PyObject* self, PyObject* key, + PyObject** result); + PyTypeObject* (*CIMultiDictProxy_GetType)(void* state); + + PyObject* (*MultiDictIter_New)(void* state_, PyObject* self); + int (*MultiDictIter_Next)(void* state_, PyObject* self, PyObject** key, + PyObject** value); + } MultiDict_CAPI; #ifndef MULTIDICT_IMPL -/// @brief Imports Multidict CAPI -/// @return A Capsule Containing the Multidict CAPI Otherwise NULL static inline MultiDict_CAPI* MultiDict_Import() { return (MultiDict_CAPI*)PyCapsule_Import(MultiDict_CAPSULE_NAME, 0); } -/// @brief Obtains the Multidict TypeObject -/// @param api Python Capsule Pointer to the API -/// @return A CPython `PyTypeObject` is returned as a pointer, -/// `NULL` on failure static inline PyTypeObject* MultiDict_GetType(MultiDict_CAPI* api) { return api->MultiDict_GetType(api->state); } -/// @brief Checks if Multidict Object Type Matches Exactly -/// @param api Python Capsule Pointer to the API -/// @param op The Object to check -/// @return 1 if `true`, 0 if `false` + static inline int MultiDict_CheckExact(MultiDict_CAPI* api, PyObject* op) { @@ -98,10 +149,6 @@ MultiDict_CheckExact(MultiDict_CAPI* api, PyObject* op) return ret; } -/// @brief Checks if Multidict Object Type Matches or is a subclass of itself -/// @param api Python Capsule Pointer to the API -/// @param op The Object to check -/// @return 1 if `true`, 0 if `false` static inline int MultiDict_Check(MultiDict_CAPI* api, PyObject* op) { @@ -111,23 +158,12 @@ MultiDict_Check(MultiDict_CAPI* api, PyObject* op) return ret; } -/// @brief Creates a New Multidict Type Object with a number entries wanted -/// preallocated -/// @param api Python Capsule Pointer to the API -/// @param prealloc_size The Number of entires to preallocate for -/// @return `MultiDict` object if successful, otherwise `NULL` static inline PyObject* MultiDict_New(MultiDict_CAPI* api, int prealloc_size) { return api->MultiDict_New(api->state, prealloc_size); } -/// @brief Adds a new entry to the `multidict` object -/// @param api Python Capsule Pointer to the API -/// @param self the Multidict object -/// @param key The key of the entry to add -/// @param value The value of the entry to add -/// @return 0 on success, -1 on failure static inline int MultiDict_Add(MultiDict_CAPI* api, PyObject* self, PyObject* key, PyObject* value) @@ -135,25 +171,12 @@ MultiDict_Add(MultiDict_CAPI* api, PyObject* self, PyObject* key, return api->MultiDict_Add(api->state, self, key, value); } -/// @brief Clears a multidict object and removes all it's entries -/// @param api Python Capsule Pointer to the API -/// @param self the multidict object -/// @return 0 if success otherwise -1 , will raise TypeError if MultiDict's -/// Type is incorrect static inline int MultiDict_Clear(MultiDict_CAPI* api, PyObject* self) { return api->MultiDict_Clear(api->state, self); } -/// XXX: Documentation is incorrect I will need to edit in a bit - Vizonex -/// @brief If key is in the dictionary its the first value. -/// If not, insert key with a value of default and return default. -/// @param api Python Capsule Pointer -/// @param self the MultiDict object -/// @param key the key to insert -/// @param _default the default value to have inserted -/// @return default on success, NULL on failure static inline int MultiDict_SetDefault(MultiDict_CAPI* api, PyObject* self, PyObject* key, PyObject* default_, PyObject** result) @@ -161,63 +184,24 @@ MultiDict_SetDefault(MultiDict_CAPI* api, PyObject* self, PyObject* key, return api->MultiDict_SetDefault(api->state, self, key, default_, result); } -/// @brief Remove all items where key is equal to key from d. -/// @param api Python Capsule Pointer -/// @param self the MultiDict -/// @param key the key to be removed -/// @return 0 on success, -1 on failure followed by rasing either -/// `TypeError` or `KeyError` if key is not in the map. static inline int MutliDict_Del(MultiDict_CAPI* api, PyObject* self, PyObject* key) { return api->MultiDict_Del(api->state, self, key); } -/// @brief Return a version of given mdict object -/// @param api Python Capsule Pointer -/// @param self the mdict object -/// @return the version flag of the object, otherwise 0 on failure static uint64_t MultiDict_Version(MultiDict_CAPI* api, PyObject* self) { return api->MultiDict_Version(api->state, self); } -// Under debate as concept - -// /// @brief Creates a new positional marker for a multidict to iterate -// /// with when being utlizied with `MultiDict_Next` -// /// @param api Python Capsule Pointer -// /// @param self the multidict to create a positional marker for -// /// @param pos the positional marker to be created -// /// @return 0 on success, -1 on failure along with `TypeError` exception -// being thrown static inline int MultiDict_CreatePosMarker(MultiDict_CAPI* -// api, PyObject* self, md_pos_t* pos){ -// return api->MultiDict_CreatePosMarker(api->state, self, pos); -// } -// static inline int MultiDict_Next(MultiDict_CAPI* api, PyObject* self, -// md_pos_t* pos, PyObject** identity, PyObject**key, PyObject **value){ -// return api->MultiDict_Next(api->state, self, pos, identity, key, value); -// }; - -/// @brief Determines if a certain key exists a multidict object -/// @param api Python Capsule Pointer -/// @param self the multidict object -/// @param key the key to look for -/// @return 1 if true, 0 if false, -1 if failure had occured static inline int MultiDict_Contains(MultiDict_CAPI* api, PyObject* self, PyObject* key) { return api->MultiDict_Contains(api->state, self, key); } -/// @brief Return the **first** value for *key* if *key* is in the -/// dictionary, else *default*. -/// @param api Python Capsule Pointer -/// @param self the multidict object -/// @param key the key to get one item from -/// @return returns a default value on success, -1 with `KeyError` or -/// `TypeError` on failure static inline int MultiDict_GetOne(MultiDict_CAPI* api, PyObject* self, PyObject* key, PyObject** result) @@ -225,13 +209,6 @@ MultiDict_GetOne(MultiDict_CAPI* api, PyObject* self, PyObject* key, return api->MultiDict_GetOne(api->state, self, key, result); } -/// @brief Return a list of all values for *key* if *key* is in the -/// dictionary, else *default*. -/// @param api Python Capsule Pointer -/// @param self the multidict obeject -/// @param key the key to obtain all the items from -/// @return a list of all the values, otherwise NULL on error -/// raises either `KeyError` or `TypeError` static inline int MultiDict_GetAll(MultiDict_CAPI* api, PyObject* self, PyObject* key, PyObject** result) @@ -239,13 +216,6 @@ MultiDict_GetAll(MultiDict_CAPI* api, PyObject* self, PyObject* key, return api->MultiDict_GetAll(api->state, self, key, result); } -/// @brief If `key` is in the dictionary, remove it and return its the -/// `first` value, else return `default`. -/// @param api Python Capsule Pointer -/// @param self the multidict object -/// @param key the key to pop -/// @return object on success, otherwise NULL on error along -/// with `KeyError` or `TypeError` being raised static inline int MultiDict_PopOne(MultiDict_CAPI* api, PyObject* self, PyObject* key, PyObject** result) @@ -253,12 +223,6 @@ MultiDict_PopOne(MultiDict_CAPI* api, PyObject* self, PyObject* key, return api->MultiDict_PopOne(api->state, self, key, result); } -/// @brief Pops all related objects corresponding to `key` -/// @param api Python Capsule Pointer -/// @param self the multidict object -/// @param key the key to pop all of -/// @return list object on success, otherwise NULL, on error and raises either -/// `KeyError` or `TyperError` static inline int MultiDict_PopAll(MultiDict_CAPI* api, PyObject* self, PyObject* key, PyObject** result) @@ -266,24 +230,12 @@ MultiDict_PopAll(MultiDict_CAPI* api, PyObject* self, PyObject* key, return api->MultiDict_PopAll(api->state, self, key, result); } -/// @brief Remove and return an arbitrary `(key, value)` pair from the -/// dictionary. -/// @param api Python Capsule Pointer -/// @param self the multidict object -/// @return an arbitray tuple on success, otherwise NULL on error along -/// with `TypeError` or `KeyError` raised static inline PyObject* MultiDict_PopItem(MultiDict_CAPI* api, PyObject* self) { return api->MultiDict_PopItem(api->state, self); } -/// @brief Replaces a set object with another object -/// @param api Python Capsule Pointer -/// @param self the multidict object -/// @param key the key to lookup for replacement -/// @param value the value to replace with -/// @return 0 on sucess, -1 on Failure and raises TypeError static inline int MultiDict_Replace(MultiDict_CAPI* api, PyObject* self, PyObject* key, PyObject* value) @@ -291,13 +243,6 @@ MultiDict_Replace(MultiDict_CAPI* api, PyObject* self, PyObject* key, return api->MultiDict_Replace(api->state, self, key, value); }; -/// @brief Updates Multidict object using another MultiDict Object -/// @param api Python Capsule Pointer -/// @param self the multidict object -/// @param other a multidict object to update corresponding object with -/// @param update if true append references and stack them, otherwise steal all -/// references. -/// @return 0 on sucess, -1 on failure static inline int MultiDict_UpdateFromMultiDict(MultiDict_CAPI* api, PyObject* self, PyObject* other, UpdateOp op) @@ -305,13 +250,6 @@ MultiDict_UpdateFromMultiDict(MultiDict_CAPI* api, PyObject* self, return api->MultiDict_UpdateFromMultiDict(api->state, self, other, op); }; -/// @brief Updates Multidict object using another Dictionary Object -/// @param api Python Capsule Pointer -/// @param self the multidict object -/// @param kwds the keywords or Dictionary object to merge -/// @param update if true append references and stack them, otherwise steal all -/// references. -/// @return 0 on sucess, -1 on failure static inline int MultiDict_UpdateFromDict(MultiDict_CAPI* api, PyObject* self, PyObject* other, UpdateOp op) @@ -319,13 +257,6 @@ MultiDict_UpdateFromDict(MultiDict_CAPI* api, PyObject* self, PyObject* other, return api->MultiDict_UpdateFromDict(api->state, self, other, op); }; -/// @brief Updates Multidict object using a sequence object -/// @param api Python Capsule Pointer -/// @param self the multidict object -/// @param seq the sequence to merge with. -/// @param update if true append references and stack them, otherwise steal all -/// references. -/// @return 0 on sucess, -1 on failure static inline int MultiDict_UpdateFromSequence(MultiDict_CAPI* api, PyObject* self, PyObject* seq, UpdateOp op) @@ -333,6 +264,292 @@ MultiDict_UpdateFromSequence(MultiDict_CAPI* api, PyObject* self, return api->MultiDict_UpdateFromSequence(api->state, self, seq, op); }; +static inline PyObject* +MultiDictProxy_New(MultiDict_CAPI* api, PyObject* md) +{ + return api->MultiDictProxy_New(api->state, md); +} + +static inline int +MultiDictProxy_CheckExact(MultiDict_CAPI* api, PyObject* op) +{ + PyTypeObject* type = api->MultiDict_GetType(api->state); + int ret = Py_IS_TYPE(op, type); + Py_DECREF(type); + return ret; +} + +static inline int +MultiDictProxy_Check(MultiDict_CAPI* api, PyObject* op) +{ + PyTypeObject* type = api->MultiDictProxy_GetType(api->state); + int ret = Py_IS_TYPE(op, type) || PyObject_TypeCheck(op, type); + Py_DECREF(type); + return ret; +} + +static inline int +MultiDictProxy_Contains(MultiDict_CAPI* api, PyObject* self, PyObject* key) +{ + return api->MultiDictProxy_Contains(api->state, self, key); +} + +static inline int +MultiDictProxy_GetAll(MultiDict_CAPI* api, PyObject* self, PyObject* key, + PyObject** result) +{ + return api->MultiDictProxy_GetAll(api->state, self, key, result); +} + +static inline int +MultiDictProxy_GetOne(MultiDict_CAPI* api, PyObject* self, PyObject* key, + PyObject** result) +{ + return api->MultiDictProxy_GetOne(api->state, self, key, result); +} + +static inline PyTypeObject* +MultiDictProxy_GetType(MultiDict_CAPI* api) +{ + return api->MultiDictProxy_GetType(api->state); +} + +static inline int +IStr_CheckExact(MultiDict_CAPI* api, PyObject* op) +{ + PyTypeObject* type = api->IStr_GetType(api->state); + int ret = Py_IS_TYPE(op, type); + Py_DECREF(type); + return ret; +} + +static inline int +IStr_Check(MultiDict_CAPI* api, PyObject* op) +{ + PyTypeObject* type = api->IStr_GetType(api->state); + int ret = Py_IS_TYPE(op, type) || PyObject_TypeCheck(op, type); + Py_DECREF(type); + return ret; +} + +static PyObject* +IStr_FromUnicode(MultiDict_CAPI* api, PyObject* str) +{ + return api->IStr_FromUnicode(api->state, str); +} + +static PyObject* +IStr_FromStringAndSize(MultiDict_CAPI* api, const char* str, Py_ssize_t size) +{ + return api->IStr_FromStringAndSize(api->state, str, size); +} + +static PyObject* +IStr_FromString(MultiDict_CAPI* api, const char* str) +{ + return api->IStr_FromString(api->state, str); +} + +static inline PyTypeObject* +IStr_GetType(MultiDict_CAPI* api) +{ + return api->IStr_GetType(api->state); +} + +static inline PyTypeObject* +CIMultiDict_GetType(MultiDict_CAPI* api) +{ + return api->CIMultiDict_GetType(api->state); +} + +static inline int +CIMultiDict_CheckExact(MultiDict_CAPI* api, PyObject* op) +{ + PyTypeObject* type = api->MultiDict_GetType(api->state); + int ret = Py_IS_TYPE(op, type); + Py_DECREF(type); + return ret; +} + +static inline int +CIMultiDict_Check(MultiDict_CAPI* api, PyObject* op) +{ + PyTypeObject* type = api->MultiDict_GetType(api->state); + int ret = Py_IS_TYPE(op, type) || PyObject_TypeCheck(op, type); + Py_DECREF(type); + return ret; +} + +static inline PyObject* +CIMultiDict_New(MultiDict_CAPI* api, int prealloc_size) +{ + return api->CIMultiDict_New(api->state, prealloc_size); +} + +static inline int +CIMultiDict_Add(MultiDict_CAPI* api, PyObject* self, PyObject* key, + PyObject* value) +{ + return api->CIMultiDict_Add(api->state, self, key, value); +} + +static inline int +CIMultiDict_Clear(MultiDict_CAPI* api, PyObject* self) +{ + return api->CIMultiDict_Clear(api->state, self); +} + +static inline int +CIMultiDict_SetDefault(MultiDict_CAPI* api, PyObject* self, PyObject* key, + PyObject* default_, PyObject** result) +{ + return api->CIMultiDict_SetDefault( + api->state, self, key, default_, result); +} + +static inline int +CIMutliDict_Del(MultiDict_CAPI* api, PyObject* self, PyObject* key) +{ + return api->CIMultiDict_Del(api->state, self, key); +} + +static uint64_t +CIMultiDict_Version(MultiDict_CAPI* api, PyObject* self) +{ + return api->CIMultiDict_Version(api->state, self); +} + +static inline int +CIMultiDict_Contains(MultiDict_CAPI* api, PyObject* self, PyObject* key) +{ + return api->CIMultiDict_Contains(api->state, self, key); +} + +static inline int +CIMultiDict_GetOne(MultiDict_CAPI* api, PyObject* self, PyObject* key, + PyObject** result) +{ + return api->CIMultiDict_GetOne(api->state, self, key, result); +} + +static inline int +CIMultiDict_GetAll(MultiDict_CAPI* api, PyObject* self, PyObject* key, + PyObject** result) +{ + return api->CIMultiDict_GetAll(api->state, self, key, result); +} + +static inline int +CIMultiDict_PopOne(MultiDict_CAPI* api, PyObject* self, PyObject* key, + PyObject** result) +{ + return api->CIMultiDict_PopOne(api->state, self, key, result); +} + +static inline int +CIMultiDict_PopAll(MultiDict_CAPI* api, PyObject* self, PyObject* key, + PyObject** result) +{ + return api->CIMultiDict_PopAll(api->state, self, key, result); +} + +static inline PyObject* +CIMultiDict_PopItem(MultiDict_CAPI* api, PyObject* self) +{ + return api->CIMultiDict_PopItem(api->state, self); +} + +static inline int +CIMultiDict_Replace(MultiDict_CAPI* api, PyObject* self, PyObject* key, + PyObject* value) +{ + return api->CIMultiDict_Replace(api->state, self, key, value); +}; + +static inline int +CIMultiDict_UpdateFromMultiDict(MultiDict_CAPI* api, PyObject* self, + PyObject* other, UpdateOp op) +{ + return api->CIMultiDict_UpdateFromMultiDict(api->state, self, other, op); +}; + +static inline int +CIMultiDict_UpdateFromDict(MultiDict_CAPI* api, PyObject* self, + PyObject* other, UpdateOp op) +{ + return api->CIMultiDict_UpdateFromDict(api->state, self, other, op); +}; + +static inline int +CIMultiDict_UpdateFromSequence(MultiDict_CAPI* api, PyObject* self, + PyObject* seq, UpdateOp op) +{ + return api->CIMultiDict_UpdateFromSequence(api->state, self, seq, op); +}; + +static inline PyObject* +CIMultiDictProxy_New(MultiDict_CAPI* api, PyObject* md) +{ + return api->CIMultiDictProxy_New(api->state, md); +} + +static inline int +CIMultiDictProxy_CheckExact(MultiDict_CAPI* api, PyObject* op) +{ + PyTypeObject* type = api->CIMultiDictProxy_GetType(api->state); + int ret = Py_IS_TYPE(op, type); + Py_DECREF(type); + return ret; +} + +static inline int +CIMultiDictProxy_Check(MultiDict_CAPI* api, PyObject* op) +{ + PyTypeObject* type = api->CIMultiDictProxy_GetType(api->state); + int ret = Py_IS_TYPE(op, type) || PyObject_TypeCheck(op, type); + Py_DECREF(type); + return ret; +} + +static inline int +CIMultiDictProxy_Contains(MultiDict_CAPI* api, PyObject* self, PyObject* key) +{ + return api->CIMultiDictProxy_Contains(api->state, self, key); +} + +static inline int +CIMultiDictProxy_GetAll(MultiDict_CAPI* api, PyObject* self, PyObject* key, + PyObject** result) +{ + return api->CIMultiDictProxy_GetAll(api->state, self, key, result); +} + +static inline int +CIMultiDictProxy_GetOne(MultiDict_CAPI* api, PyObject* self, PyObject* key, + PyObject** result) +{ + return api->CIMultiDictProxy_GetOne(api->state, self, key, result); +} + +static inline PyTypeObject* +CIMultiDictProxy_GetType(MultiDict_CAPI* api) +{ + return api->CIMultiDictProxy_GetType(api->state); +} + +static inline PyObject* +MultiDictIter_New(MultiDict_CAPI* api, PyObject* self) +{ + return api->MultiDictIter_New(api->state, self); +} + +static inline int +MultiDictIter_Next(MultiDict_CAPI* api, PyObject* self, PyObject** key, + PyObject** value) +{ + return api->MultiDictIter_Next(api->state, self, key, value); +} + #endif #ifdef __cplusplus diff --git a/requirements/pytest.txt b/requirements/pytest.txt index 3671cab9a..5fcde3640 100644 --- a/requirements/pytest.txt +++ b/requirements/pytest.txt @@ -1,5 +1,6 @@ objgraph==3.6.2 pytest==8.4.0 pytest-codspeed==3.2.0 +cffi == 1.17.1; python_version=="3.13" # CFFI-2.0.0 breaks workflows under 3.13t pytest-cov==6.1.0 -psutil==7.0.0 \ No newline at end of file +psutil==7.1.0 diff --git a/setup.cfg b/setup.cfg index 209e0d69a..0aa33e733 100644 --- a/setup.cfg +++ b/setup.cfg @@ -44,3 +44,5 @@ install_requires = typing-extensions >= 4.1.0; python_version < '3.11' packages = multidict +# Needed otherwise capi is never included... +include_package_data = True diff --git a/setup.py b/setup.py index 318229fcc..e0ca2edca 100644 --- a/setup.py +++ b/setup.py @@ -31,6 +31,12 @@ ["multidict/_multidict.c"], extra_compile_args=CFLAGS, ), + # Pytest only do not use normally. + Extension( + "multidict._testcapi", + ["multidict/_testcapi.c"], + extra_compile_args=CFLAGS, + ), ] diff --git a/testcapi/pyproject.toml b/testcapi/pyproject.toml deleted file mode 100644 index f3791e5b3..000000000 --- a/testcapi/pyproject.toml +++ /dev/null @@ -1,3 +0,0 @@ -[build-system] -requires = ["setuptools >= 40"] -build-backend = "setuptools.build_meta" diff --git a/testcapi/setup.cfg b/testcapi/setup.cfg deleted file mode 100644 index 79faef80e..000000000 --- a/testcapi/setup.cfg +++ /dev/null @@ -1,17 +0,0 @@ -[bdist_wheel] -# wheels should be OS-specific: -# their names must contain macOS/manulinux1/2010/2014/Windows identifiers -universal = 0 - -[metadata] -name = testcapi -# the version doesn't matter, the helper library is never uploaded to pypi -version = 0.0.0.dev0 -description = multidict capi test utils -author = Andrew Svetlov -author_email = andrew.svetlov@gmail.com -license = Apache 2 -[options] -python_requires = >= 3.9 -packages = - testcapi diff --git a/testcapi/setup.py b/testcapi/setup.py deleted file mode 100644 index 58a14e111..000000000 --- a/testcapi/setup.py +++ /dev/null @@ -1,25 +0,0 @@ -from setuptools import Extension, setup -import multidict -import os - -NO_EXTENSIONS = bool(os.environ.get("MULTIDICT_NO_EXTENSIONS")) - -extensions = [ - Extension( - "testcapi._api", - ["testcapi/_api.c"], - include_dirs=multidict.__path__, - extra_compile_args=["-O0", "-g3", "-UNDEBUG"], - ), -] - -if not NO_EXTENSIONS: - print("*********************") - print("* Accelerated build *") - print("*********************") - setup(ext_modules=extensions) -else: - print("*********************") - print("* Pure Python build *") - print("*********************") - setup() diff --git a/testcapi/testcapi/__init__.py b/testcapi/testcapi/__init__.py deleted file mode 100644 index 45dc165b4..000000000 --- a/testcapi/testcapi/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -try: - from testcapi._api import * # noqa -except ImportError: - pass diff --git a/testcapi/testcapi/_api.c b/testcapi/testcapi/_api.c deleted file mode 100644 index 9ff2a3f7c..000000000 --- a/testcapi/testcapi/_api.c +++ /dev/null @@ -1,337 +0,0 @@ -#include -#include -#include - -typedef struct { - MultiDict_CAPI *capi; -} mod_state; - -static inline mod_state * -get_mod_state(PyObject *mod) -{ - mod_state *state = (mod_state *)PyModule_GetState(mod); - assert(state != NULL); - return state; -} - -static inline MultiDict_CAPI * -get_capi(PyObject *mod) -{ - return get_mod_state(mod)->capi; -} - -static int -check_nargs(const char *name, Py_ssize_t nargs, Py_ssize_t required) -{ - if (nargs != required) { - PyErr_Format(PyExc_TypeError, - "%s should be called with %d arguments, got %d", - name, - required, - nargs); - return -1; - } - return 0; -} - -// Took the most repetative part and put it right here to help -// you can get rid of this comment before the pr is finished, -// Using a function was less confusing here than a macro - Vizonex - -static PyObject * -handle_result(int ret, PyObject *result) -{ - if (ret < 0) { - return NULL; - } - // Test if we missed - if (ret == 0) { - return PyTuple_Pack(2, Py_None, Py_False); - } - assert(result != NULL); - PyObject *val = PyBool_FromLong(ret); - if (val == NULL) { - Py_CLEAR(result); - return NULL; - } - return PyTuple_Pack(2, result, val); -} - -/* module functions */ - -static PyObject * -md_type(PyObject *self, PyObject *unused) -{ - return Py_NewRef(MultiDict_GetType(get_capi(self))); -} - -static PyObject * -md_new(PyObject *self, PyObject *arg) -{ - long prealloc_size = PyLong_AsLong(arg); - if (prealloc_size < 0) { - if (!PyErr_Occurred()) { - PyErr_SetString(PyExc_ValueError, - "Negative prealloc_size is not allowed"); - } - return NULL; - } - return MultiDict_New(get_capi(self), prealloc_size); -} - -static PyObject * -md_add(PyObject *self, PyObject *const *args, Py_ssize_t nargs) -{ - if (check_nargs("md_add", nargs, 3) < 0) { - return NULL; - } - if (MultiDict_Add(get_capi(self), args[0], args[1], args[2]) < 0) { - return NULL; - } - Py_RETURN_NONE; -} - -static PyObject * -md_clear(PyObject *self, PyObject *arg) -{ - if (MultiDict_Clear(get_capi(self), arg) < 0) { - return NULL; - } - Py_RETURN_NONE; -} - -static PyObject * -md_setdefault(PyObject *self, PyObject *const *args, Py_ssize_t nargs) -{ - if (check_nargs("md_setdefault", nargs, 3) < 0) { - return NULL; - } - PyObject *result = NULL; - int ret = MultiDict_SetDefault( - get_capi(self), args[0], args[1], args[2], &result); - return handle_result(ret, result); -} - -static PyObject * -md_del(PyObject *self, PyObject *const *args, Py_ssize_t nargs) -{ - // handle this check first so that there's an immediate exit - // rather than waiting for the state to be obtained - if (check_nargs("md_del", nargs, 2) < 0) { - return NULL; - } - if ((MutliDict_Del(get_capi(self), args[0], args[1])) < 0) { - return NULL; - } - Py_RETURN_NONE; -} - -static PyObject * -md_version(PyObject *self, PyObject *arg) -{ - return PyLong_FromUnsignedLongLong(MultiDict_Version(get_capi(self), arg)); -} - -static PyObject * -md_contains(PyObject *self, PyObject *const *args, Py_ssize_t nargs) -{ - if (check_nargs("md_contains", nargs, 2) < 0) { - return NULL; - } - int ret = MultiDict_Contains(get_capi(self), args[0], args[1]); - if (ret == -1) { - return NULL; - } - return PyBool_FromLong(ret); -} - -static PyObject * -md_getone(PyObject *self, PyObject *const *args, Py_ssize_t nargs) -{ - if (check_nargs("md_getone", nargs, 2) < 0) { - return NULL; - } - PyObject *result = NULL; - int ret = MultiDict_GetOne(get_capi(self), args[0], args[1], &result); - return handle_result(ret, result); -} - -static PyObject * -md_getall(PyObject *self, PyObject *const *args, Py_ssize_t nargs) -{ - if (check_nargs("md_getall", nargs, 2) < 0) { - return NULL; - } - PyObject *result = NULL; - int ret = MultiDict_GetAll(get_capi(self), args[0], args[1], &result); - return handle_result(ret, result); -} - -static PyObject * -md_popone(PyObject *self, PyObject *const *args, Py_ssize_t nargs) -{ - if (check_nargs("md_popone", nargs, 2) < 0) { - return NULL; - } - PyObject *result = NULL; - int ret = MultiDict_PopOne(get_capi(self), args[0], args[1], &result); - return handle_result(ret, result); -} - -static PyObject * -md_popall(PyObject *self, PyObject *const *args, Py_ssize_t nargs) -{ - if (check_nargs("md_popall", nargs, 2) < 0) { - return NULL; - } - mod_state *state = get_mod_state(self); - PyObject *result = NULL; - int ret = MultiDict_PopAll(get_capi(self), args[0], args[1], &result); - return handle_result(ret, result); -} - -static PyObject * -md_popitem(PyObject *self, PyObject *arg) -{ - mod_state *state = get_mod_state(self); - PyObject *REF = MultiDict_PopItem(get_capi(self), arg); - if (REF != NULL) { - Py_INCREF(REF); - } - return REF; -} - -static PyObject * -md_replace(PyObject *self, PyObject *const *args, Py_ssize_t nargs) -{ - if (check_nargs("md_replace", nargs, 3) < 0) { - return NULL; - } - - if (MultiDict_Replace(get_capi(self), args[0], args[1], args[2]) < 0) { - return NULL; - } - Py_RETURN_NONE; -} - -static PyObject * -md_update_from_md(PyObject *self, PyObject *const *args, Py_ssize_t nargs) -{ - if (check_nargs("md_update_from_md", nargs, 3) < 0) { - return NULL; - } - - if (MultiDict_UpdateFromMultiDict( - get_capi(self), args[0], args[1], PyLong_AsLong(args[2])) < 0) { - return NULL; - } - Py_RETURN_NONE; -} - -static PyObject * -md_update_from_dict(PyObject *self, PyObject *const *args, Py_ssize_t nargs) -{ - if (check_nargs("md_update_from_dict", nargs, 3) < 0) { - return NULL; - } - mod_state *state = get_mod_state(self); - - if (MultiDict_UpdateFromDict( - get_capi(self), args[0], args[1], PyLong_AsLong(args[2])) < 0) { - return NULL; - } - Py_RETURN_NONE; -} - -static PyObject * -md_update_from_seq(PyObject *self, PyObject *const *args, Py_ssize_t nargs) -{ - if (check_nargs("md_update_from_seq", nargs, 3) < 0) { - return NULL; - } - if (MultiDict_UpdateFromSequence( - get_capi(self), args[0], args[1], PyLong_AsLong(args[2]))) { - return NULL; - }; - Py_RETURN_NONE; -} - -/* module slots */ - -static int -module_traverse(PyObject *mod, visitproc visit, void *arg) -{ - return 0; -} - -static int -module_clear(PyObject *mod) -{ - return 0; -} - -static void -module_free(void *mod) -{ - (void)module_clear((PyObject *)mod); -} - -static PyMethodDef module_methods[] = { - {"md_type", (PyCFunction)md_type, METH_NOARGS}, - {"md_new", (PyCFunction)md_new, METH_O}, - {"md_add", (PyCFunction)md_add, METH_FASTCALL}, - {"md_clear", (PyCFunction)md_clear, METH_O}, - {"md_setdefault", (PyCFunction)md_setdefault, METH_FASTCALL}, - {"md_del", (PyCFunction)md_del, METH_FASTCALL}, - {"md_version", (PyCFunction)md_version, METH_O}, - {"md_contains", (PyCFunction)md_contains, METH_FASTCALL}, - {"md_getone", (PyCFunction)md_getone, METH_FASTCALL}, - {"md_getall", (PyCFunction)md_getall, METH_FASTCALL}, - {"md_popone", (PyCFunction)md_popone, METH_FASTCALL}, - {"md_popall", (PyCFunction)md_popall, METH_FASTCALL}, - {"md_popitem", (PyCFunction)md_popitem, METH_O}, - {"md_replace", (PyCFunction)md_replace, METH_FASTCALL}, - {"md_update_from_md", (PyCFunction)md_update_from_md, METH_FASTCALL}, - {"md_update_from_dict", (PyCFunction)md_update_from_dict, METH_FASTCALL}, - {"md_update_from_seq", (PyCFunction)md_update_from_seq, METH_FASTCALL}, - {NULL, NULL} /* sentinel */ -}; - -static int -module_exec(PyObject *mod) -{ - mod_state *state = get_mod_state(mod); - state->capi = MultiDict_Import(); - if (state->capi == NULL) { - return -1; - } - return 0; -} - -static struct PyModuleDef_Slot module_slots[] = { - {Py_mod_exec, module_exec}, -#if PY_VERSION_HEX >= 0x030c00f0 - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, -#endif -#if PY_VERSION_HEX >= 0x030d00f0 - {Py_mod_gil, Py_MOD_GIL_NOT_USED}, -#endif - {0, NULL}, -}; - -static PyModuleDef api_module = { - .m_base = PyModuleDef_HEAD_INIT, - .m_name = "_api", - .m_size = sizeof(mod_state), - .m_methods = module_methods, - .m_slots = module_slots, - .m_traverse = module_traverse, - .m_clear = module_clear, - .m_free = (freefunc)module_free, -}; - -PyMODINIT_FUNC -PyInit__api(void) -{ - return PyModuleDef_Init(&api_module); -} diff --git a/tests/test_capi.py b/tests/test_capi.py index 22d0df1f2..b934fe23f 100644 --- a/tests/test_capi.py +++ b/tests/test_capi.py @@ -1,37 +1,45 @@ -import multidict import pytest +from multidict import MultiDict, MultiDictProxy, istr, CIMultiDict, CIMultiDictProxy + pytest.importorskip("multidict._multidict") -testcapi = pytest.importorskip("testcapi") +# We need this extension since there was no other decent way to +# compile it as a seperate project. the only logical solution was +# to inject it directly. +testcapi = pytest.importorskip("multidict._testcapi") pytestmark = pytest.mark.capi -MultiDictStr = multidict.MultiDict[str] +MultiDictStr = MultiDict[str] +MultiDictProxyStr = MultiDictProxy[str] + +CIMultiDictStr = CIMultiDict[str] +CIMultiDictProxyStr = MultiDictProxy[str] def test_md_add() -> None: - md: MultiDictStr = multidict.MultiDict() + md: MultiDictStr = MultiDict() testcapi.md_add(md, "key", "value") assert len(md) == 1 assert list(md.items()) == [("key", "value")] def test_md_clear() -> None: - previous = multidict.MultiDict([("key", "value")]) + previous = MultiDict([("key", "value")]) md: MultiDictStr = previous.copy() testcapi.md_clear(md) assert md != previous def test_md_contains() -> None: - d = multidict.MultiDict([("key", "one")]) + d = MultiDict([("key", "one")]) assert testcapi.md_contains(d, "key") testcapi.md_del(d, "key") assert testcapi.md_contains(d, "key") is False def test_md_del() -> None: - d = multidict.MultiDict([("key", "one"), ("key", "two")], foo="bar") + d = MultiDict([("key", "one"), ("key", "two")], foo="bar") assert list(d.keys()) == ["key", "key", "foo"] testcapi.md_del(d, "key") @@ -43,7 +51,7 @@ def test_md_del() -> None: def test_md_get_all() -> None: - d: MultiDictStr = multidict.MultiDict() + d: MultiDictStr = MultiDict() d.add("key1", "val1") d.add("key2", "val2") d.add("key1", "val3") @@ -52,32 +60,29 @@ def test_md_get_all() -> None: def test_md_get_all_miss() -> None: - d = multidict.MultiDict([("key", "value1")], key="value2") + d = MultiDict([("key", "value1")], key="value2") assert testcapi.md_getall(d, "x")[1] is False def test_md_getone() -> None: - d: MultiDictStr = multidict.MultiDict(key="val1") + d: MultiDictStr = MultiDict(key="val1") d.add("key", "val2") assert testcapi.md_getone(d, "key") == ("val1", True) def test_md_getone_miss() -> None: - d: MultiDictStr = multidict.MultiDict([("key", "value1")], key="value2") + d: MultiDictStr = MultiDict([("key", "value1")], key="value2") assert testcapi.md_getone(d, "x")[1] is False -@pytest.mark.skip( - "GC/WeakRef Releated Bug: SEE: https://github.com/aio-libs/multidict/pull/1190#discussion_r2162536248" -) def test_md_new() -> None: md = testcapi.md_new(0) - assert isinstance(md, multidict.MultiDict) + assert isinstance(md, MultiDict) assert len(md) == 0 def test_md_popall() -> None: - d: MultiDictStr = multidict.MultiDict() + d: MultiDictStr = MultiDict() d.add("key1", "val1") d.add("key2", "val2") @@ -88,12 +93,12 @@ def test_md_popall() -> None: def test_md_popall_key_miss() -> None: - d: MultiDictStr = multidict.MultiDict() + d: MultiDictStr = MultiDict() assert testcapi.md_popall(d, "x")[1] is False def test_md_popone() -> None: - d: MultiDictStr = multidict.MultiDict() + d: MultiDictStr = MultiDict() d.add("key", "val1") d.add("key2", "val2") d.add("key", "val3") @@ -103,12 +108,12 @@ def test_md_popone() -> None: def test_md_popone_miss() -> None: - d: MultiDictStr = multidict.MultiDict(other="val") + d: MultiDictStr = MultiDict(other="val") assert testcapi.md_popone(d, "x")[1] is False def test_md_popitem() -> None: - d: MultiDictStr = multidict.MultiDict() + d: MultiDictStr = MultiDict() d.add("key", "val1") d.add("key", "val2") @@ -118,7 +123,7 @@ def test_md_popitem() -> None: def test_md_replace() -> None: - d: MultiDictStr = multidict.MultiDict() + d: MultiDictStr = MultiDict() d.add("key", "val1") testcapi.md_replace(d, "key", "val2") assert "val2" == d["key"] @@ -127,7 +132,7 @@ def test_md_replace() -> None: def test_md_setdefault() -> None: - md: MultiDictStr = multidict.MultiDict([("key", "one"), ("key", "two")], foo="bar") + md: MultiDictStr = MultiDict([("key", "one"), ("key", "two")], foo="bar") assert ("one", True) == testcapi.md_setdefault(md, "key", "three") assert (None, False) == testcapi.md_setdefault(md, "otherkey", "three") assert "otherkey" in md @@ -135,8 +140,19 @@ def test_md_setdefault() -> None: def test_md_update_from_md() -> None: - d1: MultiDictStr = multidict.MultiDict({"key": "val1", "k": "v1"}) - d2: MultiDictStr = multidict.MultiDict({"foo": "bar", "k": "v2"}) + d1: MultiDictStr = MultiDict({"key": "val1", "k": "v1"}) + d2: MultiDictStr = MultiDict({"foo": "bar", "k": "v2"}) + + d = d1.copy() + testcapi.md_update_from_md(d, d2, 0) + assert [("key", "val1"), ("k", "v1"), ("foo", "bar"), ("k", "v2")] == list( + d.items() + ) + + +def test_md_update_from_mdproxy() -> None: + d1: MultiDictStr = MultiDict({"key": "val1", "k": "v1"}) + d2: MultiDictProxyStr = MultiDictProxy(MultiDict({"foo": "bar", "k": "v2"})) d = d1.copy() testcapi.md_update_from_md(d, d2, 0) @@ -154,7 +170,7 @@ def test_md_update_from_md() -> None: def test_md_update_from_dict() -> None: - d1: MultiDictStr = multidict.MultiDict({"key": "val1", "k": "v1"}) + d1: MultiDictStr = MultiDict({"key": "val1", "k": "v1"}) d2 = {"foo": "bar", "k": "v2"} d = d1.copy() @@ -173,7 +189,7 @@ def test_md_update_from_dict() -> None: def test_md_update_from_seq() -> None: - d1: MultiDictStr = multidict.MultiDict({"key": "val1", "k": "v1"}) + d1: MultiDictStr = MultiDict({"key": "val1", "k": "v1"}) lst = [("foo", "bar"), ("k", "v2")] d = d1.copy() @@ -192,9 +208,340 @@ def test_md_update_from_seq() -> None: def test_md_type() -> None: - assert testcapi.md_type() is multidict.MultiDict + assert testcapi.md_type() is MultiDict def test_md_version() -> None: - d = multidict.MultiDict() # type: ignore[var-annotated] + d = MultiDict() # type: ignore[var-annotated] assert testcapi.md_version(d) != 0 + + +def test_md_proxy_new() -> None: + mdp = testcapi.md_proxy_new(MultiDict({"a": 1})) + assert mdp.getone("a") == 1 + + +def test_md_proxy_contains() -> None: + d: MultiDictProxyStr = MultiDictProxy(MultiDict([("key", "one")])) + assert testcapi.md_proxy_contains(d, "key") + + +def test_md_proxy_getall() -> None: + d: MultiDictStr = MultiDict() + d.add("key1", "val1") + d.add("key2", "val2") + d.add("key1", "val3") + proxy = MultiDictProxy(d) + + ret = testcapi.md_proxy_getall(proxy, "key1") + assert (["val1", "val3"], True) == ret + + +def test_md_proxy_get_all_miss() -> None: + d = MultiDictProxy(MultiDict([("key", "value1")], key="value2")) + assert testcapi.md_proxy_getall(d, "x")[1] is False + + +def test_md_proxy_getone() -> None: + m = MultiDict(key="val1") + m.add("key", "val2") + d: MultiDictProxyStr = MultiDictProxy(m) + assert testcapi.md_proxy_getone(d, "key") == ("val1", True) + + +def test_md_proxy_getone_miss() -> None: + d: MultiDictProxyStr = MultiDictProxy(MultiDict([("key", "value1")], key="value2")) + assert testcapi.md_proxy_getone(d, "x")[1] is False + + +def test_md_proxy_type() -> None: + assert testcapi.md_proxy_type() is MultiDictProxy + + +def test_istr_from_unicode() -> None: + i = testcapi.istr_from_unicode("aBcD") + assert isinstance(i, istr) + assert i == "aBcD" + + +def test_istr_from_string() -> None: + # bytes are used to represent char* in C... + i = testcapi.istr_from_string(b"aBcD") + assert isinstance(i, istr) + assert i == "aBcD" + + +def test_istr_from_string_and_size() -> None: + i = testcapi.istr_from_string_and_size(b"testingDO_NOT_SHOW", 7) + assert isinstance(i, istr) + assert i == "testing" + + +def test_istr_get_type() -> None: + assert istr == testcapi.istr_get_type() + + +def test_ci_md_add() -> None: + md: CIMultiDictStr = CIMultiDict() + testcapi.ci_md_add(md, "key", "value") + assert len(md) == 1 + assert list(md.items()) == [("key", "value")] + + +def test_ci_md_clear() -> None: + previous = CIMultiDict([("key", "value")]) + md: CIMultiDictStr = previous.copy() + testcapi.ci_md_clear(md) + assert md != previous + + +def test_ci_md_contains() -> None: + d = CIMultiDict([("key", "one")]) + assert testcapi.ci_md_contains(d, "key") + testcapi.ci_md_del(d, "key") + assert testcapi.ci_md_contains(d, "key") is False + + +def test_ci_md_del() -> None: + d = CIMultiDict([("key", "one"), ("key", "two")], foo="bar") + assert list(d.keys()) == ["key", "key", "foo"] + + testcapi.ci_md_del(d, "key") + assert d == {"foo": "bar"} + assert list(d.items()) == [("foo", "bar")] + + with pytest.raises(KeyError, match="key"): + testcapi.ci_md_del(d, "key") + + +def test_ci_md_get_all() -> None: + d: CIMultiDictStr = CIMultiDict() + d.add("key1", "val1") + d.add("key2", "val2") + d.add("key1", "val3") + ret = testcapi.ci_md_getall(d, "key1") + assert (["val1", "val3"], True) == ret + + +def test_ci_md_get_all_miss() -> None: + d = CIMultiDict([("key", "value1")], key="value2") + assert testcapi.ci_md_getall(d, "x")[1] is False + + +def test_ci_md_getone() -> None: + d: CIMultiDictStr = CIMultiDict(key="val1") + d.add("key", "val2") + assert testcapi.ci_md_getone(d, "key") == ("val1", True) + + +def test_ci_md_getone_miss() -> None: + d: CIMultiDictStr = CIMultiDict([("key", "value1")], key="value2") + assert testcapi.ci_md_getone(d, "x")[1] is False + + +def test_ci_md_new() -> None: + md = testcapi.ci_md_new(0) + assert isinstance(md, CIMultiDict) + assert len(md) == 0 + + +def test_ci_md_popall() -> None: + d: CIMultiDictStr = CIMultiDict() + + d.add("key1", "val1") + d.add("key2", "val2") + d.add("key1", "val3") + ret = testcapi.ci_md_popall(d, "key1") + assert (["val1", "val3"], True) == ret + assert {"key2": "val2"} == d + + +def test_ci_md_popall_key_miss() -> None: + d: CIMultiDictStr = CIMultiDict() + assert testcapi.ci_md_popall(d, "x")[1] is False + + +def test_ci_md_popone() -> None: + d: CIMultiDictStr = CIMultiDict() + d.add("key", "val1") + d.add("key2", "val2") + d.add("key", "val3") + + assert ("val1", True) == testcapi.ci_md_popone(d, "key") + assert [("key2", "val2"), ("key", "val3")] == list(d.items()) + + +def test_ci_md_popone_miss() -> None: + d: CIMultiDictStr = CIMultiDict(other="val") + assert testcapi.ci_md_popone(d, "x")[1] is False + + +def test_ci_md_popitem() -> None: + d: CIMultiDictStr = CIMultiDict() + d.add("key", "val1") + d.add("key", "val2") + + assert ("key", "val2") == testcapi.ci_md_popitem(d) + assert len(d) == 1 + assert [("key", "val1")] == list(d.items()) + + +def test_ci_md_replace() -> None: + d: CIMultiDictStr = CIMultiDict() + d.add("key", "val1") + testcapi.ci_md_replace(d, "key", "val2") + assert "val2" == d["key"] + testcapi.ci_md_replace(d, "key", "val3") + assert "val3" == d["key"] + + +def test_ci_md_setdefault() -> None: + md: CIMultiDictStr = CIMultiDict([("key", "one"), ("key", "two")], foo="bar") + assert ("one", True) == testcapi.ci_md_setdefault(md, "key", "three") + assert (None, False) == testcapi.ci_md_setdefault(md, "otherkey", "three") + assert "otherkey" in md + assert "three" == md["otherkey"] + + +def test_ci_md_update_from_md() -> None: + d1: CIMultiDictStr = CIMultiDict({"key": "val1", "k": "v1"}) + d2: CIMultiDictStr = CIMultiDict({"foo": "bar", "k": "v2"}) + + d = d1.copy() + testcapi.ci_md_update_from_md(d, d2, 0) + assert [("key", "val1"), ("k", "v1"), ("foo", "bar"), ("k", "v2")] == list( + d.items() + ) + + d = d1.copy() + testcapi.ci_md_update_from_md(d, d2, 1) + assert [("key", "val1"), ("k", "v2"), ("foo", "bar")] == list(d.items()) + + d = d1.copy() + testcapi.ci_md_update_from_md(d, d2, 2) + assert [("key", "val1"), ("k", "v1"), ("foo", "bar")] == list(d.items()) + + +def test_ci_md_update_from_ci_mdproxy() -> None: + d1: MultiDictStr = CIMultiDict({"key": "val1", "k": "v1"}) + d2: MultiDictProxyStr = CIMultiDictProxy(CIMultiDict({"foo": "bar", "k": "v2"})) + + d = d1.copy() + testcapi.md_update_from_md(d, d2, 0) + assert [("key", "val1"), ("k", "v1"), ("foo", "bar"), ("k", "v2")] == list( + d.items() + ) + + d = d1.copy() + testcapi.md_update_from_md(d, d2, 1) + assert [("key", "val1"), ("k", "v2"), ("foo", "bar")] == list(d.items()) + + d = d1.copy() + testcapi.md_update_from_md(d, d2, 2) + assert [("key", "val1"), ("k", "v1"), ("foo", "bar")] == list(d.items()) + + +def test_ci_md_update_from_dict() -> None: + d1: CIMultiDictStr = CIMultiDict({"key": "val1", "k": "v1"}) + d2 = {"foo": "bar", "k": "v2"} + + d = d1.copy() + testcapi.ci_md_update_from_dict(d, d2, 0) + assert [("key", "val1"), ("k", "v1"), ("foo", "bar"), ("k", "v2")] == list( + d.items() + ) + + d = d1.copy() + testcapi.ci_md_update_from_dict(d, d2, 1) + assert [("key", "val1"), ("k", "v2"), ("foo", "bar")] == list(d.items()) + + d = d1.copy() + testcapi.ci_md_update_from_dict(d, d2, 2) + assert [("key", "val1"), ("k", "v1"), ("foo", "bar")] == list(d.items()) + + +def test_ci_md_update_from_seq() -> None: + d1: CIMultiDictStr = CIMultiDict({"key": "val1", "k": "v1"}) + lst = [("foo", "bar"), ("k", "v2")] + + d = d1.copy() + testcapi.ci_md_update_from_seq(d, lst, 0) + assert [("key", "val1"), ("k", "v1"), ("foo", "bar"), ("k", "v2")] == list( + d.items() + ) + + d = d1.copy() + testcapi.ci_md_update_from_seq(d, lst, 1) + assert [("key", "val1"), ("k", "v2"), ("foo", "bar")] == list(d.items()) + + d = d1.copy() + testcapi.ci_md_update_from_seq(d, lst, 2) + assert [("key", "val1"), ("k", "v1"), ("foo", "bar")] == list(d.items()) + + +def test_ci_md_type() -> None: + assert testcapi.ci_md_type() is CIMultiDict + + +def test_ci_md_version() -> None: + d = CIMultiDict() # type: ignore[var-annotated] + assert testcapi.ci_md_version(d) != 0 + + +def test_ci_md_proxy_new() -> None: + mdp = testcapi.ci_md_proxy_new(CIMultiDict({"a": 1})) + assert mdp.getone("a") == 1 + + +def test_ci_md_proxy_contains() -> None: + d: CIMultiDictProxyStr = CIMultiDictProxy(CIMultiDict([("key", "one")])) + assert testcapi.ci_md_proxy_contains(d, "key") + + +def test_ci_md_proxy_getall() -> None: + d: CIMultiDictStr = CIMultiDict() + d.add("key1", "val1") + d.add("key2", "val2") + d.add("key1", "val3") + proxy = CIMultiDictProxy(d) + + ret = testcapi.ci_md_proxy_getall(proxy, "key1") + assert (["val1", "val3"], True) == ret + + +def test_ci_md_proxy_get_all_miss() -> None: + d = CIMultiDictProxy(CIMultiDict([("key", "value1")], key="value2")) + assert testcapi.ci_md_proxy_getall(d, "x")[1] is False + + +def test_ci_md_proxy_getone() -> None: + m = CIMultiDict(key="val1") + m.add("key", "val2") + d: CIMultiDictProxyStr = CIMultiDictProxy(m) + assert testcapi.ci_md_proxy_getone(d, "key") == ("val1", True) + + +def test_ci_md_proxy_getone_miss() -> None: + d: CIMultiDictProxyStr = CIMultiDictProxy( + CIMultiDict([("key", "value1")], key="value2") + ) + assert testcapi.ci_md_proxy_getone(d, "x")[1] is False + + +def test_ci_md_proxy_type() -> None: + assert testcapi.ci_md_proxy_type() is CIMultiDictProxy + + +def test_md_iter_new() -> None: + md = MultiDict([("key", "value1")], key="value2") + it = testcapi.md_iter_new(md.copy()) + for k, _ in it: + assert k in md + + +def test_md_iter_next() -> None: + md = MultiDict([("key", "value1")], key="value2") + it = testcapi.md_iter_new(md.copy()) + assert testcapi.md_iter_next(it) == ("key", "value1", True) + assert testcapi.md_iter_next(it) == ("key", "value2", True) + assert testcapi.md_iter_next(it) == (None, None, False) diff --git a/tools/generate_cython_api.py b/tools/generate_cython_api.py new file mode 100644 index 000000000..535e422ef --- /dev/null +++ b/tools/generate_cython_api.py @@ -0,0 +1,547 @@ +""" +Generates A Multidict Cython API from multidict_api.h +by parsing the functions to generate the Cython API +Automatically. +""" + +from __future__ import annotations + +import argparse +import os +from dataclasses import dataclass, field +from datetime import datetime, timezone +from functools import cached_property +from pathlib import Path +from typing import Any, Callable, Sequence + +from clang.cindex import Cursor, CursorKind, Index, TranslationUnit + +# This uses cython by default because it's assumde you have cython installed +# if not throw an issue about it, will program a workaround + +from Cython.CodeWriter import LinesResult +from Cython.Tempita import Template + +__author__ = "Vizonex" +__license__ = "MIT" + +# Inspired by rust's Bindgen tool they use for making C bindings +# Originally I was going to make my own new version of pxdgen that +# retained argument names but I guess it had a different use... + + +class CursorVisitor: + """Inspired by Cython's Visitor Approch made for use with Clang""" + + dispatch_table: dict[CursorKind, Callable[[Cursor], None | Any]] + + def __init__(self) -> None: + self.dispatch_table = {} + + def visit(self, cursor: Cursor) -> Any | None: + return self._visit(cursor) + + def _visit(self, cursor: Cursor) -> Any | None: + try: + handler_method = self.dispatch_table[cursor.kind] + except KeyError: + handler_method = getattr(self, "visit_" + cursor.kind.name.lower(), None) # type: ignore[assignment] + self.dispatch_table[cursor.kind] = handler_method + + if handler_method is not None: + return handler_method(cursor) + raise RuntimeError( + "Visitor %r does not accept %s" % (self, cursor.kind.name.lower()) + ) + + def visitchildren( + self, + parent: Cursor, + attrs: Sequence[CursorKind] | None = None, + exclude: Sequence[CursorKind] | None = None, + ) -> list[Any]: + return self._visitchildren(parent, attrs, exclude) + + def _visitchildren( + self, + parent: Cursor, + attrs: Sequence[CursorKind] | None, + exclude: Sequence[CursorKind] | None, + ) -> list[Any]: + items = [] + for child in parent.get_children(): + if attrs is not None and child.kind not in attrs: + continue + if exclude is not None and child.kind in exclude: + continue + items.append(self.visit(child)) + return items + + +class RootVisitor(CursorVisitor): + """Basic BaseType for working with different clang visitors""" + + def __init__(self, translation_unit: TranslationUnit, header: str): + super().__init__() + self.tu = translation_unit + self.header = header + self.data: dict[Any, Any] = {} + + def start(self) -> list[Any]: + return [r for r in self.visitchildren(self.tu.cursor) if r is not None] + + def visit(self, cursor: Cursor) -> Any | None: + # Filter anything this isn't us... + if Path(str(cursor.location.file)).parts[-1] == self.header: + return super().visit(cursor) + return None + + +# === C Datatypes === + + +@dataclass +class EnumType: + name: str + fields: list[tuple[str, int]] + + +@dataclass +class TypeDef: # type: ignore[misc] + name: str + obj: list[Any] + + +@dataclass +class StructType: # type: ignore[misc] + name: str + fields: list[Any] + + +@dataclass +class FieldType: + name: str + type_name: str + + @property + def real_typename(self) -> str: + """Determines and translates typenames""" + if self.type_name == "PyObject *": + return "object" + return self.type_name + + +@dataclass +class PrototypeFunction: + name: str + fields: list[FieldType] + ret_name: str + + def define(self) -> str: + code = f"{self.ret_name} (*{self.name})(" + code += ", ".join([f"{f.real_typename} {f.name}" for f in self.fields]) + ")" + return code + + +@dataclass +class TypeRef: + name: str + + +@dataclass +class FunctionType(PrototypeFunction): + @cached_property + def exception_check(self) -> str: + if "*" in self.ret_name: + return " except NULL" + if "int" == self.ret_name: + return " except -1" + return "" + + @cached_property + def cy_return_name(self) -> str: + """defines the Cythonic return name of a variable""" + if self.name.endswith("New"): + name = self.name[: self.name.find("_")] + if name != "MultiDictIter": + return name + else: + return "object" + elif self.name.endswith("PopItem"): + return "tuple" + + elif self.name.endswith("GetType"): + return "type" + + elif self.name.endswith(("_CheckExact", "_Check")): + return "int" + + elif self.name.startswith("IStr"): + return "istr" + else: + return self.ret_name + + def define(self) -> str: + # we have to underscore the definitions in order to ensure we can + # still define the others the way we wish to define them... + # I'll give it a C at the beginning for users who want to + # use the CAPI directly instead of with the global __cython_multidict_api block. + code = f'{self.ret_name} C_{self.name} "{self.name}"(' + code += ", ".join([f"{f.real_typename} {f.name}" for f in self.fields]) + ")" + return code + self.exception_check + + def cython_definition(self, w: CythonBodyWriter) -> None: + w.startline(f"cdef inline {self.cy_return_name} {self.name}(") + w.endline( + ", ".join([f"{f.real_typename} {f.name}" for f in self.fields][1:]) + "):" + ) + w.indent() + w.startline("return ") + if self.ret_name != self.cy_return_name: + w.put(f"<{self.cy_return_name}>") + + w.put(f"C_{self.name}(") + + if self.fields and self.fields[0].name == "api": + w.put("__cython_multidict_api") + if len(self.fields) > 1: + w.put(", ") + w.put(", ".join([f.name for f in self.fields][1:])) + + else: + w.put(", ".join([f.name for f in self.fields])) + + w.endline(")") + w.dedent() + # Give a newline + w.putline("") + + +class CythonBodyWriter: + """Inspired by Cython's DeclarationWriter""" + + indent_string = " " + + def __init__(self, result: LinesResult | None = None) -> None: + super().__init__() + if result is None: + result = LinesResult() # type: ignore[no-untyped-call] + self.result = result + self.numindents = 0 + + def indent(self) -> None: + self.numindents += 1 + + def dedent(self) -> None: + self.numindents -= 1 + + def startline(self, s: str = "") -> None: + self.result.put(self.indent_string * self.numindents + s) # type: ignore[no-untyped-call] + + def put(self, s: str) -> None: + self.result.put(s) # type: ignore[no-untyped-call] + + def putline(self, s: str) -> None: + self.result.putline(self.indent_string * self.numindents + s) # type: ignore[no-untyped-call] + + def endline(self, s: str = "") -> None: + self.result.putline(s) # type: ignore[no-untyped-call] + + def line(self, s: str) -> None: + self.startline(s) + self.endline() + + def finish(self) -> str: + return "\n".join(self.result.lines) + + +class Library: + def __init__(self, attrs: list[Any]) -> None: + self.attrs = attrs + + @cached_property + def capi(self) -> StructType: + for s in self.attrs: + if isinstance(s, StructType): + if s.name == "MultiDict_CAPI": + return s + raise RuntimeError("MultiDict_CAPI Not found") + + def write_cython_body(self) -> str: + w = CythonBodyWriter() + # indent once becuase were inside of an extern + w.indent() + for t in self.attrs: + w.putline("") + if isinstance(t, EnumType): + # === Enums === + w.putline(f"enum {t.name}:") + w.indent() + for f in t.fields: + w.putline(f"{f[0]} = {f[1]}") + w.dedent() + + elif isinstance(t, StructType): + # === Structs === + w.putline(f"struct {t.name}:") + w.indent() + for f in t.fields: + if isinstance(f, PrototypeFunction): + w.putline(f.define()) # type: ignore[unreachable] + + w.dedent() + + elif isinstance(t, TypeDef): + # === TypeDefines === + # Cython compiler hates same-named things so ignore these... + if t.name != t.obj[0].name: + w.putline(f"ctypedef {t.obj[0].name} {t.name}") + + elif isinstance(t, FunctionType): + # === Functions === + w.putline(t.define()) + + # Finally the Cherry On top... + w.putline("MultiDict_CAPI* __cython_multidict_api") + w.dedent() + w.putline("") + w.putline("# === Cython API ===") + + for t in self.attrs: + # Filter Non-Function-Types Were only intrested in the C-API Hooks + if not isinstance(t, FunctionType): + continue + t.cython_definition(w) + + return w.finish() + + +class CAPI_Visitor(RootVisitor): + def start(self) -> Library: # type: ignore[override] + return Library(list(super().start())) + + def visit_enum_constant_decl(self, cursor: Cursor) -> tuple[str, int]: + return (str(cursor.spelling), cursor.enum_value) + + def visit_enum_decl(self, cursor: Cursor) -> EnumType: + return EnumType(cursor.spelling, self.visitchildren(cursor)) # type: ignore[arg-type] + + def visit_typedef_decl(self, cursor: Cursor) -> TypeDef: + return TypeDef(cursor.spelling, self.visitchildren(cursor)) # type: ignore[arg-type] + + def visit_struct_decl(self, cursor: Cursor) -> StructType: + children = self.visitchildren(cursor) + return StructType(cursor.spelling, children) # type: ignore[arg-type] + + def visit_type_ref(self, cursor: Cursor) -> TypeRef: + return TypeRef(cursor.spelling) # type: ignore[arg-type] + + def visit_parm_decl(self, cursor: Cursor) -> FieldType: + return FieldType(cursor.spelling, cursor.type.spelling) # type: ignore[arg-type] + + def visit_field_decl(self, cursor: Cursor) -> PrototypeFunction | FieldType: + if "(*)" in cursor.type.spelling: # type: ignore[attr-defined] + # Prototype Function + rettype = cursor.type.spelling.split("(*)", 1)[0].strip() + # Not done yet give me the parameter names, We can do better than do far better than pxdgen... + return PrototypeFunction( + cursor.spelling, # type:ignore[arg-type] + self.visitchildren(cursor, exclude=[CursorKind.TYPE_REF]), + rettype, + ) + else: + return FieldType(str(cursor.spelling), str(cursor.type.spelling)) + + def visit_function_decl(self, cursor: Cursor) -> FunctionType: + return FunctionType( + str(cursor.spelling), + self.visitchildren(cursor, attrs=[CursorKind.PARM_DECL]), + ret_name=str(cursor.result_type.spelling), + ) + + def visit_unexposed_decl(self, cursor: Cursor) -> None: + # Unknown so pass, visitor will filter this as something to ignore + pass + + +@dataclass +class MiniBindgen: + clang_args: list[str] = field(default_factory=list) + + def clang_arg(self, arg: str | Sequence[str]) -> None: + """Adds in a single clang argument a list of arguments that should be joined""" + if not isinstance(arg, str): + arg = "".join(arg) + self.clang_args.append(arg) + + def include(self, path: str | Path) -> None: + r"""Used for defining a path to your header files + this will include clang arguments under the hood + or you. This will also transform windows paths to forward + slashes on the fly. + :: + bindings = MiniBindgen() + bindings.include("path/to/header/files") + pathlib is also supported if multiple operating systems need supporting + :: + from pathlib import Path + bindings = MiniBindgen() + bindings.header("header.h") + bindings.include(Path("path") / "to" / "header" / "files") + """ + # Translate Windows Paths to forward slashes if possible. + return self.clang_arg(("-I", Path(path).as_posix())) + + def includes(self, paths: list[str | Path]) -> None: + """ + Same idea as `include` but it made for iterators, + sequence types and lists \n + See: `include` function for details + :: + bindings = MiniBindings() + # to demonstrate how it can be used... + bindings.includes(["path1", "path2", Path("custom") / "path3"]) + """ + for p in paths: + self.include(p) + + def generate( # type:ignore[no-untyped-def] + self, + header: Path | str, + excludeDecls=False, + options: int = 0, + unsaved_files: list[tuple[str, str]] = [], + ) -> CAPI_Visitor: + """Generates a clang Translation unit to utilize""" + index = Index.create(excludeDecls=excludeDecls) + return CAPI_Visitor( + index.parse( + Path(header).as_posix(), + self.clang_args, + unsaved_files=unsaved_files, + options=options, + ), + Path(header).parts[-1], + ) + + +CYTHON_API_HEAD = Template( + """# cython: language_level = 3, freethreading_compatible=True +from cpython.object cimport PyObject, PyTypeObject +from libc.stdint cimport uint64_t + +# WARNING: THIS FILE IS AUTOGENERATED DO NOT EDIT!!! +# YOUR CHANGES WILL GET OVERWRITTEN AND YOUR POOR EDITS WILL BE GONE!!! +# GENERATED ON: {{date}} +# COMPILIED BY: {{author}} + +cdef extern from "multidict_api.h": + \"\"\" +/* Extra Data comes from multidict.__init__.pxd */ +/* Ensure we can obtain the Functions we wish to utilize */ +#define MULTIDICT_IMPL +MultiDict_CAPI* __cython_multidict_api; +// Redefinitions incase required by cython... +// Don't want size calculations to get screwed up... +#if PY_VERSION_HEX >= 0x030c00f0 +#define __MANAGED_WEAKREFS +#endif +typedef struct { + PyObject_HEAD +#ifndef __MANAGED_WEAKREFS + PyObject *weaklist; +#endif + // we can ignore state however we already know it's + // a size of 4/8 depending on 32/64 bit... + void *state; + Py_ssize_t used; + uint64_t version; + bool is_ci; + htkeys_t *keys; +} MultiDictObject; +typedef struct { + PyObject_HEAD +#ifndef __MANAGED_WEAKREFS + PyObject *weaklist; +#endif + MultiDictObject *md; +} MultiDictProxyObject; +typedef struct { + PyUnicodeObject str; + PyObject *canonical; + void *state; +} istrobject; +int multidict_import(){ + __cython_multidict_api = MultiDict_Import(); + return __cython_multidict_api != NULL; +} + \"\"\" + # NOTE: Important that you import this + # After you've c-imported multidict + int multidict_import() except 0 + # Predefined objects from istr & multidict + ctypedef struct MultiDictObject: + pass + + ctypedef struct MultiDictProxyObject: + pass + + ctypedef struct istrobject: + pass + + ctypedef class _multidict.istr [object istrobject, check_size ignore]: + pass + + ctypedef class _multidict.MultiDict [object MultiDictObject, check_size ignore]: + pass + + ctypedef class _multidict.CIMultiDict [object MultiDictObject, check_size ignore]: + pass + + ctypedef class _multidict.MultiDictProxy [object MultiDictProxyObject, check_size ignore]: + pass + + ctypedef class _multidict.CIMultiDictProxy [object MultiDictProxyObject, check_size ignore]: + pass + + +""" +) # type: ignore[no-untyped-call] + + +def scan_header_file(header_file: str, author_name: str = "anonymous") -> str: + bg = MiniBindgen() + # clang needs to be able to identify PyObjects and PyTypeObjects + # this should be able to find a user's Python Path to where Python.h + # is stored. If this is not you I am in the middle of making a workaround for that... + bg.include(Path(os.environ["PYTHONPATH"]) / "include") + # print(bg) + data: Library = bg.generate(header_file).start() + + file: str = CYTHON_API_HEAD.substitute( + author=author_name, date=datetime.now(timezone.utc) + ) # type: ignore[no-untyped-call] + + file += data.write_cython_body() + return file + + +def cli() -> None: + parser = argparse.ArgumentParser( + description="Compiles Multidict Cython pxd file using clang" + ) + parser.add_argument( + "-a", "--author", help="who's compiling the code", default="anonymous" + ) + parser.add_argument("-o", "--output", default="multidict/__init__.pxd") + parser.add_argument( + "-i", "--input", help="input header file", default="multidict/multidict_api.h" + ) + args = parser.parse_args() + data = scan_header_file(args.input, args.author) + with open(args.output, "w") as w: + w.write(data) + + +if __name__ == "__main__": + cli()