1 /* -*- Mode: C; c-basic-offset: 4 -*-
2 * vim: tabstop=4 shiftwidth=4 expandtab
3 *
4 * Copyright (C) 2015 Christoph Reiter <reiter.christoph@gmail.com>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <Python.h>
21 #include <glib.h>
22 #include "pygi-resulttuple.h"
23 #include "pygi-util.h"
24
25 static char repr_format_key[] = "__repr_format";
26 static char tuple_indices_key[] = "__tuple_indices";
27
28 #define PYGI_USE_FREELIST
29
30 #ifdef PYPY_VERSION
31 #undef PYGI_USE_FREELIST
32 #endif
33
34 #ifdef PYGI_USE_FREELIST
35 /* A free list similar to the one used for the CPython tuple. Difference
36 * is that zero length tuples aren't cached (as we don't need them)
37 * and that the freelist is smaller as we don't free it with the cyclic GC
38 * as CPython does. This wastes 21kB max.
39 */
40 #define PyGIResultTuple_MAXSAVESIZE 10
41 #define PyGIResultTuple_MAXFREELIST 100
42 static PyObject *free_list[PyGIResultTuple_MAXSAVESIZE];
43 static int numfree[PyGIResultTuple_MAXSAVESIZE];
44 #endif
45
46 PYGI_DEFINE_TYPE ("gi._gi.ResultTuple", PyGIResultTuple_Type, PyTupleObject)
47
48 /**
49 * ResultTuple.__repr__() implementation.
50 * Takes the _ResultTuple.__repr_format format string and applies the tuple
51 * values to it.
52 */
53 static PyObject*
resulttuple_repr(PyObject * self)54 resulttuple_repr(PyObject *self) {
55 PyObject *format, *repr, *format_attr;
56
57 format_attr = PyUnicode_FromString (repr_format_key);
58 format = PyTuple_Type.tp_getattro (self, format_attr);
59 Py_DECREF (format_attr);
60 if (format == NULL)
61 return NULL;
62 repr = PyUnicode_Format (format, self);
63 Py_DECREF (format);
64 return repr;
65 }
66
67 /**
68 * PyGIResultTuple_Type.tp_getattro implementation.
69 * Looks up the tuple index in _ResultTuple.__tuple_indices and returns the
70 * tuple item.
71 */
72 static PyObject*
resulttuple_getattro(PyObject * self,PyObject * name)73 resulttuple_getattro(PyObject *self, PyObject *name) {
74 PyObject *mapping, *index, *mapping_attr, *item;
75
76 mapping_attr = PyUnicode_FromString (tuple_indices_key);
77 mapping = PyTuple_Type.tp_getattro (self, mapping_attr);
78 Py_DECREF (mapping_attr);
79 if (mapping == NULL)
80 return NULL;
81 g_assert (PyDict_Check (mapping));
82 index = PyDict_GetItem (mapping, name);
83
84 if (index != NULL) {
85 item = PyTuple_GET_ITEM (self, PyLong_AsSsize_t (index));
86 Py_INCREF (item);
87 } else {
88 item = PyTuple_Type.tp_getattro (self, name);
89 }
90 Py_DECREF (mapping);
91
92 return item;
93 }
94
95 /**
96 * ResultTuple.__reduce__() implementation.
97 * Always returns (tuple, tuple(self))
98 * Needed so that pickling doesn't depend on our tuple subclass and unpickling
99 * works without it. As a result unpickle will give back in a normal tuple.
100 */
101 static PyObject *
resulttuple_reduce(PyObject * self)102 resulttuple_reduce(PyObject *self)
103 {
104 PyObject *tuple = PySequence_Tuple (self);
105 if (tuple == NULL)
106 return NULL;
107 return Py_BuildValue ("(O, (N))", &PyTuple_Type, tuple);
108 }
109
110 /**
111 * Extends __dir__ with the extra attributes accessible through
112 * resulttuple_getattro()
113 */
114 static PyObject *
resulttuple_dir(PyObject * self)115 resulttuple_dir(PyObject *self)
116 {
117 PyObject *mapping_attr;
118 PyObject *items = NULL;
119 PyObject *mapping = NULL;
120 PyObject *mapping_values = NULL;
121 PyObject *result = NULL;
122
123 mapping_attr = PyUnicode_FromString (tuple_indices_key);
124 mapping = PyTuple_Type.tp_getattro (self, mapping_attr);
125 Py_DECREF (mapping_attr);
126 if (mapping == NULL)
127 goto error;
128 items = PyObject_Dir ((PyObject*)Py_TYPE (self));
129 if (items == NULL)
130 goto error;
131 mapping_values = PyDict_Keys (mapping);
132 if (mapping_values == NULL)
133 goto error;
134 result = PySequence_InPlaceConcat (items, mapping_values);
135
136 error:
137 Py_XDECREF (items);
138 Py_XDECREF (mapping);
139 Py_XDECREF (mapping_values);
140
141 return result;
142 }
143
144 /**
145 * resulttuple_new_type:
146 * @args: one list object containing tuple item names and None
147 *
148 * Exposes pygi_resulttuple_new_type() as ResultTuple._new_type()
149 * to allow creation of result types for unit tests.
150 *
151 * Returns: A new PyTypeObject which is a subclass of PyGIResultTuple_Type
152 * or %NULL in case of an error.
153 */
154 static PyObject *
resulttuple_new_type(PyObject * self,PyObject * args)155 resulttuple_new_type(PyObject *self, PyObject *args) {
156 PyObject *tuple_names, *new_type;
157
158 if (!PyArg_ParseTuple (args, "O:ResultTuple._new_type", &tuple_names))
159 return NULL;
160
161 if (!PyList_Check (tuple_names)) {
162 PyErr_SetString (PyExc_TypeError, "not a list");
163 return NULL;
164 }
165
166 new_type = (PyObject *)pygi_resulttuple_new_type (tuple_names);
167 return new_type;
168 }
169
170 static PyMethodDef resulttuple_methods[] = {
171 {"__reduce__", (PyCFunction)resulttuple_reduce, METH_NOARGS},
172 {"__dir__", (PyCFunction)resulttuple_dir, METH_NOARGS},
173 {"_new_type", (PyCFunction)resulttuple_new_type,
174 METH_VARARGS | METH_STATIC},
175 {NULL, NULL, 0},
176 };
177
178 /**
179 * pygi_resulttuple_new_type:
180 * @tuple_names: A python list containing str or None items.
181 *
182 * Similar to namedtuple() creates a new tuple subclass which
183 * allows to access items by name and have a pretty __repr__.
184 * Each item in the passed name list corresponds to an item with
185 * the same index in the tuple class. If the name is None the item/index
186 * is unnamed.
187 *
188 * Returns: A new PyTypeObject which is a subclass of PyGIResultTuple_Type
189 * or %NULL in case of an error.
190 */
191 PyTypeObject*
pygi_resulttuple_new_type(PyObject * tuple_names)192 pygi_resulttuple_new_type(PyObject *tuple_names) {
193 PyTypeObject *new_type;
194 PyObject *class_dict, *format_string, *empty_format, *named_format,
195 *format_list, *sep, *index_dict, *slots, *paren_format, *new_type_args,
196 *paren_string;
197 Py_ssize_t len, i;
198
199 g_assert (PyList_Check (tuple_names));
200
201 class_dict = PyDict_New ();
202
203 /* To save some memory don't use an instance dict */
204 slots = PyTuple_New (0);
205 PyDict_SetItemString (class_dict, "__slots__", slots);
206 Py_DECREF (slots);
207
208 format_list = PyList_New (0);
209 index_dict = PyDict_New ();
210
211 empty_format = PyUnicode_FromString ("%r");
212 named_format = PyUnicode_FromString ("%s=%%r");
213 len = PyList_Size (tuple_names);
214 for (i = 0; i < len; i++) {
215 PyObject *item, *named_args, *named_build, *index;
216 item = PyList_GET_ITEM (tuple_names, i);
217 if (item == Py_None) {
218 PyList_Append (format_list, empty_format);
219 } else {
220 named_args = Py_BuildValue ("(O)", item);
221 named_build = PyUnicode_Format (named_format, named_args);
222 Py_DECREF (named_args);
223 PyList_Append (format_list, named_build);
224 Py_DECREF (named_build);
225 index = PyLong_FromSsize_t (i);
226 PyDict_SetItem (index_dict, item, index);
227 Py_DECREF (index);
228 }
229 }
230 Py_DECREF (empty_format);
231 Py_DECREF (named_format);
232
233 sep = PyUnicode_FromString (", ");
234 format_string = PyObject_CallMethod (sep, "join", "O", format_list);
235 Py_DECREF (sep);
236 Py_DECREF (format_list);
237 paren_format = PyUnicode_FromString ("(%s)");
238 paren_string = PyUnicode_Format (paren_format, format_string);
239 Py_DECREF (paren_format);
240 Py_DECREF (format_string);
241
242 PyDict_SetItemString (class_dict, repr_format_key, paren_string);
243 Py_DECREF (paren_string);
244
245 PyDict_SetItemString (class_dict, tuple_indices_key, index_dict);
246 Py_DECREF (index_dict);
247
248 new_type_args = Py_BuildValue ("s(O)O", "_ResultTuple",
249 &PyGIResultTuple_Type, class_dict);
250 new_type = (PyTypeObject *)PyType_Type.tp_new (&PyType_Type,
251 new_type_args, NULL);
252 Py_DECREF (new_type_args);
253 Py_DECREF (class_dict);
254
255 if (new_type != NULL) {
256 /* disallow subclassing as that would break the free list caching
257 * since we assume that all subclasses use PyTupleObject */
258 new_type->tp_flags &= ~Py_TPFLAGS_BASETYPE;
259 }
260
261 return new_type;
262 }
263
264
265 /**
266 * pygi_resulttuple_new:
267 * @subclass: A PyGIResultTuple_Type subclass which will be the type of the
268 * returned instance.
269 * @len: Length of the returned tuple
270 *
271 * Like PyTuple_New(). Return an uninitialized tuple of the given @length.
272 *
273 * Returns: An instance of @subclass or %NULL on error.
274 */
275 PyObject *
pygi_resulttuple_new(PyTypeObject * subclass,Py_ssize_t len)276 pygi_resulttuple_new(PyTypeObject *subclass, Py_ssize_t len) {
277 #ifdef PYGI_USE_FREELIST
278 PyObject *self;
279 Py_ssize_t i;
280
281 /* Check the free list for a tuple object with the needed size;
282 * clear it and change the class to ours.
283 */
284 if (len > 0 && len < PyGIResultTuple_MAXSAVESIZE) {
285 self = free_list[len];
286 if (self != NULL) {
287 free_list[len] = PyTuple_GET_ITEM (self, 0);
288 numfree[len]--;
289 for (i=0; i < len; i++) {
290 PyTuple_SET_ITEM (self, i, NULL);
291 }
292 Py_TYPE (self) = subclass;
293 Py_INCREF (subclass);
294 _Py_NewReference (self);
295 PyObject_GC_Track (self);
296 return self;
297 }
298 }
299 #endif
300
301 /* For zero length tuples and in case the free list is empty, alloc
302 * as usual.
303 */
304 return subclass->tp_alloc (subclass, len);
305 }
306
307 #ifdef PYGI_USE_FREELIST
resulttuple_dealloc(PyObject * self)308 static void resulttuple_dealloc(PyObject *self) {
309 Py_ssize_t i, len;
310
311 PyObject_GC_UnTrack (self);
312 Py_TRASHCAN_SAFE_BEGIN (self)
313
314 /* Free the tuple items and, if there is space, save the tuple object
315 * pointer to the front of the free list for its size. Otherwise free it.
316 */
317 len = Py_SIZE (self);
318 if (len > 0) {
319 for (i=0; i < len; i++) {
320 Py_XDECREF (PyTuple_GET_ITEM (self, i));
321 }
322
323 if (len < PyGIResultTuple_MAXSAVESIZE && numfree[len] < PyGIResultTuple_MAXFREELIST) {
324 PyTuple_SET_ITEM (self, 0, free_list[len]);
325 numfree[len]++;
326 free_list[len] = self;
327 goto done;
328 }
329 }
330
331 Py_TYPE (self)->tp_free (self);
332
333 done:
334 Py_TRASHCAN_SAFE_END (self)
335 }
336 #endif
337
338 /**
339 * pygi_resulttuple_register_types:
340 * @module: A Python modules to which ResultTuple gets added to.
341 *
342 * Initializes the ResultTuple class and adds it to the passed @module.
343 *
344 * Returns: -1 on error, 0 on success.
345 */
pygi_resulttuple_register_types(PyObject * module)346 int pygi_resulttuple_register_types(PyObject *module) {
347
348 PyGIResultTuple_Type.tp_base = &PyTuple_Type;
349 PyGIResultTuple_Type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
350 PyGIResultTuple_Type.tp_repr = (reprfunc)resulttuple_repr;
351 PyGIResultTuple_Type.tp_getattro = (getattrofunc)resulttuple_getattro;
352 PyGIResultTuple_Type.tp_methods = resulttuple_methods;
353 #ifdef PYGI_USE_FREELIST
354 PyGIResultTuple_Type.tp_dealloc = (destructor)resulttuple_dealloc;
355 #endif
356
357 if (PyType_Ready (&PyGIResultTuple_Type) < 0)
358 return -1;
359
360 Py_INCREF (&PyGIResultTuple_Type);
361 if (PyModule_AddObject (module, "ResultTuple",
362 (PyObject *)&PyGIResultTuple_Type) < 0) {
363 Py_DECREF (&PyGIResultTuple_Type);
364 return -1;
365 }
366
367 return 0;
368 }
369