Skip to content

Conversation

@Vizonex
Copy link
Contributor

@Vizonex Vizonex commented Jun 21, 2025

What do these changes do?

First of all, thank you for giving something better to work with I really appreciate it.
This Adds a few new api calls mainly belonging to Multidict's api I had a proposed idea for an iterator but I think I might delete it and call it a day. I wasn't lazy at all about it in fact it encouraged me to keep going. I was pretty creative about exposing some functions such as MultiDicts updating each other or using Sequence and Mapping so that they could be utilized as shortcuts if needed.

Are there changes in behavior for the user?

Only the Good stuff is coming, I even wrote doxygen comments into each functions so that users would have a much better time using the C-API. Writing the pytest stuff is on my todo list I haven't done it yet but maybe sometime tomorrow I will finish this.

Related issue number

Checklist

  • I think the code is well written
  • Unit tests for the changes exist
  • Documentation reflects the changes

@Vizonex Vizonex requested a review from asvetlov as a code owner June 21, 2025 01:51
@Vizonex Vizonex requested a review from webknjaz as a code owner June 21, 2025 01:54
@psf-chronographer psf-chronographer bot added the bot:chronographer:provided There is a change note present in this PR label Jun 21, 2025
@Vizonex
Copy link
Contributor Author

Vizonex commented Jun 21, 2025

Probably a good thing as well that I chose this route instead of the one I was going down I didn't feel as confused with what I was doing this time.

@Vizonex
Copy link
Contributor Author

Vizonex commented Jun 21, 2025

After I sleep I will need to investigate This error I get when running pytest

(multidict) PS ...> pytest tests/test_capi.py
==================================================================== test session starts =====================================================================
platform win32 -- Python 3.10.18, pytest-8.4.0, pluggy-1.6.0
codspeed: 3.2.0 (disabled, mode: walltime, timer_resolution: 100.0ns)
rootdir: [...]
configfile: pytest.ini
plugins: cov-6.1.0, codspeed-3.2.0
collected 3 items

tests\test_capi.py Windows fatal exception: access violation

The other 2 tests work just fine except for md_new(0).

@Vizonex
Copy link
Contributor Author

Vizonex commented Jun 21, 2025

I woke up time to finish this.

@Vizonex
Copy link
Contributor Author

Vizonex commented Jun 21, 2025

@asvetlov I solved the problem with MultiDict_New Adding a reference count by 1 stops the segfault.

@Vizonex
Copy link
Contributor Author

Vizonex commented Jun 21, 2025

When I'm done testing the C stuff I will redo the Cython API in the same format as testcapi to keep things organized.

@Vizonex Vizonex changed the title New MultiDict C-API functions New MultiDict C-API functions And Added Documentation Jun 21, 2025
@Vizonex
Copy link
Contributor Author

Vizonex commented Jun 21, 2025

there seems to be some bugs with MultiDict_Get where instead of returning None it returns NULL and says it didn't throw an exception either. Something confusing might be happening in the background.

Copy link
Member

@asvetlov asvetlov left a comment

Choose a reason for hiding this comment

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

Looks good, thanks!
Please let me carefully re-read the source again a little later before the merging.

