Skip to content

Commit 342fdca

Browse files
committed
CAPI
1 parent 4bb923e commit 342fdca

14 files changed

Lines changed: 410 additions & 30 deletions

File tree

.mypy.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@ warn_unused_ignores = True
2929

3030
[mypy-test_incorrect_args]
3131
disable_error_code = arg-type, call-overload
32+
33+
[mypy-test_capi]
34+
disable_error_code = attr-defined

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ all: test
1414
lint:
1515
python -Im pre_commit run --all-files --show-diff-on-failure
1616

17-
.develop: .install-deps $(shell find multidict -type f)
17+
.develop: .install-deps $(shell find multidict -type f) $(shell find testcapi -type f)
1818
pip install -e .
19+
cd testcapi; pip install -e .
1920
@touch .develop
2021

2122
test: .develop

multidict/_multidict.c

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <Python.h>
22
#include <structmember.h>
33

4+
#include "_multilib/capsule.h"
45
#include "_multilib/dict.h"
56
#include "_multilib/hashtable.h"
67
#include "_multilib/istr.h"
@@ -10,34 +11,6 @@
1011
#include "_multilib/state.h"
1112
#include "_multilib/views.h"
1213

13-
#define MultiDict_CheckExact(state, obj) Py_IS_TYPE(obj, state->MultiDictType)
14-
#define MultiDict_Check(state, obj) \
15-
(MultiDict_CheckExact(state, obj) || \
16-
PyObject_TypeCheck(obj, state->MultiDictType))
17-
#define CIMultiDict_CheckExact(state, obj) \
18-
Py_IS_TYPE(obj, state->CIMultiDictType)
19-
#define CIMultiDict_Check(state, obj) \
20-
(CIMultiDict_CheckExact(state, obj) || \
21-
PyObject_TypeCheck(obj, state->CIMultiDictType))
22-
#define AnyMultiDict_Check(state, obj) \
23-
(MultiDict_CheckExact(state, obj) || \
24-
CIMultiDict_CheckExact(state, obj) || \
25-
PyObject_TypeCheck(obj, state->MultiDictType))
26-
#define MultiDictProxy_CheckExact(state, obj) \
27-
Py_IS_TYPE(obj, state->MultiDictProxyType)
28-
#define MultiDictProxy_Check(state, obj) \
29-
(MultiDictProxy_CheckExact(state, obj) || \
30-
PyObject_TypeCheck(obj, state->MultiDictProxyType))
31-
#define CIMultiDictProxy_CheckExact(state, obj) \
32-
Py_IS_TYPE(obj, state->CIMultiDictProxyType)
33-
#define CIMultiDictProxy_Check(state, obj) \
34-
(CIMultiDictProxy_CheckExact(state, obj) || \
35-
PyObject_TypeCheck(obj, state->CIMultiDictProxyType))
36-
#define AnyMultiDictProxy_Check(state, obj) \
37-
(MultiDictProxy_CheckExact(state, obj) || \
38-
CIMultiDictProxy_CheckExact(state, obj) || \
39-
PyObject_TypeCheck(obj, state->MultiDictProxyType))
40-
4114
/******************** Internal Methods ********************/
4215

4316
static inline PyObject *
@@ -1520,6 +1493,15 @@ module_exec(PyObject *mod)
15201493
goto fail;
15211494
}
15221495

1496+
PyObject *capsule = new_capsule(state);
1497+
if (capsule == NULL) {
1498+
goto fail;
1499+
}
1500+
1501+
if (PyModule_Add(mod, MultiDict_CAPI_NAME, capsule) < 0) {
1502+
goto fail;
1503+
}
1504+
15231505
return 0;
15241506
fail:
15251507
Py_CLEAR(tpl);

multidict/_multilib/capsule.h

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#ifndef _MULTIDICT_CAPSULE_H
2+
#define _MULTIDICT_CAPSULE_H
3+
4+
#ifdef __cplusplus
5+
extern "C" {
6+
#endif
7+
8+
#define MULTIDICT_IMPL
9+
10+
#include "../multidict_api.h"
11+
#include "dict.h"
12+
#include "hashtable.h"
13+
#include "state.h"
14+
15+
inline static void
16+
_invalid_type()
17+
{
18+
PyErr_SetString(PyExc_TypeError, "self should be a MultiDict instance");
19+
}
20+
21+
static PyTypeObject *
22+
MultiDict_GetType(void *state_)
23+
{
24+
mod_state *state = (mod_state *)state_;
25+
return (PyTypeObject *)Py_NewRef(state->MultiDictType);
26+
}
27+
28+
static PyObject *
29+
MultiDict_New(void *state_, int prealloc_size)
30+
{
31+
mod_state *state = (mod_state *)state_;
32+
MultiDictObject *md =
33+
PyObject_GC_New(MultiDictObject, state->MultiDictType);
34+
if (md == NULL) {
35+
return NULL;
36+
}
37+
if (md_init(md, state, false, prealloc_size) < 0) {
38+
Py_CLEAR(md);
39+
return NULL;
40+
}
41+
PyObject_GC_Track(md);
42+
return (PyObject *)md;
43+
}
44+
45+
static int
46+
MultiDict_Add(void *state_, PyObject *self, PyObject *key, PyObject *value)
47+
{
48+
mod_state *state = (mod_state *)state_;
49+
if (MultiDict_Check(state, self) <= 0) {
50+
_invalid_type();
51+
return -1;
52+
}
53+
return md_add((MultiDictObject *)self, key, value);
54+
}
55+
56+
static void
57+
capsule_free(MultiDict_CAPI *capi)
58+
{
59+
PyMem_Free(capi);
60+
}
61+
62+
static void
63+
capsule_destructor(PyObject *o)
64+
{
65+
MultiDict_CAPI *capi = PyCapsule_GetPointer(o, MultiDict_CAPSULE_NAME);
66+
capsule_free(capi);
67+
}
68+
69+
static PyObject *
70+
new_capsule(mod_state *state)
71+
{
72+
MultiDict_CAPI *capi =
73+
(MultiDict_CAPI *)PyMem_Malloc(sizeof(MultiDict_CAPI));
74+
if (capi == NULL) {
75+
PyErr_NoMemory();
76+
return NULL;
77+
}
78+
capi->state = state;
79+
capi->MultiDict_GetType = MultiDict_GetType;
80+
capi->MultiDict_New = MultiDict_New;
81+
capi->MultiDict_Add = MultiDict_Add;
82+
83+
PyObject *ret =
84+
PyCapsule_New(capi, MultiDict_CAPSULE_NAME, capsule_destructor);
85+
if (ret == NULL) {
86+
capsule_free(capi);
87+
}
88+
return ret;
89+
}
90+
91+
#ifdef __cplusplus
92+
}
93+
#endif
94+
95+
#endif

multidict/_multilib/state.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,34 @@ NEXT_VERSION(mod_state *state)
131131
return ++state->global_version;
132132
}
133133

134+
#define MultiDict_CheckExact(state, obj) Py_IS_TYPE(obj, state->MultiDictType)
135+
#define MultiDict_Check(state, obj) \
136+
(MultiDict_CheckExact(state, obj) || \
137+
PyObject_TypeCheck(obj, state->MultiDictType))
138+
#define CIMultiDict_CheckExact(state, obj) \
139+
Py_IS_TYPE(obj, state->CIMultiDictType)
140+
#define CIMultiDict_Check(state, obj) \
141+
(CIMultiDict_CheckExact(state, obj) || \
142+
PyObject_TypeCheck(obj, state->CIMultiDictType))
143+
#define AnyMultiDict_Check(state, obj) \
144+
(MultiDict_CheckExact(state, obj) || \
145+
CIMultiDict_CheckExact(state, obj) || \
146+
PyObject_TypeCheck(obj, state->MultiDictType))
147+
#define MultiDictProxy_CheckExact(state, obj) \
148+
Py_IS_TYPE(obj, state->MultiDictProxyType)
149+
#define MultiDictProxy_Check(state, obj) \
150+
(MultiDictProxy_CheckExact(state, obj) || \
151+
PyObject_TypeCheck(obj, state->MultiDictProxyType))
152+
#define CIMultiDictProxy_CheckExact(state, obj) \
153+
Py_IS_TYPE(obj, state->CIMultiDictProxyType)
154+
#define CIMultiDictProxy_Check(state, obj) \
155+
(CIMultiDictProxy_CheckExact(state, obj) || \
156+
PyObject_TypeCheck(obj, state->CIMultiDictProxyType))
157+
#define AnyMultiDictProxy_Check(state, obj) \
158+
(MultiDictProxy_CheckExact(state, obj) || \
159+
CIMultiDictProxy_CheckExact(state, obj) || \
160+
PyObject_TypeCheck(obj, state->MultiDictProxyType))
161+
134162
#ifdef __cplusplus
135163
}
136164
#endif

multidict/multidict_api.h

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#ifndef _MULTIDICT_API_H
2+
#define _MULTIDICT_API_H
3+
4+
#ifdef __cplusplus
5+
extern "C" {
6+
#endif
7+
8+
#include <Python.h>
9+
10+
#define MultiDict_MODULE_NAME "multidict._multidict"
11+
#define MultiDict_CAPI_NAME "CAPI"
12+
#define MultiDict_CAPSULE_NAME MultiDict_MODULE_NAME "." MultiDict_CAPI_NAME
13+
14+
typedef struct {
15+
/* N.B.
16+
17+
For the sake of backward and future compatibility,
18+
new fields should be added at the end of the structure,
19+
unused fields should be never removed.
20+
21+
Otherwise, it could lead to crashes with memory corruptions
22+
if the client is compiled with older multidict_api.h header
23+
*/
24+
25+
void *state;
26+
27+
PyTypeObject *(*MultiDict_GetType)(void *state);
28+
29+
PyObject *(*MultiDict_New)(void *state, int prealloc_size);
30+
int (*MultiDict_Add)(void *state, PyObject *self, PyObject *key,
31+
PyObject *value);
32+
} MultiDict_CAPI;
33+
34+
#ifndef MULTIDICT_IMPL
35+
36+
static inline MultiDict_CAPI *
37+
MultiDict_Import()
38+
{
39+
return (MultiDict_CAPI *)PyCapsule_Import(MultiDict_CAPSULE_NAME, 0);
40+
}
41+
42+
static inline PyTypeObject *
43+
MultiDict_GetType(MultiDict_CAPI *api)
44+
{
45+
return api->MultiDict_GetType(api->state);
46+
}
47+
48+
static inline int
49+
MultiDict_CheckExact(MultiDict_CAPI *api, PyObject *op)
50+
{
51+
PyTypeObject *type = api->MultiDict_GetType(api->state);
52+
int ret = Py_IS_TYPE(op, type);
53+
Py_DECREF(type);
54+
return ret;
55+
}
56+
57+
static inline int
58+
MultiDict_Check(MultiDict_CAPI *api, PyObject *op)
59+
{
60+
PyTypeObject *type = api->MultiDict_GetType(api->state);
61+
int ret = Py_IS_TYPE(op, type) || PyObject_TypeCheck(op, type);
62+
Py_DECREF(type);
63+
return ret;
64+
}
65+
66+
static inline PyObject *
67+
MultiDict_New(MultiDict_CAPI *api, int prealloc_size)
68+
{
69+
return api->MultiDict_New(api->state, prealloc_size);
70+
}
71+
72+
static inline int
73+
MultiDict_Add(MultiDict_CAPI *api, PyObject *self, PyObject *key,
74+
PyObject *value)
75+
{
76+
return api->MultiDict_Add(api->state, self, key, value);
77+
}
78+
79+
#endif
80+
81+
#ifdef __cplusplus
82+
}
83+
#endif
84+
85+
#endif

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
55

66
[tool.cibuildwheel]
77
test-requires = "-r requirements/pytest.txt"
8-
test-command = 'pytest -m "not leaks" --no-cov {project}/tests'
8+
test-command = 'pytest -m "not leaks and not capi" --no-cov {project}/tests'
99
# don't build PyPy wheels, install from source instead
1010
skip = "pp*"
1111
enable = ["cpython-freethreading"]

pytest.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ junit_suite_name = multidict_test_suite
5757
# A mapping of markers to their descriptions allowed in strict mode:
5858
markers =
5959
leaks: memory leak tests
60+
capi: C API tests
6061

6162
minversion = 3.8.2
6263

testcapi/pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[build-system]
2+
requires = ["setuptools >= 40"]
3+
build-backend = "setuptools.build_meta"

testcapi/setup.cfg

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[bdist_wheel]
2+
# wheels should be OS-specific:
3+
# their names must contain macOS/manulinux1/2010/2014/Windows identifiers
4+
universal = 0
5+
6+
[metadata]
7+
name = testcapi
8+
# the version doesn't matter, the helper library is never uploaded to pypi
9+
version = 0.0.0.dev0
10+
description = multidict capi test utils
11+
author = Andrew Svetlov
12+
author_email = [email protected]
13+
license = Apache 2
14+
[options]
15+
python_requires = >= 3.9
16+
packages =
17+
testcapi

0 commit comments

Comments
 (0)