Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions multidict/_multidict.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "_multilib/pythoncapi_compat.h"

#include "_multilib/capsule.h"
#include "_multilib/dict.h"
#include "_multilib/istr.h"
#include "_multilib/iter.h"
Expand Down Expand Up @@ -1506,6 +1507,32 @@ module_exec(PyObject *mod)
goto fail;
}

/*************************** CAPI ***********************************/

PyMultiDict_CAPI* _MultiDict_CAPI;
_MultiDict_CAPI->_IStrType = state->IStrType;
_MultiDict_CAPI->_MultiDictType = state->MultiDictType;
_MultiDict_CAPI->_MultiDictProxyType = state->MultiDictProxyType;
_MultiDict_CAPI->_CIMultiDictType = state->CIMultiDictType;
_MultiDict_CAPI->_CIMultiDictProxyType = state->CIMultiDictProxyType;

_MultiDict_CAPI->_MultiDict_Copy = multidict_copy;
_MultiDict_CAPI->_MultiDict_Items = multidict_items;
_MultiDict_CAPI->_MultiDict_Iter = multidict_tp_iter;
_MultiDict_CAPI->_MultiDict_Keys = multidict_keys;
_MultiDict_CAPI->_MultiDict_Values = multidict_values;
_MultiDict_CAPI->_MultiDict_Reduce = multidict_reduce;
_MultiDict_CAPI->_MultiDict_Repr = multidict_repr;
_MultiDict_CAPI->_MultiDict_Update = multidict_update;
_MultiDict_CAPI->_MultiDictProxy_Copy = multidict_proxy_copy;

PyObject* c_api_object = PyCapsule_New((void *)MultiDict_CAPI, "_multidict._C_API", NULL);

if (PyModule_AddObject(mod, "_C_API", c_api_object) < 0) {
Py_XDECREF(c_api_object);
goto fail;
}

return 0;
fail:
Py_CLEAR(tpl);
Expand Down
302 changes: 302 additions & 0 deletions multidict/_multilib/capsule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
#ifndef __MULTIDICT_CAPSULE_H__
#define __MULTIDICT_CAPSULE_H__

#include "Python.h"
#include "dict.h"