cython code
"""

return str(pathlib.Path(__file__).parent)
Copy link
Member

Choose a reason for hiding this comment

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

We have two options here.

  1. Drop the method, document import multidict, include_path=multidict.__path__ as I did in testcapi/setup.py
  2. Keep the function but return bare __path__; pathlib usage is redundant here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@asvetlov There was an issue when I tried compiling it but it may have been a configuration problem on my end so met let me go back and revert this change.

@Vizonex
Copy link
Contributor Author

Vizonex commented Jun 21, 2025

@asvetlov I'm going to finish writing all the tests should we worry about patching up the bugs now or later?

@Vizonex
Copy link
Contributor Author

Vizonex commented Jun 21, 2025

Only one thing is left on the table now will be debugging.

SKIPPED [1] tests\test_capi.py:69: SystemError: <built-in function md_get> returned NULL without setting an exception
FAILED tests/test_capi.py::test_md_get_all - TypeError: self should be a MultiDict instance
FAILED tests/test_capi.py::test_md_popone_raise - Failed: DID NOT RAISE <class 'KeyError'>
FAILED tests/test_capi.py::test_md_popall_key_error - Failed: DID NOT RAISE <class 'KeyError'>
FAILED tests/test_capi.py::test_md_update_from_md - TypeError: md_replace should be called with md, key and value
FAILED tests/test_capi.py::test_md_update_from_dict - TypeError: md_update_from_md should be called with md, other, and update
FAILED tests/test_capi.py::test_md_update_from_seq - TypeError: md_update_from_md should be called with md, other, and update
FAILED tests/test_capi.py::test_md_equals_1 - SystemError: <built-in function md_equals> returned NULL without setting an exception

@Vizonex Vizonex requested a review from asvetlov June 21, 2025 19:58
@Vizonex
Copy link
Contributor Author

Vizonex commented Jun 21, 2025

@asvetlov Doesn't seem like I can merge this is there anything I'm missing that you would suggest I add before I start making more things later so that I don't overburden anyone?

@Vizonex
Copy link
Contributor Author

Vizonex commented Jun 22, 2025

While I wait let me fill out the documentation for these new functions we added to the capi since I figured out how to do so.
https://www.sphinx-doc.org/en/master/usage/domains/c.html

Copy link
Member

@asvetlov asvetlov left a comment

Choose a reason for hiding this comment

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

Hey, I've left a bunch of comments.

Let's process them.

static int
MultiDict_Clear(void* state_, PyObject* self)
{
// TODO: Macro for repeated steps being done?
Copy link
Member

Choose a reason for hiding this comment

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

Maybe the macro is a good idea, please feel free to try

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will I moved to #1190 but I am making sure to read over all your suggestions real quick before I move on...

return -1;
}
if (md_del((MultiDictObject*)self, key) < 0) {
PyErr_SetObject(PyExc_KeyError, key);
Copy link
Member

Choose a reason for hiding this comment

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

This line converts all errors (for example, TypeError if passed key is not a string) to KeyError; it is not good.

Better to just return a result of md_del() as is; md_del() already sets KeyError if the key is not found.


static PyTypeObject *
MultiDict_GetType(void *state_)
/// @brief Gets the multidict type object
Copy link
Member

Choose a reason for hiding this comment

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

Regarding Doxigen comments -- I'm not sure if we need it.

We should either set up a Doxygen docs generator, as we already do for Sphinx docs, or we should drop these comments.

Without an automatic generator/checker, the comments eventually will go out of sync with the code itself; misleading comments are harmful.

Taking into account that we use Sphinx already, maybe better to document C API in sphinx also?

Aha, I see the comment that you already started Sphinx docs. In this case, Doxigen comments don't make sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Aha, I see the comment that you already started Sphinx docs. In this case, Doxigen comments don't make sense.

I documented everything as I was writing everything so that moving things around would be less painful to move but I think we still need a readable format to have in the api header file incase some user decides to hover over the function and wonder what it's doing immediately. Msvc's C/C++ Extension seems to autogenerate these comments and allows me to see the definitions when I hover my mouse of them. However, I am willing to remove delete or reformat them all since I'm done moving the current sphinx documentation over.

return NULL;
}
PyObject* ret = NULL;
if (md_get_one((MultiDictObject*)self, key, &ret) < 0) {
Copy link
Member

Choose a reason for hiding this comment

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

md_get_one() is designed to quickly figure out if the key exists or not without analyzing Python exception.

PyErr_ExceptionMatches() + PyErr_Clear() is much slower than if (ret == NULL).

I think MultiDict_GetOne() should follow PyDict_GetItemRef.
As I see, now md_get_one returns -1 if an exception was raised, 0 otherwise. Maybe better return 0 if not found, 1 if found. Let's do it in a separate PR against the master branch; the fix is not directly related to CAPI itself.

Copy link
Contributor Author

@Vizonex Vizonex Jun 22, 2025

Choose a reason for hiding this comment

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

As I see, now md_get_one returns -1 if an exception was raised, 0 otherwise. Maybe better return 0 if not found, 1 if found. Let's do it in a separate PR against the master branch; the fix is not directly related to CAPI itself.

@asvetlov Sure, I would be willing to work on that when I'm done making fixes into this branch shouldn't be too difficult to port something like that into #1190 and another branch

/// @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)
Copy link
Member

Choose a reason for hiding this comment

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

Do we need MultiDict_Get() if GetOne()/GetAll() also provided?
In python API, MultiDict.get() exists because multidict implements collections.abc.Mapping interface.
In C, we do not have such a contract.
If the function doesn't exist, nobody should learn how the function works.

_invalid_type();
return -1;
}
if (PyMapping_Check(other)) {
Copy link
Member

Choose a reason for hiding this comment

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

MultiDict_Check should go first. PyMapping_Check succeeds for multidict objects, which leads to a correct but slow path.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alright let me make sure it has that in #1190 then...

/// @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)
Copy link
Member

Choose a reason for hiding this comment

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

I doubt if the function should be exposed; standard Python containers (dict, list, tuple, set) have no comparison functions, PyObject_RichCompare() does its job well.
The only exception is str, which has a rich set of comparison functions to work with objects, ascii and utf8 strings.

*/

void *state;
void* state;
Copy link
Member

Choose a reason for hiding this comment

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

Cython branch will be setup once the C-API is done but I will keep it under the same format as the C-API and the module, the setup will be configured to same way as testcapi only this time I will name it testcythonapi.
Minor: testcapi could have two modules, one for C api test helpers and another one for Cython. Maybe we can even provide the same API by both wrappers to run the same api tests for both implementations, like we run the same tests suite for pure python and C extensions.

{
mod_state *state = get_mod_state(self);
return MultiDict_New(state->capi, 0);
return Py_NewRef(MultiDict_New(state->capi, 0));
Copy link
Member

Choose a reason for hiding this comment

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

This is suspicious.
MultiDict_New should return a new reference; double incref is not needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removing Py_NewRef here triggers Segfault when running in pytest tests\test_capi.py

tests\test_capi.py Windows fatal exception: access violation

Perhaps maybe I should move NewRef into Multidict_New instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Or Maybe Py_INCREF?


#define RETURN_NULL_OR_NEWREF(ITEM) \
PyObject* REF = ITEM; \
return (REF != NULL) ? Py_NewRef(REF) : NULL
Copy link
Member

Choose a reason for hiding this comment

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

Why newref here? It looks like a memory leak generation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My reasoning behind this was that not doing so would cause the program to segfault.

@asvetlov asvetlov merged commit 8f94d17 into aio-libs:capi Jun 22, 2025
5 checks passed
@asvetlov
Copy link
Member

Sorry, I've merged the PR by accident; I'll revert the merge.
PR is not ready yet.

@asvetlov
Copy link
Member

I've reverted the merge into aio-libs:capi but I have no idea how to change the status from 'merged' back to 'in-progress', sorry.

@Vizonex
Copy link
Contributor Author

Vizonex commented Jun 22, 2025

I've reverted the merge into aio-libs:capi but I have no idea how to change the status from 'merged' back to 'in-progress', sorry.

@asvetlov It's fine I'll try continuing where I left off in a new branch by just re-forking everything off again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:chronographer:provided There is a change note present in this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants