Skip to content

Commit 2a6e20c

Browse files
authored
Make is C Extension (#96)
1 parent c344332 commit 2a6e20c

6 files changed

Lines changed: 306 additions & 88 deletions

File tree

multidict/_istr.c

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
#include "Python.h"
2+
#include "structmember.h"
3+
4+
PyDoc_STRVAR(istr__doc__, "istr class implementation");
5+
6+
/* We link this module statically for convenience. If compiled as a shared
7+
library instead, some compilers don't allow addresses of Python objects
8+
defined in other libraries to be used in static initializers here. The
9+
DEFERRED_ADDRESS macro is used to tag the slots where such addresses
10+
appear; the module init function must fill in the tagged slots at runtime.
11+
The argument is for documentation -- the macro ignores it.
12+
*/
13+
#define DEFERRED_ADDRESS(ADDR) 0
14+
15+
16+
typedef struct {
17+
PyUnicodeObject str;
18+
PyObject * canonical;
19+
} istrobject;
20+
21+
typedef struct {
22+
PyObject *title;
23+
PyObject *emptystr;
24+
PyObject *emptydict;
25+
} ModData;
26+
27+
static struct PyModuleDef _istrmodule;
28+
static PyTypeObject istr_type;
29+
30+
static ModData *
31+
modstate(PyObject *mod)
32+
{
33+
return (ModData*)PyModule_GetState(mod);
34+
}
35+
36+
static ModData *
37+
global_state(void)
38+
{
39+
return modstate(PyState_FindModule(&_istrmodule));
40+
}
41+
42+
43+
44+
static PyObject *
45+
istr_title(istrobject *self, PyObject *args)
46+
{
47+
if (!PyArg_ParseTuple(args, ":title"))
48+
return NULL;
49+
Py_INCREF(self);
50+
return (PyObject*)self;
51+
}
52+
53+
static PyObject *
54+
istr_str(istrobject *self)
55+
{
56+
Py_INCREF(self->canonical);
57+
return self->canonical;
58+
}
59+
60+
static PyMethodDef istr_methods[] = {
61+
{"title", (PyCFunction)istr_title, METH_VARARGS,
62+
PyDoc_STR("title()")},
63+
{NULL, NULL},
64+
};
65+
66+
67+
void istr_dealloc(istrobject *self)
68+
{
69+
Py_XDECREF(self->canonical);
70+
}
71+
72+
static PyObject *
73+
istr_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
74+
{
75+
PyObject *x = NULL;
76+
static char *kwlist[] = {"object", "encoding", "errors", 0};
77+
char *encoding = NULL;
78+
char *errors = NULL;
79+
PyObject *s = NULL;
80+
PyObject *tmp = NULL;
81+
PyObject * new_args = NULL;
82+
PyObject * ret = NULL;
83+
84+
ModData * state = global_state();
85+
86+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oss:str",
87+
kwlist, &x, &encoding, &errors))
88+
return NULL;
89+
if (x == NULL) {
90+
s = state->emptystr;
91+
Py_INCREF(s);
92+
}
93+
else if (PyObject_IsInstance(x, (PyObject*)&istr_type)) {
94+
Py_INCREF(x);
95+
return x;
96+
}
97+
else {
98+
if (encoding == NULL && errors == NULL) {
99+
tmp = PyObject_Str(x);
100+
} else {
101+
tmp = PyUnicode_FromEncodedObject(x, encoding, errors);
102+
}
103+
if (!tmp) {
104+
goto finish;
105+
}
106+
s = PyObject_CallMethodObjArgs(tmp, state->title, NULL);
107+
}
108+
if (!s)
109+
goto finish;
110+
111+
new_args = PyTuple_Pack(1, s);
112+
if (!new_args) {
113+
goto finish;
114+
}
115+
ret = PyUnicode_Type.tp_new(type, new_args, state->emptydict);
116+
if (!ret) {
117+
goto finish;
118+
}
119+
((istrobject*)ret)->canonical = s;
120+
s = NULL; /* the reference is stollen by .canonical */
121+
finish:
122+
Py_XDECREF(tmp);
123+
Py_XDECREF(s);
124+
Py_XDECREF(new_args);
125+
return ret;
126+
}
127+
128+
static PyTypeObject istr_type = {
129+
PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
130+
"multidict._istr.istr",
131+
sizeof(istrobject),
132+
0,
133+
(destructor)istr_dealloc, /* tp_dealloc */
134+
0, /* tp_print */
135+
0, /* tp_getattr */
136+
0, /* tp_setattr */
137+
0, /* tp_reserved */
138+
0, /* tp_repr */
139+
0, /* tp_as_number */
140+
0, /* tp_as_sequence */
141+
0, /* tp_as_mapping */
142+
0, /* tp_hash */
143+
0, /* tp_call */
144+
(reprfunc)istr_str, /* tp_str */
145+
0, /* tp_getattro */
146+
0, /* tp_setattro */
147+
0, /* tp_as_buffer */
148+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_UNICODE_SUBCLASS,
149+
/* tp_flags */
150+
0, /* tp_doc */
151+
0, /* tp_traverse */
152+
0, /* tp_clear */
153+
0, /* tp_richcompare */
154+
0, /* tp_weaklistoffset */
155+
0, /* tp_iter */
156+
0, /* tp_iternext */
157+
istr_methods, /* tp_methods */
158+
0, /* tp_members */
159+
0, /* tp_getset */
160+
DEFERRED_ADDRESS(&PyUnicode_Type), /* tp_base */
161+
0, /* tp_dict */
162+
0, /* tp_descr_get */
163+
0, /* tp_descr_set */
164+
0, /* tp_dictoffset */
165+
0, /* tp_init */
166+
0, /* tp_alloc */
167+
(newfunc)istr_new, /* tp_new */
168+
};
169+
170+
171+
static int mod_clear(PyObject *m)
172+
{
173+
Py_CLEAR(modstate(m)->title);
174+
Py_CLEAR(modstate(m)->emptystr);
175+
Py_CLEAR(modstate(m)->emptydict);
176+
return 0;
177+
}
178+
179+
180+
static struct PyModuleDef _istrmodule = {
181+
PyModuleDef_HEAD_INIT,
182+
"multidict._istr",
183+
istr__doc__,
184+
sizeof(ModData),
185+
NULL, /* m_methods */
186+
NULL, /* m_reload */
187+
NULL, /* m_traverse */
188+
mod_clear, /* m_clear */
189+
NULL /* m_free */
190+
};
191+
192+
193+
PyObject* PyInit__istr(void)
194+
{
195+
PyObject * tmp;
196+
PyObject *mod;
197+
198+
mod = PyState_FindModule(&_istrmodule);
199+
if (mod) {
200+
Py_INCREF(mod);
201+
return mod;
202+
}
203+
204+
istr_type.tp_base = &PyUnicode_Type;
205+
if (PyType_Ready(&istr_type) < 0) {
206+
return NULL;
207+
}
208+
209+
mod = PyModule_Create(&_istrmodule);
210+
if (!mod) {
211+
return NULL;
212+
}
213+
tmp = PyUnicode_FromString("title");
214+
if (!tmp) {
215+
goto err;
216+
}
217+
modstate(mod)->title = tmp;
218+
tmp = PyUnicode_New(0, 0);
219+
if (!tmp) {
220+
goto err;
221+
}
222+
modstate(mod)->emptystr = tmp;
223+
tmp = PyUnicode_FromString("title");
224+
if(!tmp) {
225+
goto err;
226+
}
227+
modstate(mod)->title = tmp;
228+
229+
Py_INCREF(&istr_type);
230+
if (PyModule_AddObject(mod, "istr", (PyObject *)&istr_type) < 0)
231+
goto err;
232+
233+
return mod;
234+
err:
235+
Py_DECREF(mod);
236+
return NULL;
237+
}

