Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 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
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ all: test
lint:
python -Im pre_commit run --all-files --show-diff-on-failure

.develop: .install-deps $(shell find multidict -type f)
.develop: .install-deps $(shell find multidict -type f) $(shell find testcapi -type f)
pip install -e .
cd testcapi; pip install -e .
@touch .develop

test: .develop
Expand Down
38 changes: 10 additions & 28 deletions multidict/_multidict.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <Python.h>
#include <structmember.h>

#include "_multilib/capsule.h"
#include "_multilib/dict.h"
#include "_multilib/hashtable.h"
#include "_multilib/istr.h"
Expand All @@ -10,34 +11,6 @@
#include "_multilib/state.h"
#include "_multilib/views.h"

#define MultiDict_CheckExact(state, obj) Py_IS_TYPE(obj, state->MultiDictType)
#define MultiDict_Check(state, obj) \
(MultiDict_CheckExact(state, obj) || \
PyObject_TypeCheck(obj, state->MultiDictType))
#define CIMultiDict_CheckExact(state, obj) \
Py_IS_TYPE(obj, state->CIMultiDictType)
#define CIMultiDict_Check(state, obj) \
(CIMultiDict_CheckExact(state, obj) || \
PyObject_TypeCheck(obj, state->CIMultiDictType))
#define AnyMultiDict_Check(state, obj) \
(MultiDict_CheckExact(state, obj) || \
CIMultiDict_CheckExact(state, obj) || \
PyObject_TypeCheck(obj, state->MultiDictType))
#define MultiDictProxy_CheckExact(state, obj) \
Py_IS_TYPE(obj, state->MultiDictProxyType)
#define MultiDictProxy_Check(state, obj) \
(MultiDictProxy_CheckExact(state, obj) || \
PyObject_TypeCheck(obj, state->MultiDictProxyType))
#define CIMultiDictProxy_CheckExact(state, obj) \
Py_IS_TYPE(obj, state->CIMultiDictProxyType)
#define CIMultiDictProxy_Check(state, obj) \
(CIMultiDictProxy_CheckExact(state, obj) || \
PyObject_TypeCheck(obj, state->CIMultiDictProxyType))
#define AnyMultiDictProxy_Check(state, obj) \
(MultiDictProxy_CheckExact(state, obj) || \
CIMultiDictProxy_CheckExact(state, obj) || \
PyObject_TypeCheck(obj, state->MultiDictProxyType))

/******************** Internal Methods ********************/

static inline PyObject *
Expand Down Expand Up @@ -1520,6 +1493,15 @@ module_exec(PyObject *mod)
goto fail;
}

PyObject *capsule = new_capsule(state);
if (capsule == NULL) {
goto fail;
}

if (PyModule_Add(mod, MultiDict_CAPI_NAME, capsule) < 0) {
goto fail;
}

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

#ifdef __cplusplus
extern "C" {
#endif

#define MULTIDICT_IMPL

#include "../multidict_api.h"
#include "dict.h"
#include "hashtable.h"
#include "state.h"

inline static void
_invalid_type()
{
PyErr_SetString(PyExc_TypeError, "self should be a MultiDict instance");
}

static PyTypeObject *
MultiDict_GetType(void *state_)
{
mod_state *state = (mod_state *)state_;
return (PyTypeObject *)Py_NewRef(state->MultiDictType);
}

static PyObject *
MultiDict_New(void *state_, int prealloc_size)
{
mod_state *state = (mod_state *)state_;
MultiDictObject *md =
PyObject_GC_New(MultiDictObject, state->MultiDictType);
if (md == NULL) {
return NULL;
}
if (md_init(md, state, false, prealloc_size) < 0) {
Py_CLEAR(md);
return NULL;
}
PyObject_GC_Track(md);
return (PyObject *)md;
}

static int
MultiDict_Add(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_add((MultiDictObject *)self, key, value);
}

static void
capsule_free(MultiDict_CAPI *capi)
{
PyMem_Free(capi);
}

static void
capsule_destructor(PyObject *o)
{
MultiDict_CAPI *capi = PyCapsule_GetPointer(o, MultiDict_CAPSULE_NAME);
capsule_free(capi);
}

static PyObject *
new_capsule(mod_state *state)
{
MultiDict_CAPI *capi =
(MultiDict_CAPI *)PyMem_Malloc(sizeof(MultiDict_CAPI));
if (capi == NULL) {
PyErr_NoMemory();
return NULL;
}
capi->state = state;
capi->MultiDict_GetType = MultiDict_GetType;
capi->MultiDict_New = MultiDict_New;
capi->MultiDict_Add = MultiDict_Add;

PyObject *ret =
PyCapsule_New(capi, MultiDict_CAPSULE_NAME, capsule_destructor);
if (ret == NULL) {
capsule_free(capi);
}
return ret;
}

#ifdef __cplusplus
}
#endif

#endif
28 changes: 28 additions & 0 deletions multidict/_multilib/state.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,34 @@ NEXT_VERSION(mod_state *state)
return ++state->global_version;
}

