1 /* Use this file as a template to start implementing a module that
2 also declares object types. All occurrences of 'Xxo' should be changed
3 to something reasonable for your objects. After that, all other
4 occurrences of 'xx' should be changed to something reasonable for your
5 module. If your module is named foo your source file should be named
6 foo.c or foomodule.c.
7
8 You will probably want to delete all references to 'x_attr' and add
9 your own types of attributes instead. Maybe you want to name your
10 local variables other than 'self'. If your object type is needed in
11 other files, you'll have to create a file "foobarobject.h"; see
12 floatobject.h for an example.
13
14 This module roughly corresponds to::
15
16 class Xxo:
17 """A class that explicitly stores attributes in an internal dict"""
18
19 def __init__(self):
20 # In the C class, "_x_attr" is not accessible from Python code
21 self._x_attr = {}
22 self._x_exports = 0
23
24 def __getattr__(self, name):
25 return self._x_attr[name]
26
27 def __setattr__(self, name, value):
28 self._x_attr[name] = value
29
30 def __delattr__(self, name):
31 del self._x_attr[name]
32
33 @property
34 def x_exports(self):
35 """Return the number of times an internal buffer is exported."""
36 # Each Xxo instance has a 10-byte buffer that can be
37 # accessed via the buffer interface (e.g. `memoryview`).
38 return self._x_exports
39
40 def demo(o, /):
41 if isinstance(o, str):
42 return o
43 elif isinstance(o, Xxo):
44 return o
45 else:
46 raise Error('argument must be str or Xxo')
47
48 class Error(Exception):
49 """Exception raised by the xxlimited module"""
50
51 def foo(i: int, j: int, /):
52 """Return the sum of i and j."""
53 # Unlike this pseudocode, the C function will *only* work with
54 # integers and perform C long int arithmetic
55 return i + j
56
57 def new():
58 return Xxo()
59
60 def Str(str):
61 # A trivial subclass of a built-in type
62 pass
63 */
64
65 #define Py_LIMITED_API 0x030b0000
66
67 #include "Python.h"
68 #include <string.h>
69
70 #define BUFSIZE 10
71
72 // Module state
73 typedef struct {
74 PyObject *Xxo_Type; // Xxo class
75 PyObject *Error_Type; // Error class
76 } xx_state;
77
78
79 /* Xxo objects */
80
81 // Instance state
82 typedef struct {
83 PyObject_HEAD
84 PyObject *x_attr; /* Attributes dictionary */
85 char x_buffer[BUFSIZE]; /* buffer for Py_buffer */
86 Py_ssize_t x_exports; /* how many buffer are exported */
87 } XxoObject;
88
89 // XXX: no good way to do this yet
90 // #define XxoObject_Check(v) Py_IS_TYPE(v, Xxo_Type)
91
92 static XxoObject *
newXxoObject(PyObject * module)93 newXxoObject(PyObject *module)
94 {
95 xx_state *state = PyModule_GetState(module);
96 if (state == NULL) {
97 return NULL;
98 }
99 XxoObject *self;
100 self = PyObject_GC_New(XxoObject, (PyTypeObject*)state->Xxo_Type);
101 if (self == NULL) {
102 return NULL;
103 }
104 self->x_attr = NULL;
105 memset(self->x_buffer, 0, BUFSIZE);
106 self->x_exports = 0;
107 return self;
108 }
109
110 /* Xxo finalization */
111
112 static int
Xxo_traverse(PyObject * self_obj,visitproc visit,void * arg)113 Xxo_traverse(PyObject *self_obj, visitproc visit, void *arg)
114 {
115 // Visit the type
116 Py_VISIT(Py_TYPE(self_obj));
117
118 // Visit the attribute dict
119 XxoObject *self = (XxoObject *)self_obj;
120 Py_VISIT(self->x_attr);
121 return 0;
122 }
123
124 static int
Xxo_clear(XxoObject * self)125 Xxo_clear(XxoObject *self)
126 {
127 Py_CLEAR(self->x_attr);
128 return 0;
129 }
130
131 static void
Xxo_finalize(PyObject * self_obj)132 Xxo_finalize(PyObject *self_obj)
133 {
134 XxoObject *self = (XxoObject *)self_obj;
135 Py_CLEAR(self->x_attr);
136 }
137
138 static void
Xxo_dealloc(PyObject * self)139 Xxo_dealloc(PyObject *self)
140 {
141 PyObject_GC_UnTrack(self);
142 Xxo_finalize(self);
143 PyTypeObject *tp = Py_TYPE(self);
144 freefunc free = PyType_GetSlot(tp, Py_tp_free);
145 free(self);
146 Py_DECREF(tp);
147 }
148
149
150 /* Xxo attribute handling */
151
152 static PyObject *
Xxo_getattro(XxoObject * self,PyObject * name)153 Xxo_getattro(XxoObject *self, PyObject *name)
154 {
155 if (self->x_attr != NULL) {
156 PyObject *v = PyDict_GetItemWithError(self->x_attr, name);
157 if (v != NULL) {
158 Py_INCREF(v);
159 return v;
160 }
161 else if (PyErr_Occurred()) {
162 return NULL;
163 }
164 }
165 return PyObject_GenericGetAttr((PyObject *)self, name);
166 }
167
168 static int
Xxo_setattro(XxoObject * self,PyObject * name,PyObject * v)169 Xxo_setattro(XxoObject *self, PyObject *name, PyObject *v)
170 {
171 if (self->x_attr == NULL) {
172 // prepare the attribute dict
173 self->x_attr = PyDict_New();
174 if (self->x_attr == NULL) {
175 return -1;
176 }
177 }
178 if (v == NULL) {
179 // delete an attribute
180 int rv = PyDict_DelItem(self->x_attr, name);
181 if (rv < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) {
182 PyErr_SetString(PyExc_AttributeError,
183 "delete non-existing Xxo attribute");
184 return -1;
185 }
186 return rv;
187 }
188 else {
189 // set an attribute
190 return PyDict_SetItem(self->x_attr, name, v);
191 }
192 }
193
194 /* Xxo methods */
195
196 static PyObject *
Xxo_demo(XxoObject * self,PyTypeObject * defining_class,PyObject ** args,Py_ssize_t nargs,PyObject * kwnames)197 Xxo_demo(XxoObject *self, PyTypeObject *defining_class,
198 PyObject **args, Py_ssize_t nargs, PyObject *kwnames)
199 {
200 if (kwnames != NULL && PyObject_Length(kwnames)) {
201 PyErr_SetString(PyExc_TypeError, "demo() takes no keyword arguments");
202 return NULL;
203 }
204 if (nargs != 1) {
205 PyErr_SetString(PyExc_TypeError, "demo() takes exactly 1 argument");
206 return NULL;
207 }
208
209 PyObject *o = args[0];
210
211 /* Test if the argument is "str" */
212 if (PyUnicode_Check(o)) {
213 Py_INCREF(o);
214 return o;
215 }
216
217 /* test if the argument is of the Xxo class */
218 if (PyObject_TypeCheck(o, defining_class)) {
219 Py_INCREF(o);
220 return o;
221 }
222
223 Py_INCREF(Py_None);
224 return Py_None;
225 }
226
227 static PyMethodDef Xxo_methods[] = {
228 {"demo", _PyCFunction_CAST(Xxo_demo),
229 METH_METHOD | METH_FASTCALL | METH_KEYWORDS, PyDoc_STR("demo(o) -> o")},
230 {NULL, NULL} /* sentinel */
231 };
232
233 /* Xxo buffer interface */
234
235 static int
Xxo_getbuffer(XxoObject * self,Py_buffer * view,int flags)236 Xxo_getbuffer(XxoObject *self, Py_buffer *view, int flags)
237 {
238 int res = PyBuffer_FillInfo(view, (PyObject*)self,
239 (void *)self->x_buffer, BUFSIZE,
240 0, flags);
241 if (res == 0) {
242 self->x_exports++;
243 }
244 return res;
245 }
246
247 static void
Xxo_releasebuffer(XxoObject * self,Py_buffer * view)248 Xxo_releasebuffer(XxoObject *self, Py_buffer *view)
249 {
250 self->x_exports--;
251 }
252
253 static PyObject *
Xxo_get_x_exports(XxoObject * self,void * c)254 Xxo_get_x_exports(XxoObject *self, void *c)
255 {
256 return PyLong_FromSsize_t(self->x_exports);
257 }
258
259 /* Xxo type definition */
260
261 PyDoc_STRVAR(Xxo_doc,
262 "A class that explicitly stores attributes in an internal dict");
263
264 static PyGetSetDef Xxo_getsetlist[] = {
265 {"x_exports", (getter) Xxo_get_x_exports, NULL, NULL},
266 {NULL},
267 };
268
269
270 static PyType_Slot Xxo_Type_slots[] = {
271 {Py_tp_doc, (char *)Xxo_doc},
272 {Py_tp_traverse, Xxo_traverse},
273 {Py_tp_clear, Xxo_clear},
274 {Py_tp_finalize, Xxo_finalize},
275 {Py_tp_dealloc, Xxo_dealloc},
276 {Py_tp_getattro, Xxo_getattro},
277 {Py_tp_setattro, Xxo_setattro},
278 {Py_tp_methods, Xxo_methods},
279 {Py_bf_getbuffer, Xxo_getbuffer},
280 {Py_bf_releasebuffer, Xxo_releasebuffer},
281 {Py_tp_getset, Xxo_getsetlist},
282 {0, 0}, /* sentinel */
283 };
284
285 static PyType_Spec Xxo_Type_spec = {
286 .name = "xxlimited.Xxo",
287 .basicsize = sizeof(XxoObject),
288 .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
289 .slots = Xxo_Type_slots,
290 };
291
292
293 /* Str type definition*/
294
295 static PyType_Slot Str_Type_slots[] = {
296 {0, 0}, /* sentinel */
297 };
298
299 static PyType_Spec Str_Type_spec = {
300 .name = "xxlimited.Str",
301 .basicsize = 0,
302 .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
303 .slots = Str_Type_slots,
304 };
305
306
307 /* Function of two integers returning integer (with C "long int" arithmetic) */
308
309 PyDoc_STRVAR(xx_foo_doc,
310 "foo(i,j)\n\
311 \n\
312 Return the sum of i and j.");
313
314 static PyObject *
xx_foo(PyObject * module,PyObject * args)315 xx_foo(PyObject *module, PyObject *args)
316 {
317 long i, j;
318 long res;
319 if (!PyArg_ParseTuple(args, "ll:foo", &i, &j))
320 return NULL;
321 res = i+j; /* XXX Do something here */
322 return PyLong_FromLong(res);
323 }
324
325
326 /* Function of no arguments returning new Xxo object */
327
328 static PyObject *
xx_new(PyObject * module,PyObject * Py_UNUSED (unused))329 xx_new(PyObject *module, PyObject *Py_UNUSED(unused))
330 {
331 XxoObject *rv;
332
333 rv = newXxoObject(module);
334 if (rv == NULL)
335 return NULL;
336 return (PyObject *)rv;
337 }
338
339
340
341 /* List of functions defined in the module */
342
343 static PyMethodDef xx_methods[] = {
344 {"foo", xx_foo, METH_VARARGS,
345 xx_foo_doc},
346 {"new", xx_new, METH_NOARGS,
347 PyDoc_STR("new() -> new Xx object")},
348 {NULL, NULL} /* sentinel */
349 };
350
351
352 /* The module itself */
353
354 PyDoc_STRVAR(module_doc,
355 "This is a template module just for instruction.");
356
357 static int
xx_modexec(PyObject * m)358 xx_modexec(PyObject *m)
359 {
360 xx_state *state = PyModule_GetState(m);
361
362 state->Error_Type = PyErr_NewException("xxlimited.Error", NULL, NULL);
363 if (state->Error_Type == NULL) {
364 return -1;
365 }
366 if (PyModule_AddType(m, (PyTypeObject*)state->Error_Type) < 0) {
367 return -1;
368 }
369
370 state->Xxo_Type = PyType_FromModuleAndSpec(m, &Xxo_Type_spec, NULL);
371 if (state->Xxo_Type == NULL) {
372 return -1;
373 }
374 if (PyModule_AddType(m, (PyTypeObject*)state->Xxo_Type) < 0) {
375 return -1;
376 }
377
378 // Add the Str type. It is not needed from C code, so it is only
379 // added to the module dict.
380 // It does not inherit from "object" (PyObject_Type), but from "str"
381 // (PyUnincode_Type).
382 PyObject *Str_Type = PyType_FromModuleAndSpec(
383 m, &Str_Type_spec, (PyObject *)&PyUnicode_Type);
384 if (Str_Type == NULL) {
385 return -1;
386 }
387 if (PyModule_AddType(m, (PyTypeObject*)Str_Type) < 0) {
388 return -1;
389 }
390 Py_DECREF(Str_Type);
391
392 return 0;
393 }
394
395 static PyModuleDef_Slot xx_slots[] = {
396 {Py_mod_exec, xx_modexec},
397 {0, NULL}
398 };
399
400 static int
xx_traverse(PyObject * module,visitproc visit,void * arg)401 xx_traverse(PyObject *module, visitproc visit, void *arg)
402 {
403 xx_state *state = PyModule_GetState(module);
404 Py_VISIT(state->Xxo_Type);
405 Py_VISIT(state->Error_Type);
406 return 0;
407 }
408
409 static int
xx_clear(PyObject * module)410 xx_clear(PyObject *module)
411 {
412 xx_state *state = PyModule_GetState(module);
413 Py_CLEAR(state->Xxo_Type);
414 Py_CLEAR(state->Error_Type);
415 return 0;
416 }
417
418 static struct PyModuleDef xxmodule = {
419 PyModuleDef_HEAD_INIT,
420 .m_name = "xxlimited",
421 .m_doc = module_doc,
422 .m_size = sizeof(xx_state),
423 .m_methods = xx_methods,
424 .m_slots = xx_slots,
425 .m_traverse = xx_traverse,
426 .m_clear = xx_clear,
427 /* m_free is not necessary here: xx_clear clears all references,
428 * and the module state is deallocated along with the module.
429 */
430 };
431
432
433 /* Export function for the module (*must* be called PyInit_xx) */
434
435 PyMODINIT_FUNC
PyInit_xxlimited(void)436 PyInit_xxlimited(void)
437 {
438 return PyModuleDef_Init(&xxmodule);
439 }
440