multidict/_multidict.pyx

Lines changed: 8 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,46 +5,20 @@ from collections import abc
55
from collections.abc import Iterable, Set
66
from operator import itemgetter
77

8+
from cpython.object cimport PyObject_Str
89

9-
cdef object _marker = object()
10-
11-
12-
class istr(str):
13-
14-
"""Case insensitive str."""
15-
16-
__is_istr__ = True
17-
18-
def __new__(cls, val='',
19-
encoding=sys.getdefaultencoding(), errors='strict'):
20-
if getattr(val, '__is_istr__', False):
21-
# Faster than instance check
22-
return val
23-
if isinstance(val, (bytes, bytearray, memoryview)):
24-
val = str(val, encoding, errors)
25-
elif isinstance(val, str):
26-
pass
27-
else:
28-
val = str(val)
29-
val = val.title()
30-
ret = str.__new__(cls, val)
31-
ret._canonical = val
32-
return ret
33-
34-
def title(self):
35-
return self
10+
from ._istr import istr
3611

12+
cdef object _marker = object()
3713

3814
upstr = istr # for relaxing backward compatibility problems
15+
cdef object _istr = istr
3916

4017

4118
def getversion(_Base md):
4219
return md._impl._version
4320

4421

45-
cdef object _istr = istr
46-
47-
4822
cdef _eq(self, other):
4923
cdef int is_left_base, is_right_base
5024
cdef Py_ssize_t i, l
@@ -117,7 +91,7 @@ cdef class _Base:
11791
if typ is str:
11892
return <str>s
11993
elif typ is _istr:
120-
return <str>(s._canonical)
94+
return PyObject_Str(s)
12195
else:
12296
return str(s)
12397

@@ -279,7 +253,7 @@ cdef class CIMultiDictProxy(MultiDictProxy):
279253
if typ is str:
280254
return <str>(s.title())
281255
elif type(s) is _istr:
282-
return <str>(s._canonical)
256+
return PyObject_Str(s)
283257
return s.title()
284258

285259
def copy(self):
@@ -295,7 +269,7 @@ cdef str _str(key):
295269
if typ is str:
296270
return <str>key
297271
if typ is _istr:
298-
return <str>(key._canonical)
272+
return PyObject_Str(key)
299273
elif issubclass(typ, str):
300274
return str(key)
301275
else:
@@ -564,7 +538,7 @@ cdef class CIMultiDict(MultiDict):
564538
if typ is str:
565539
return <str>(s.title())
566540
elif type(s) is _istr:
567-
return <str>(s._canonical)
541+
return PyObject_Str(s)
568542
return s.title()
569543

570544

multidict/_multidict_py.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@ def __new__(cls, val='',
1616
if getattr(val, '__is_istr__', False):
1717
# Faster than instance check
1818
return val
19-
if isinstance(val, (bytes, bytearray, memoryview)):
20-
val = str(val, encoding, errors)
21-
elif isinstance(val, str):
19+
if type(val) is str:
2220
pass
2321
else:
2422
val = str(val)

setup.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
if USE_CYTHON:
2828
extensions = cythonize(extensions)
2929

30+
extensions.append(Extension('multidict._istr',
31+
['multidict/_istr.c']))
32+
3033

3134
class BuildFailed(Exception):
3235
pass

0 commit comments

Comments
 (0)