#define MultiDict_CheckExact(state, obj) Py_IS_TYPE(obj, state->MultiDictType)
#define MultiDict_Check(state, obj) \
(MultiDict_CheckExact(state, obj) || \
PyObject_TypeCheck(obj, state->MultiDictType))
#define CIMultiDict_CheckExact(state, obj) \
Py_IS_TYPE(obj, state->CIMultiDictType)
#define CIMultiDict_Check(state, obj) \
(CIMultiDict_CheckExact(state, obj) || \
PyObject_TypeCheck(obj, state->CIMultiDictType))
#define AnyMultiDict_Check(state, obj) \
(MultiDict_CheckExact(state, obj) || \
CIMultiDict_CheckExact(state, obj) || \
PyObject_TypeCheck(obj, state->MultiDictType))
#define MultiDictProxy_CheckExact(state, obj) \
Py_IS_TYPE(obj, state->MultiDictProxyType)
#define MultiDictProxy_Check(state, obj) \
(MultiDictProxy_CheckExact(state, obj) || \
PyObject_TypeCheck(obj, state->MultiDictProxyType))
#define CIMultiDictProxy_CheckExact(state, obj) \
Py_IS_TYPE(obj, state->CIMultiDictProxyType)
#define CIMultiDictProxy_Check(state, obj) \
(CIMultiDictProxy_CheckExact(state, obj) || \
PyObject_TypeCheck(obj, state->CIMultiDictProxyType))
#define AnyMultiDictProxy_Check(state, obj) \
(MultiDictProxy_CheckExact(state, obj) || \
CIMultiDictProxy_CheckExact(state, obj) || \
PyObject_TypeCheck(obj, state->MultiDictProxyType))

#ifdef __cplusplus
}
#endif
Expand Down
85 changes: 85 additions & 0 deletions multidict/multidict_api.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#ifndef _MULTIDICT_API_H
#define _MULTIDICT_API_H

#ifdef __cplusplus
extern "C" {
#endif

#include <Python.h>

#define MultiDict_MODULE_NAME "multidict._multidict"
#define MultiDict_CAPI_NAME "CAPI"
#define MultiDict_CAPSULE_NAME MultiDict_MODULE_NAME "." MultiDict_CAPI_NAME

typedef struct {
/* N.B.

For the sake of backward and future compatibility,
new fields should be added at the end of the structure,
unused fields should be never removed.

Otherwise, it could lead to crashes with memory corruptions
if the client is compiled with older multidict_api.h header
*/

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);
} MultiDict_CAPI;

#ifndef MULTIDICT_IMPL

static inline MultiDict_CAPI *
MultiDict_Import()
{
return (MultiDict_CAPI *)PyCapsule_Import(MultiDict_CAPSULE_NAME, 0);
}

static inline PyTypeObject *
MultiDict_GetType(MultiDict_CAPI *api)
{
return api->MultiDict_GetType(api->state);
}

static inline int
MultiDict_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
MultiDict_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 *
MultiDict_New(MultiDict_CAPI *api, int prealloc_size)
{
return api->MultiDict_New(api->state, prealloc_size);
}

static inline int
MultiDict_Add(MultiDict_CAPI *api, PyObject *self, PyObject *key,
PyObject *value)
{
return api->MultiDict_Add(api->state, self, key, value);
}

#endif

#ifdef __cplusplus
}
#endif

#endif
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"

[tool.cibuildwheel]
test-requires = "-r requirements/pytest.txt"
test-command = 'pytest -m "not leaks" --no-cov {project}/tests'
test-command = 'pytest -m "not leaks and not capi" --no-cov {project}/tests'
# don't build PyPy wheels, install from source instead
skip = "pp*"
enable = ["cpython-freethreading"]
Expand Down
1 change: 1 addition & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ junit_suite_name = multidict_test_suite
# A mapping of markers to their descriptions allowed in strict mode:
markers =
leaks: memory leak tests
capi: C API tests

minversion = 3.8.2

Expand Down
3 changes: 3 additions & 0 deletions testcapi/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools >= 40"]
build-backend = "setuptools.build_meta"
17 changes: 17 additions & 0 deletions testcapi/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[bdist_wheel]
# wheels should be OS-specific:
# their names must contain macOS/manulinux1/2010/2014/Windows identifiers
universal = 0
Comment thread
asvetlov marked this conversation as resolved.

[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 = [email protected]
license = Apache 2
[options]
python_requires = >= 3.9
packages =
testcapi
24 changes: 24 additions & 0 deletions testcapi/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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__,
),
]

if not NO_EXTENSIONS:
print("*********************")
print("* Accelerated build *")
print("*********************")
setup(ext_modules=extensions)
else:
print("*********************")
print("* Pure Python build *")
print("*********************")
setup()
4 changes: 4 additions & 0 deletions testcapi/testcapi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
try:
from testcapi._api import * # noqa
except ImportError:
Comment thread Dismissed
pass
Loading
Loading