#ifdef __cplusplus
extern "C" {
#endif


// This C-API Provides Comptability to CPython & Cython.

typedef struct _multidict_capi_s {
PyTypeObject *_IStrType;

PyTypeObject *_MultiDictType;
PyTypeObject *_CIMultiDictType;
PyTypeObject *_MultiDictProxyType;
PyTypeObject *_CIMultiDictProxyType;

// multidict_copy
PyObject* (*_MultiDict_Copy)(MultiDictObject* self);

// multidict_items
PyObject* (*_MultiDict_Items)(MultiDictObject* self);

// multidict_tp_iter
PyObject* (*_MultiDict_Iter)(MultiDictObject* self);

// multidict_keys
PyObject* (*_MultiDict_Keys)(MultiDictObject* self);

// multidict_values
PyObject* (*_MultiDict_Values)(MultiDictObject* self);

// multidict_reduce
PyObject* (*_MultiDict_Reduce)(MultiDictObject* self);

// multidict_repr
PyObject* (*_MultiDict_Repr)(MultiDictObject* self);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we expose tp_repr slot?
I doubt if it is a hot path in any code; PyObject_Repr() should call the slot in a performant enough way.

Also, I'm not sure about __reduce__, but I think that .extend() is worth being listed here.

@Vizonex Vizonex Jun 9, 2025

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I think about it PyObject_Repr() could be a better approach to this.


// multidict_update
PyObject* (*_MultiDict_Update)(MultiDictObject* self, PyObject* args, PyObject* kwargs);

// multidict_proxy_copy(MultiDictProxyObject *self)
PyObject* (*_MultiDictProxy_Copy)(MultiDictProxyObject* self);

} PyMultiDict_CAPI;


static PyMultiDict_CAPI* MultiDict_CAPI;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a bad feeling about mixing two-phase init, mod_state, and this static variable.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a bad feeling about mixing two-phase init, mod_state, and this static variable.



// https://docs.python.org/3.9/extending/extending.html#using-capsules

// Most inline functions have a different calling style and need a different approch.

/************************ istr *************************/

#define PyIStrType MultiDict_CAPI->_IStrType

#define PyIStr_New(args) PyIStrType->tp_new(PyIStrType, args, NULL)

// NOTE: IStr_CheckExact(state, obj) , IStr_Check(state, obj) are already defined for us...



#define PyMultiDictType MultiDict_CAPI->_MultiDictType



/********************* MultiDict *********************/


#define MultiDict_Len(self) \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it the beginning of a public C API?
If yes, it shouldn't use macros with direct access to the multidict internals.
Say, after merging hashtable-based solution this api will be broken if a third-party compiles exactly this version but imports HT-based multidict library version.

Did I miss something?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently I started waiting for that after being told about the hash-table solution so I put this on hold, however when it's done I plan to start redoing stuff shouldn't take me that long as I found writing this all to be a lot quicker than I expected.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asvetlov When the new hash-table is finished I have a few questions that could help me with revising this more

  • Should I look into only writing C-API Capsules for all functions that require functions that are only written in _multidict.c or should I try hooking all the major components that do not have Vector-Call Styled approaches?
  • Should I start looking at this more from a CPython API Approach for the Dictionaries?
  • Would Looking at python's datetime.h be a good reference for me to use or do you know of any better approaches for it?
  • Should the Capsule just carry the mod_state as opposed to the Capsule having to carry all of these types itself?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asvetlov I also have an idea since I have a lot of time on my hands, I'm gonna close this pull request, help you finish the hash-table end and then after that I can restart this again.

pair_list_len(&self->pairs)

MultiDictObject* MultiDict_New(PyObject* args, PyObject* kwargs){
MultiDictObject* self = (MultiDictObject*) PyMultiDictType->tp_alloc(PyMultiDictType, 0);
if (PyMultiDictType->tp_init((PyObject*)self, args, kwargs) < 0){
Py_XDECREF(self);
return NULL;
}
return self;
}

#define MultiDict_GetAll(self, key, list) \
pair_list_get_all(&self->pairs, key, list)

#define MultiDict_GetOne(self, key, value) \
pair_list_get_one(&self->pairs, key, value)


PyObject* MultiDict_Get(MultiDictObject* self, PyObject *key){
PyObject* val;
if (MultiDict_GetOne(self, key, &val) < 0){
return NULL;
}
Py_INCREF(val);
return val;
}

// Adopted from CPython's approch

PyObject* MultiDict_GetWithError(MultiDictObject* self, PyObject *key){
PyObject* val = MultiDict_Get(self, key);
if (val == NULL){
PyErr_SetObject(PyExc_KeyError, key);
return NULL;
}
return val;
}


#define MultiDict_Del(self, key) \
pair_list_del(&self->pairs, key);


#define MultiDict_Contains(self, key) \
pair_list_contains(&self->pairs, key, NULL)

#define MultiDict_Add(self, key, value) \
pair_list_add(&self->pairs, key, value)


#define MultiDict_Clear(self) \
pair_list_clear(&self->pairs);


#define MultiDict_Replace(self, key, value) \
pair_list_replace(&self->pairs, key, value)

#define MultiDict_PopOne(self, key, ret_val) \
pair_list_pop_one(&self->pairs, key, ret_val)

PyObject* MultiDict_Pop(MultiDictObject *self, PyObject* key, PyObject* _default){
PyObject* ret_val;
if (MultiDict_PopOne(self, key, &ret_val) < 0) {
return NULL;
}
if (ret_val != NULL){
return ret_val;
}
if (_default != NULL){
Py_INCREF(_default);
return _default;
}
PyErr_SetObject(PyExc_KeyError, key);
return NULL;
}

#define MultiDict_PopAll(self, key, ret_val) \
pair_list_pop_all(&self->pairs, key, ret_val)

#define MultiDict_PopItem(self) \
pair_list_pop_item(&self->pairs)

#define MultiDict_SetItem(self, key, val) \
pair_list_set_default(&self->pairs, key, val)


// CAPI-Methods

#define MultiDict_Copy MultiDict_CAPI->_MultiDict_Copy
#define MultiDict_Items MultiDict_CAPI->_MultiDict_Items
#define MultiDict_Iter MultiDict_CAPI->_MultiDict_Iter
#define MultiDict_Keys MultiDict_CAPI->_MultiDict_Keys
#define MultiDict_Values MultiDict_CAPI->_MultiDict_Values
#define MultiDict_Reduce MultiDict_CAPI->_MultiDict_Reduce
#define MultiDict_Repr MultiDict_CAPI->_MultiDict_Repr
#define MultiDict_Update MultiDict_CAPI->_MultiDict_Update


/******************** CIMultiDict ********************/

#define PyCIMultiDictType MultiDict_CAPI->_CIMultiDictType

MultiDictObject* CIMultiDict_New(PyObject* args, PyObject* kwargs){
MultiDictObject* self = (MultiDictObject*) PyCIMultiDictType->tp_alloc(PyMultiDictType, 0);
if (PyCIMultiDictType->tp_init((PyObject*)self, args, kwargs) < 0){
Py_XDECREF(self);
return NULL;
}
return self;
}


#define CIMultiDict_GetAll MultiDict_GetAll
#define CIMultiDict_GetOne MultiDict_GetOne
#define CIMultiDict_Get MultiDict_Get
#define CIMultiDict_GetWithError MultiDict_GetWithError
#define CIMultiDict_Del MultiDict_Del
#define CIMultiDict_Contains MultiDict_Contains
#define CIMultiDict_Add MultiDict_Add
#define CIMultiDict_Clear MultiDict_Clear
#define CIMultiDict_Replace MultiDict_Replace
#define CIMultiDict_Len MultiDict_Len

#define CIMultiDict_Copy MultiDict_Copy
#define CIMultiDict_Items MultiDict_Items
#define CIMultiDict_Iter MultiDict_Iter
#define CIMultiDict_Keys MultiDict_Keys
#define CIMultiDict_Values MultiDict_Values
#define CIMultiDict_Reduce MultiDict_Reduce
#define CIMultiDict_Repr MultiDict_Repr
#define CIMultiDict_Update MultiDict_Update
#define CIMultiDict_SetItem MultiDict_SetItem




/******************** MultiDictProxy ********************/

#define PyMultiDictProxyType MultiDict_CAPI->_MultiDictProxyType

MultiDictProxyObject* MultiDictProxy_New(PyObject* args, PyObject* kwargs){
MultiDictProxyObject* self = (MultiDictProxyObject*) PyMultiDictProxyType->tp_alloc(PyMultiDictType, 0);
if (PyMultiDictProxyType->tp_init((PyObject*)self, args, kwargs) < 0){
Py_XDECREF(self);
return NULL;
}
return self;
}

#define MultiDictProxy_Copy(self) \
MultiDict_CAPI->_MultiDictProxy_Copy(self)

#define MultiDictProxy_GetAll(self, key, list) \
MultiDict_GetAll(self->md, key, list);

#define MultiDictProxy_GetOne(self, key, value) \
MultiDict_GetOne(self->md, key, value)

#define MultiDictProxy_Get(self, key) \
MultiDict_Get(self->md, key)

#define MultiDictProxy_Keys(self) \
MultiDict_CAPI->MultiDict_Keys(self->md)

#define MultiDictProxy_Items(self) \
MultiDict_CAPI->MultiDict_Items(self->md)

#define MultiDictProxy_Values(self) \
MultiDict_CAPI->MultiDict_Values(self->md);

#define MultiDictProxy_Len(self) \
MultiDict_Len((self->md))

#define MultiDictProxy_Iter(self) MultiDict_Iter(self->md)
#define MultiDictProxy_Reduce(self) MultiDict_Reduce(self->md)
#define MultiDictProxy_Repr(self) MultiDict_Repr(self->md)
#define MultiDictProxy_Update(self, args, kwargs) MultiDict_Update(self->md, args, kwargs)
#define MultiDictProxy_SetItem(self, key, value) MultiDict_SetItem(self->md, key, value)



/******************** CIMultiDictProxy ********************/

#define PyCIMultiDictProxyType MultiDict_CAPI->_CIMultiDictProxyType

MultiDictProxyObject* CIMultiDictProxy_New(PyObject* args, PyObject* kwargs){
MultiDictProxyObject* self = (MultiDictProxyObject*) PyCIMultiDictProxyType->tp_alloc(PyMultiDictType, 0);
if (PyCIMultiDictProxyType->tp_init((PyObject*)self, args, kwargs) < 0){
Py_XDECREF(self);
return NULL;
}
return self;
}

// Isn't it just hillarious how this can all be done with just macros?

// Unfortunately this had to be done so that cython would know how
// to typecast everything without confusing it...

#define CIMultiDictProxy_Copy MultiDictProxy_Copy
#define CIMultiDictProxy_GetAll MultiDictProxy_GetAll
#define CIMultiDictProxy_GetOne MultiDictProxy_GetOne
#define CIMultiDictProxy_Get MultiDictProxy_Get
#define CIMultiDictProxy_Keys MultiDictProxy_Keys
#define CIMultiDictProxy_Items MultiDictProxy_Items
#define CIMultiDictProxy_Values MultiDictProxy_Values
#define CIMultiDictProxy_Len MultiDictProxy_Len
#define CIMultiDictProxy_Iter MultiDictProxy_Iter
#define CIMultiDictProxy_Repr MultiDictProxy_Repr
#define CIMultiDictProxy_Update MultiDictProxy_Update


// Cython / CPython helper...
static int
import_multidict(void)
{
MultiDict_CAPI = (PyMultiDict_CAPI*)PyCapsule_Import("multidict._multidict._C_API", 0);
return (MultiDict_CAPI != NULL) ? 0 : -1;
}


#ifdef __cplusplus
};
#endif

#endif // __MULTIDICT_CAPSULE_H__