diff --git a/CHANGES/1185.feature.rst b/CHANGES/1185.feature.rst new file mode 100644 index 000000000..bde0ba5ea --- /dev/null +++ b/CHANGES/1185.feature.rst @@ -0,0 +1 @@ +Added MultiDict Functions to C-API -- by ``@Vizonex``. \ No newline at end of file diff --git a/multidict/_multilib/capsule.h b/multidict/_multilib/capsule.h index a5a3f7e1e..4ad0cf186 100644 --- a/multidict/_multilib/capsule.h +++ b/multidict/_multilib/capsule.h @@ -18,18 +18,26 @@ _invalid_type() PyErr_SetString(PyExc_TypeError, "self should be a MultiDict instance"); } -static PyTypeObject * -MultiDict_GetType(void *state_) +/// @brief Gets the multidict type object +/// @param state_ the module state to obtain the type from +/// @return MultiDictType as a pointer object otherwise NULLL +static PyTypeObject* +MultiDict_GetType(void* state_) { - mod_state *state = (mod_state *)state_; - return (PyTypeObject *)Py_NewRef(state->MultiDictType); + mod_state* state = (mod_state*)state_; + return (PyTypeObject*)Py_NewRef(state->MultiDictType); } -static PyObject * -MultiDict_New(void *state_, int prealloc_size) +/// @brief Creates a new multidict with a preallocated number of entires to +/// work with +/// @param state_ the state to obtain the MultiDictType from +/// @param prealloc_size the number of entries to preallocate room for +/// @return A Multidict object otherwise NULL +static PyObject* +MultiDict_New(void* state_, int prealloc_size) { - mod_state *state = (mod_state *)state_; - MultiDictObject *md = + mod_state* state = (mod_state*)state_; + MultiDictObject* md = PyObject_GC_New(MultiDictObject, state->MultiDictType); if (md == NULL) { return NULL; @@ -39,38 +47,404 @@ MultiDict_New(void *state_, int prealloc_size) return NULL; } PyObject_GC_Track(md); - return (PyObject *)md; + return (PyObject*)md; } +/// @brief Adds a key and value to the mulitidict +/// @param state_ the module state pointer +/// @param self the multidict object +/// @param key the key +/// @param value the value to set +/// @return 0 if success otherwise -1 , will raise TypeError if MultiDict's +/// Type is incorrect static int -MultiDict_Add(void *state_, PyObject *self, PyObject *key, PyObject *value) +MultiDict_Add(void* state_, PyObject* self, PyObject* key, PyObject* value) { - mod_state *state = (mod_state *)state_; + mod_state* state = (mod_state*)state_; if (MultiDict_Check(state, self) <= 0) { _invalid_type(); return -1; } - return md_add((MultiDictObject *)self, key, value); + return md_add((MultiDictObject*)self, key, value); } +/// @brief Clears a multidict object and removes all it's entries +/// @param state_ the module state pointer +/// @param self the multidict object +/// @return 0 if success otherwise -1 , will raise TypeError if MultiDict's +/// Type is incorrect +static int +MultiDict_Clear(void* state_, PyObject* self) +{ + // TODO: Macro for repeated steps being done? + mod_state* state = (mod_state*)state_; + if (MultiDict_Check(state, self) <= 0) { + _invalid_type(); + return -1; + } + return md_clear((MultiDictObject*)self); +} + +/// @brief If key is in the dictionary its the first value. +/// If not, insert key with a value of default and return default. +/// @param state_ the module state pointer +/// @param self the MultiDict object +/// @param key the key to insert +/// @param _default the default value to have inserted +/// @return default on sucess, NULL on failure +PyObject* +MultiDict_SetDefault(void* state_, PyObject* self, PyObject* key, + PyObject* _default) +{ + mod_state* state = (mod_state*)state_; + if (MultiDict_Check(state, self) <= 0) { + _invalid_type(); + return NULL; + } + return md_set_default((MultiDictObject*)self, key, _default); +} + +/// @brief Remove all items where key is equal to key from d. +/// @param state_ the module state 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 int +MultiDict_Del(void* state_, PyObject* self, PyObject* key) +{ + mod_state* state = (mod_state*)state_; + if (MultiDict_Check(state, self) <= 0) { + _invalid_type(); + return -1; + } + if (md_del((MultiDictObject*)self, key) < 0) { + PyErr_SetObject(PyExc_KeyError, key); + return -1; + } + return 0; +} + +/// @brief Return a version of given mdict object +/// @param state_ the module state pointer +/// @param self the mdict object +/// @return the version flag of the object, otherwise 0 on failure +static uint64_t +MultiDict_Version(void* state_, PyObject* self) +{ + mod_state* state = (mod_state*)state_; + if (MultiDict_Check(state, self) <= 0) { + _invalid_type(); + // Can't use -1 because version is unsigned, resort to zero... + return 0; + } + return md_version((MultiDictObject*)self); +} + +/// @brief Determines if a certain key exists a multidict object +/// @param state_ the module state +/// @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 int +MultiDict_Contains(void* state_, PyObject* self, PyObject* key) +{ + mod_state* state = (mod_state*)state_; + if (MultiDict_Check(state, self) <= 0) { + _invalid_type(); + return -1; + } + return md_contains((MultiDictObject*)self, key, NULL); +}; + +/// @brief Return the **first** value for *key* if *key* is in the +/// dictionary, else *default*. +/// @param state_ the module state 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 PyObject* +MultiDict_GetOne(void* state_, PyObject* self, PyObject* key) +{ + mod_state* state = (mod_state*)state_; + if (MultiDict_Check(state, self) <= 0) { + _invalid_type(); + return NULL; + } + PyObject* ret = NULL; + if (md_get_one((MultiDictObject*)self, key, &ret) < 0) { + return NULL; + } else if (ret == NULL) { + PyErr_SetObject(PyExc_KeyError, key); + } + return ret; +} + +/// @brief Return the **first** value for *key* if *key* is in the +/// dictionary, else None. +/// @param state_ the module state pointer +/// @param self the multidict object +/// @param key the key to get one item from +/// @return returns a default value on success, NULL is returned and raises +/// `TypeError` on failure +static PyObject* +MultiDict_Get(void* state_, PyObject* self, PyObject* key) +{ + mod_state* state = (mod_state*)state_; + if (MultiDict_Check(state, self) <= 0) { + _invalid_type(); + return NULL; + } + PyObject* ret = NULL; + if ((md_get_one((MultiDictObject*)self, key, &ret) < 0)) { + return NULL; + } + return (ret == NULL) ? Py_None : ret; +} + +/// @brief Return a list of all values for *key* if *key* is in the +/// dictionary, else *default*. +/// @param state_ the module state 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 PyObject* +MultiDict_GetAll(void* state_, PyObject* self, PyObject* key) +{ + mod_state* state = (mod_state*)state_; + if (MultiDict_Check(state, self) < 0) { + _invalid_type(); + return NULL; + } + PyObject* ret = NULL; + if (md_get_all((MultiDictObject*)self, key, &ret) < 0) { + return NULL; + }; + if (ret == NULL) { + PyErr_SetObject(PyExc_KeyError, key); + return NULL; + } + return ret; +} + +/// @brief Remove and return a value from the dictionary. +/// @param state_ the module state pointer +/// @param self the multidict object +/// @param key the key to remove +/// @return corresponding value on success or None, otherwise raises +/// `TypeError` or `KeyError` and returns NULL +static PyObject* +MultiDict_Pop(void* state_, PyObject* self, PyObject* key) +{ + mod_state* state = (mod_state*)state_; + if (MultiDict_Check(state, self) <= 0) { + _invalid_type(); + return NULL; + } + PyObject* ret; + if (md_pop_one((MultiDictObject*)self, key, &ret) < 0) { + PyErr_SetObject(PyExc_KeyError, key); + }; + return ret; +} + +/// @brief If `key` is in the dictionary, remove it and return its the +/// `first` value, else return `default`. +/// @param state_ the module state 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 PyObject* +MultiDict_PopOne(void* state_, PyObject* self, PyObject* key) +{ + mod_state* state = (mod_state*)state_; + if (MultiDict_Check(state, self) <= 0) { + _invalid_type(); + return NULL; + } + PyObject* ret = NULL; + if (md_pop_one((MultiDictObject*)self, key, &ret) < 0) { + PyErr_SetObject(PyExc_KeyError, key); + return NULL; + } + + if (ret == NULL) { + PyErr_SetObject(PyExc_KeyError, key); + return NULL; + } + return ret; +} + +/// @brief Pops all related objects corresponding to `key` +/// @param state_ the module state 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 PyObject* +MultiDict_PopAll(void* state_, PyObject* self, PyObject* key) +{ + mod_state* state = (mod_state*)state_; + if (MultiDict_Check(state, self) <= 0) { + _invalid_type(); + return NULL; + } + PyObject* ret = NULL; + if (md_pop_all((MultiDictObject*)self, key, &ret) < 0) { + return NULL; + }; + if (ret == NULL) { + PyErr_SetObject(PyExc_KeyError, key); + return NULL; + } + return ret; +} + +/// @brief Remove and return an arbitrary `(key, value)` pair from the +/// dictionary. +/// @param state_ the module state pointer +/// @param self the multidict object +/// @return an arbitray tuple on success, otherwise NULL on error along +/// with `TypeError` or `KeyError` raised +static PyObject* +MultiDict_PopItem(void* state_, PyObject* self) +{ + mod_state* state = (mod_state*)state_; + if (MultiDict_Check(state, self) <= 0) { + _invalid_type(); + return NULL; + } + return md_pop_item((MultiDictObject*)self); +} + +/// @brief Replaces a set object with another object +/// @param state_ the module state 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 int +MultiDict_Replace(void* state_, PyObject* self, PyObject* key, PyObject* value) +{ + mod_state* state = (mod_state*)state_; + if (MultiDict_Check(state, self) <= 0) { + _invalid_type(); + return -1; + } + return md_replace((MultiDictObject*)self, key, value); +} + +/// @brief Updates Multidict object using another MultiDict Object +/// @param state_ the module state 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 int +MultiDict_UpdateFromMultiDict(void* state_, PyObject* self, PyObject* other, + bool update) +{ + mod_state* state = (mod_state*)state_; + if (MultiDict_Check(state, self) <= 0) { + PyErr_SetString(PyExc_TypeError, + "self should be a MultiDict instance"); + return -1; + } + if (MultiDict_Check(state, other) <= 0) { + PyErr_SetString(PyExc_TypeError, + "other should be a MultiDict instance"); + return -1; + } + return md_update_from_ht( + (MultiDictObject*)self, (MultiDictObject*)other, update); +} + +/// @brief Updates Multidict object using another Dictionary Object +/// @param state_ the module state 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 int +MultiDict_UpdateFromDict(void* state_, PyObject* self, PyObject* kwds, + bool update) +{ + mod_state* state = (mod_state*)state_; + if (MultiDict_Check(state, self) <= 0) { + _invalid_type(); + return -1; + } + return md_update_from_dict((MultiDictObject*)self, kwds, update); +} + +/// @brief Updates Multidict object using a sequence object +/// @param state_ the module state 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 int +MultiDict_UpdateFromSequence(void* state_, PyObject* self, PyObject* seq, + bool update) +{ + mod_state* state = (mod_state*)state_; + if (MultiDict_Check(state, self) <= 0) { + _invalid_type(); + return -1; + } + return md_update_from_seq((MultiDictObject*)self, seq, update); +} + +/// @brief Checks to see if a multidict matches another dictionary or multidict +/// object +/// @param state_ the module state pointer +/// @param self the multidict object +/// @param other the corresponding object to check against +/// @return 1 if true, 0 if false, -1 if failue occured follwed by raising a +/// TypeError +static int +MultiDict_Equals(void* state_, PyObject* self, PyObject* other) +{ + mod_state* state = (mod_state*)state_; + if (MultiDict_Check(state, self) <= 0) { + _invalid_type(); + return -1; + } + if (PyMapping_Check(other)) { + return md_eq_to_mapping((MultiDictObject*)self, other); + } else if (MultiDict_Check(state, other)) { + return md_eq((MultiDictObject*)self, (MultiDictObject*)other); + } + return 0; +} + +/// @brief Frees the Multidict CAPI Capsule Object from +/// the Heap Normally you won't be needing to call this +/// @param capi The Capsule Object To Free static void -capsule_free(MultiDict_CAPI *capi) +capsule_free(MultiDict_CAPI* capi) { PyMem_Free(capi); } static void -capsule_destructor(PyObject *o) +capsule_destructor(PyObject* o) { - MultiDict_CAPI *capi = PyCapsule_GetPointer(o, MultiDict_CAPSULE_NAME); + MultiDict_CAPI* capi = PyCapsule_GetPointer(o, MultiDict_CAPSULE_NAME); capsule_free(capi); } -static PyObject * -new_capsule(mod_state *state) +static PyObject* +new_capsule(mod_state* state) { - MultiDict_CAPI *capi = - (MultiDict_CAPI *)PyMem_Malloc(sizeof(MultiDict_CAPI)); + MultiDict_CAPI* capi = + (MultiDict_CAPI*)PyMem_Malloc(sizeof(MultiDict_CAPI)); if (capi == NULL) { PyErr_NoMemory(); return NULL; @@ -79,8 +453,25 @@ new_capsule(mod_state *state) capi->MultiDict_GetType = MultiDict_GetType; capi->MultiDict_New = MultiDict_New; capi->MultiDict_Add = MultiDict_Add; + capi->MultiDict_Clear = MultiDict_Clear; + capi->MultiDict_SetDefault = MultiDict_SetDefault; + capi->MultiDict_Del = MultiDict_Del; + capi->MultiDict_Version = MultiDict_Version; + capi->MultiDict_Contains = MultiDict_Contains; + capi->MultiDict_Get = MultiDict_Get; + capi->MultiDict_GetOne = MultiDict_GetOne; + capi->MultiDict_GetAll = MultiDict_GetAll; + capi->MultiDict_Pop = MultiDict_Pop; + capi->MultiDict_PopOne = MultiDict_PopOne; + capi->MultiDict_PopAll = MultiDict_PopAll; + capi->MultiDict_PopItem = MultiDict_PopItem; + capi->MultiDict_Replace = MultiDict_Replace; + capi->MultiDict_UpdateFromMultiDict = MultiDict_UpdateFromMultiDict; + capi->MultiDict_UpdateFromDict = MultiDict_UpdateFromDict; + capi->MultiDict_UpdateFromSequence = MultiDict_UpdateFromSequence; + capi->MultiDict_Equals = MultiDict_Equals; - PyObject *ret = + PyObject* ret = PyCapsule_New(capi, MultiDict_CAPSULE_NAME, capsule_destructor); if (ret == NULL) { capsule_free(capi); diff --git a/multidict/multidict_api.h b/multidict/multidict_api.h index 91e2beb53..ba928ff08 100644 --- a/multidict/multidict_api.h +++ b/multidict/multidict_api.h @@ -6,6 +6,7 @@ extern "C" { #endif #include +#include #define MultiDict_MODULE_NAME "multidict._multidict" #define MultiDict_CAPI_NAME "CAPI" @@ -22,60 +23,348 @@ typedef struct { if the client is compiled with older multidict_api.h header */ - void *state; + void* state; - PyTypeObject *(*MultiDict_GetType)(void *state); + PyTypeObject* (*MultiDict_GetType)(void* state); + + 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); + + PyObject* (*MultiDict_SetDefault)(void* state, PyObject* self, + PyObject* key, PyObject* _default); + + int (*MultiDict_Del)(void* state, PyObject* self, PyObject* key); + uint64_t (*MultiDict_Version)(void* state, PyObject* self); + + // proposed but IDK yet... + // static int MultiDict_CreatePosMarker(void* state, PyObject* self, + // md_pos_t* pos) + + // static int MultiDict_Next(void* state, PyObject* self, + // md_pos_t* pos, PyObject** identity, PyObject**key, PyObject **value) + + int (*MultiDict_Contains)(void* state, PyObject* self, PyObject* key); + PyObject* (*MultiDict_Get)(void* state, PyObject* self, PyObject* key); + PyObject* (*MultiDict_GetOne)(void* state, PyObject* self, PyObject* key); + PyObject* (*MultiDict_GetAll)(void* state, PyObject* self, PyObject* key); + PyObject* (*MultiDict_Pop)(void* state, PyObject* self, PyObject* key); + PyObject* (*MultiDict_PopOne)(void* state, PyObject* self, PyObject* key); + PyObject* (*MultiDict_PopAll)(void* state, PyObject* self, PyObject* key); + PyObject* (*MultiDict_PopItem)(void* state, PyObject* self); + int (*MultiDict_Replace)(void* state, PyObject* self, PyObject* key, + PyObject* value); + int (*MultiDict_UpdateFromMultiDict)(void* state, PyObject* self, + PyObject* other, bool update); + int (*MultiDict_UpdateFromDict)(void* state, PyObject* self, + PyObject* kwds, bool update); + int (*MultiDict_UpdateFromSequence)(void* state, PyObject* self, + PyObject* kwds, bool update); + int (*MultiDict_Equals)(void* state, PyObject* self, PyObject* other); - PyObject *(*MultiDict_New)(void *state, int prealloc_size); - int (*MultiDict_Add)(void *state, PyObject *self, PyObject *key, - PyObject *value); } MultiDict_CAPI; #ifndef MULTIDICT_IMPL -static inline MultiDict_CAPI * +/// @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); + return (MultiDict_CAPI*)PyCapsule_Import(MultiDict_CAPSULE_NAME, 0); } -static inline PyTypeObject * -MultiDict_GetType(MultiDict_CAPI *api) +/// @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) +MultiDict_CheckExact(MultiDict_CAPI* api, PyObject* op) { - PyTypeObject *type = api->MultiDict_GetType(api->state); + PyTypeObject* type = api->MultiDict_GetType(api->state); int ret = Py_IS_TYPE(op, type); Py_DECREF(type); 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) +MultiDict_Check(MultiDict_CAPI* api, PyObject* op) { - PyTypeObject *type = api->MultiDict_GetType(api->state); + 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 * -MultiDict_New(MultiDict_CAPI *api, int prealloc_size) +/// @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) +MultiDict_Add(MultiDict_CAPI* api, PyObject* self, PyObject* key, + PyObject* value) { 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 int +MultiDict_Clear(MultiDict_CAPI* api, PyObject* self) +{ + return api->MultiDict_Clear(api->state, self); +} + +/// @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 +PyObject* +Multidict_SetDefault(MultiDict_CAPI* api, PyObject* self, PyObject* key, + PyObject* _default) +{ + return api->MultiDict_SetDefault(api->state, self, key, _default); +} + +/// @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 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 int MultiDict_CreatePosMarker(MultiDict_CAPI* api, +// PyObject* self, md_pos_t* pos){ +// return api->MultiDict_CreatePosMarker(api->state, self, pos); +// } +// static 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 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 PyObject* +MultiDict_GetOne(MultiDict_CAPI* api, PyObject* self, PyObject* key) +{ + return api->MultiDict_GetOne(api->state, self, key); +} + +/// @brief Return the **first** value for *key* if *key* is in the +/// dictionary, else None. +/// @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, NULL is returned and raises +/// `TypeError` on failure +static PyObject* +MultiDict_Get(MultiDict_CAPI* api, PyObject* self, PyObject* key) +{ + return api->MultiDict_Get(api->state, self, key); +} + +/// @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 PyObject* +MultiDict_GetAll(MultiDict_CAPI* api, PyObject* self, PyObject* key) +{ + return api->MultiDict_GetAll(api, self, key); +} + +/// @brief Remove and return a value from the dictionary. +/// @param api Python Capsule Pointer +/// @param self the multidict object +/// @param key the key to remove +/// @return corresponding value on success or None, otherwise raises TypeError +/// and returns NULL +static PyObject* +MultiDict_Pop(MultiDict_CAPI* api, PyObject* self, PyObject* key) +{ + return api->MultiDict_Pop(api->state, self, key); +} + +/// @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 PyObject* +MultiDict_PopOne(MultiDict_CAPI* api, PyObject* self, PyObject* key) +{ + return api->MultiDict_PopOne(api->state, self, key); +} + +/// @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 PyObject* +MultiDict_PopAll(MultiDict_CAPI* api, PyObject* self, PyObject* key) +{ + return api->MultiDict_PopAll(api->state, self, key); +} + +/// @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 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 int +MultiDict_Replace(MultiDict_CAPI* api, PyObject* self, PyObject* key, + PyObject* value) +{ + 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 int +MultiDict_UpdateFromMultiDict(MultiDict_CAPI* api, PyObject* self, + PyObject* other, bool update) +{ + return api->MultiDict_UpdateFromMultiDict(api->state, self, other, update); +}; + +/// @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 int +MultiDict_UpdateFromDict(MultiDict_CAPI* api, PyObject* self, PyObject* kwds, + bool update) +{ + return api->MultiDict_UpdateFromDict(api->state, self, kwds, update); +}; + +/// @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 int +MultiDict_UpdateFromSequence(MultiDict_CAPI* api, PyObject* self, + PyObject* seq, bool update) +{ + return api->MultiDict_UpdateFromSequence(api->state, self, seq, update); +}; + +/// @brief Checks to see if a multidict matches another dictionary or multidict +/// object +/// @param api Python Capsule Pointer +/// @param self the multidict object +/// @param other the corresponding object to check against +/// @return 1 if true, 0 if false, -1 if failue occured follwed by raising a +/// TypeError +static int +MultiDict_Equals(MultiDict_CAPI* api, PyObject* self, PyObject* other) +{ + return api->MultiDict_Equals(api->state, self, other); +}; + #endif #ifdef __cplusplus diff --git a/testcapi/testcapi/_api.c b/testcapi/testcapi/_api.c index bb1a0220f..52f6c633b 100644 --- a/testcapi/testcapi/_api.c +++ b/testcapi/testcapi/_api.c @@ -1,5 +1,6 @@ #include #include +#include typedef struct { MultiDict_CAPI *capi; @@ -13,6 +14,15 @@ get_mod_state(PyObject *mod) return state; } + +#define PyBool_As_CBool(obj) \ + PyObject_IsTrue(obj) ? true: false + +#define RETURN_NULL_OR_NEWREF(ITEM) \ + PyObject* REF = ITEM; \ + return (REF != NULL) ? Py_NewRef(REF) : NULL + + /* module functions */ static PyObject * @@ -26,7 +36,7 @@ static PyObject * md_new(PyObject *self, PyObject *arg) { mod_state *state = get_mod_state(self); - return MultiDict_New(state->capi, 0); + return Py_NewRef(MultiDict_New(state->capi, 0)); } static PyObject * @@ -44,6 +54,234 @@ md_add(PyObject *self, PyObject *const *args, Py_ssize_t nargs) Py_RETURN_NONE; } +static PyObject* +md_clear( + PyObject* self, PyObject *arg +){ + mod_state *state = get_mod_state(self); + if (MultiDict_Clear(state->capi, arg) < 0){ + return NULL; + } + Py_RETURN_NONE; +} + + +static PyObject* +md_set_default(PyObject* self, PyObject *const *args, Py_ssize_t nargs){ + if (nargs != 3) { + PyErr_SetString(PyExc_TypeError, + "md_set_default should be called with md, key and value"); + return NULL; + } + mod_state* state = get_mod_state(self); + RETURN_NULL_OR_NEWREF(Multidict_SetDefault(state->capi, args[0], args[1], args[2])); +} + +static PyObject* +md_del( + PyObject* self, PyObject *const *args, Py_ssize_t nargs +){ + if (nargs != 2) { + PyErr_SetString(PyExc_TypeError, + "md_del should be called with md and key"); + return NULL; + } + mod_state* state = get_mod_state(self); + if ((MutliDict_Del(state->capi, args[0], args[1])) < 0){ + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject* +md_version( + PyObject* self, PyObject *arg +){ + mod_state* state = get_mod_state(self); + return PyLong_FromUnsignedLongLong(MultiDict_Version(state->capi, arg)); +} + +static PyObject* +md_contains( + PyObject* self, PyObject *const *args, Py_ssize_t nargs +){ + if (nargs != 2) { + PyErr_SetString(PyExc_TypeError, + "md_contains should be called with md and key"); + return NULL; + } + mod_state* state = get_mod_state(self); + int ret = MultiDict_Contains(state->capi, args[0], args[1]); + if (ret == -1){ + return NULL; + } + return PyBool_FromLong(ret); +} + +static PyObject* +md_get( + PyObject* self, PyObject *const *args, Py_ssize_t nargs +){ + if (nargs != 2) { + PyErr_SetString(PyExc_TypeError, + "md_get should be called with md and key"); + return NULL; + } + mod_state* state = get_mod_state(self); + RETURN_NULL_OR_NEWREF(MultiDict_Get(state->capi, args[0], args[1])); +} + +static PyObject* +md_get_all( + PyObject* self, PyObject *const *args, Py_ssize_t nargs +){ + if (nargs != 2) { + PyErr_SetString(PyExc_TypeError, + "md_get_all should be called with md and key"); + return NULL; + } + mod_state* state = get_mod_state(self); + RETURN_NULL_OR_NEWREF(MultiDict_GetAll(state->capi, args[0], args[1])); +} + +static PyObject* +md_pop( + PyObject* self, PyObject *const *args, Py_ssize_t nargs +){ + if (nargs != 2) { + PyErr_SetString(PyExc_TypeError, + "md_pop should be called with md and key"); + return NULL; + } + mod_state* state = get_mod_state(self); + RETURN_NULL_OR_NEWREF(MultiDict_Pop(state->capi, args[0], args[1])); +} + +static PyObject* +md_popone( + PyObject* self, PyObject *const *args, Py_ssize_t nargs +){ + if (nargs != 2){ + PyErr_SetString(PyExc_TypeError, + "md_popone should be called with md and key"); + return NULL; + } + mod_state* state = get_mod_state(self); + RETURN_NULL_OR_NEWREF(MultiDict_PopOne(state->capi, args[0], args[1])); +} + +static PyObject* +md_popall( + PyObject* self, PyObject *const *args, Py_ssize_t nargs +){ + if (nargs != 2){ + PyErr_SetString(PyExc_TypeError, + "md_popone should be called with md and key"); + return NULL; + } + mod_state* state = get_mod_state(self); + RETURN_NULL_OR_NEWREF(MultiDict_PopAll(state->capi, args[0], args[1])); +} + +static PyObject* +md_popitem( + PyObject* self, PyObject* arg +){ + mod_state* state = get_mod_state(self); + RETURN_NULL_OR_NEWREF(MultiDict_PopItem(state->capi, arg)); +} + +static PyObject* +md_replace( + PyObject* self, PyObject *const *args, Py_ssize_t nargs +){ + if (nargs != 3){ + PyErr_SetString(PyExc_TypeError, + "md_replace should be called with md, key and value"); + return NULL; + } + mod_state* state = get_mod_state(self); + if (MultiDict_Replace(state->capi, 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 (nargs != 3){ + PyErr_SetString(PyExc_TypeError, + "md_update_from_md should be called with md, other, and update"); + return NULL; + } + mod_state* state = get_mod_state(self); + + if (MultiDict_UpdateFromMultiDict(state->capi, args[0], args[1], PyBool_As_CBool(args[2])) < 0){ + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject* +md_update_from_dict( + PyObject* self, PyObject *const *args, Py_ssize_t nargs +){ + if (nargs != 3){ + PyErr_SetString(PyExc_TypeError, + "md_update_from_dict should be called with md, other, and update"); + return NULL; + } + mod_state* state = get_mod_state(self); + + if (MultiDict_UpdateFromDict(state->capi, args[0], args[1], PyBool_As_CBool(args[2])) < 0){ + return NULL; + } + Py_RETURN_NONE; +} + + + +static PyObject* +md_update_from_seq( + PyObject* self, PyObject *const *args, Py_ssize_t nargs +){ + if (nargs != 3){ + PyErr_SetString(PyExc_TypeError, + "md_update_from_seq should be called with md, other, and update"); + return NULL; + } + mod_state* state = get_mod_state(self); + if (MultiDict_UpdateFromSequence(state->capi, args[0], args[1], PyBool_As_CBool(args[2]))){ + return NULL; + }; + Py_RETURN_NONE; +} + +static PyObject* +md_equals( + PyObject* self, PyObject *const *args, Py_ssize_t nargs +){ + if (nargs != 2){ + PyErr_SetString(PyExc_TypeError, + "md_equals should be called with md and other"); + return NULL; + } + mod_state* state = get_mod_state(self); + + switch (MultiDict_Equals(state->capi, args[0], args[1])) { + case -1: + return NULL; + case 0: + Py_RETURN_FALSE; + default: + Py_RETURN_TRUE; + } +} + + + /* module slots */ static int @@ -68,6 +306,22 @@ 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_set_default", (PyCFunction)md_set_default, METH_FASTCALL}, + {"md_del", (PyCFunction)md_del, METH_FASTCALL}, + {"md_version", (PyCFunction)md_version, METH_O}, + {"md_contains", (PyCFunction)md_contains, METH_FASTCALL}, + {"md_get", (PyCFunction)md_get, METH_FASTCALL}, + {"md_get_all", (PyCFunction)md_get_all, METH_FASTCALL}, + {"md_pop", (PyCFunction)md_pop, 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_equals", (PyCFunction)md_equals, METH_FASTCALL}, {NULL, NULL} /* sentinel */ }; diff --git a/tests/test_capi.py b/tests/test_capi.py index c9e80ff79..c5cbc6ded 100644 --- a/tests/test_capi.py +++ b/tests/test_capi.py @@ -24,3 +24,153 @@ def test_md_add() -> None: 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")]) + md: MultiDictStr = previous.copy() + testcapi.md_clear(md) + assert md != previous + + +def test_set_default() -> None: + md: MultiDictStr = multidict.MultiDict([("key", "one"), ("key", "two")], foo="bar") + assert "one" == testcapi.md_set_default(md, "key", "three") + assert "three" == testcapi.md_set_default(md, "otherkey", "three") + assert "otherkey" in md + assert "three" == md["otherkey"] + + +def test_del() -> None: + d = multidict.MultiDict([("key", "one"), ("key", "two")], foo="bar") + assert list(d.keys()) == ["key", "key", "foo"] + + testcapi.md_del(d, "key") + assert d == {"foo": "bar"} + assert list(d.items()) == [("foo", "bar")] + + with pytest.raises(KeyError, match="key"): + testcapi.md_del(d, "key") + + +def test_md_version() -> None: + d = multidict.MultiDict() # type: ignore[var-annotated] + assert testcapi.md_version(d) != 0 + + +def test_md_contains() -> None: + d = multidict.MultiDict([("key", "one")]) + assert testcapi.md_contains(d, "key") + testcapi.md_del(d, "key") + assert testcapi.md_contains(d, "key") is False + + +# I will deal with this one later, Seems beyond my control... + + +def test_md_get() -> None: + d = multidict.MultiDict([("key", "one"), ("foo", "bar")]) + assert testcapi.md_get(d, "key") == "one" + assert testcapi.md_get(d, "i dont exist") is None + + +def test_md_get_all() -> None: + d = multidict.MultiDict([("key", "value1")], key="value2") + assert testcapi.md_get_all(d, "key") == ["value1", "value2"] + + +def test_md_get_all_excpection() -> None: + d = multidict.MultiDict([("key", "value1")], key="value2") + with pytest.raises(KeyError, match="some_key"): + testcapi.md_get_all(d, "some_key") + + +def test_md_pop() -> None: + d: MultiDictStr = multidict.MultiDict() + d.add("key", "val1") + d.add("key", "val2") + + assert "val1" == testcapi.md_pop(d, "key") + assert {"key": "val2"} == d + + +def test_md_popone() -> None: + d: MultiDictStr = multidict.MultiDict() + d.add("key", "val1") + d.add("key2", "val2") + d.add("key", "val3") + + assert "val1" == testcapi.md_popone(d, "key") + assert [("key2", "val2"), ("key", "val3")] == list(d.items()) + + +def test_md_popone_exception() -> None: + md: MultiDictStr = multidict.MultiDict(other="val") + with pytest.raises(KeyError, match="key"): + testcapi.md_popone(md, "key") + + +def test_md_popall() -> None: + d: MultiDictStr = multidict.MultiDict() + + d.add("key1", "val1") + d.add("key2", "val2") + d.add("key1", "val3") + ret = testcapi.md_popall(d, "key1") + assert ["val1", "val3"] == ret + assert {"key2": "val2"} == d + + +def test_md_popall_key_error() -> None: + d: MultiDictStr = multidict.MultiDict() + with pytest.raises(KeyError, match="key"): + testcapi.md_popall(d, "key") + + +def test_md_popitem() -> None: + d: MultiDictStr = multidict.MultiDict() + d.add("key", "val1") + d.add("key", "val2") + + assert ("key", "val2") == testcapi.md_popitem(d) + assert len(d) == 1 + assert [("key", "val1")] == list(d.items()) + + +def test_md_replace() -> None: + d: MultiDictStr = multidict.MultiDict() + d.add("key", "val1") + testcapi.md_replace(d, "key", "val2") + assert "val2" == d["key"] + testcapi.md_replace(d, "key", "val3") + assert "val3" == d["key"] + + +def test_md_update_from_md() -> None: + d1: MultiDictStr = multidict.MultiDict() + d1.add("key", "val1") + d2: MultiDictStr = multidict.MultiDict() + d2.add("foo", "bar") + testcapi.md_update_from_md(d1, d2, False) + assert [("key", "val1"), ("foo", "bar")] == list(d1.items()) + + +def test_md_update_from_dict() -> None: + d1: MultiDictStr = multidict.MultiDict() + d1.add("key", "val1") + testcapi.md_update_from_dict(d1, {"foo": "bar"}, False) + assert [("key", "val1"), ("foo", "bar")] == list(d1.items()) + + +def test_md_update_from_seq() -> None: + d1: MultiDictStr = multidict.MultiDict() + testcapi.md_update_from_seq(d1, [("key", "val1"), ("foo", "bar")], False) + assert [("key", "val1"), ("foo", "bar")] == list(d1.items()) + + +def test_md_equals_1() -> None: + d: MultiDictStr = multidict.MultiDict([("key", "val1")]) + assert testcapi.md_equals(d, multidict.MultiDict([("key", "val1")])) + assert not testcapi.md_equals(d, multidict.MultiDict([("key", "val2")])) + assert testcapi.md_equals(d, {"key": "val1"}) + assert not testcapi.md_equals(d, {"key": "not it"}) diff --git a/tests/test_mutable_multidict.py b/tests/test_mutable_multidict.py index 671ba7130..85997e3a9 100644 --- a/tests/test_mutable_multidict.py +++ b/tests/test_mutable_multidict.py @@ -141,7 +141,6 @@ def test_del( del d["key"] assert d == {"foo": "bar"} assert list(d.items()) == [("foo", "bar")] - with pytest.raises(KeyError, match="key"): del d["key"]