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 // Need limited C API version 3.13 for Py_mod_gil
66 #include "pyconfig.h" // Py_GIL_DISABLED
67 #ifndef Py_GIL_DISABLED
68 # define Py_LIMITED_API 0x030d0000
69 #endif
70
71 #include "Python.h"
72 #include <string.h>
73
74 #define BUFSIZE 10
75
76 // Module state
77 typedef struct {
78 PyObject *Xxo_Type; // Xxo class
79 PyObject *Error_Type; // Error class
80 } xx_state;
81
82
83 /* Xxo objects */
84
85 // Instance state
86 typedef struct {
87 PyObject_HEAD
88 PyObject *x_attr; /* Attributes dictionary */
89 char x_buffer[BUFSIZE]; /* buffer for Py_buffer */
90 Py_ssize_t x_exports; /* how many buffer are exported */
91 } XxoObject;
92
93 // XXX: no good way to do this yet
94 // #define XxoObject_Check(v) Py_IS_TYPE(v, Xxo_Type)
95
96 static XxoObject *
newXxoObject(PyObject * module)97 newXxoObject(PyObject *module)
98 {
99 xx_state *state = PyModule_GetState(module);
100 if (state == NULL) {
101 return NULL;
102 }
103 XxoObject *self;
104 self = PyObject_GC_New(XxoObject, (PyTypeObject*)state->Xxo_Type);
105 if (self == NULL) {
106 return NULL;
107 }
108 self->x_attr = NULL;
109 memset(self->x_buffer, 0, BUFSIZE);
110 self->x_exports = 0;
111 return self;
112 }
113
114 /* Xxo finalization */
115
116 static int
Xxo_traverse(PyObject * self_obj,visitproc visit,void * arg)117 Xxo_traverse(PyObject *self_obj, visitproc visit, void *arg)
118 {
119 // Visit the type
120 Py_VISIT(Py_TYPE(self_obj));
121
122 // Visit the attribute dict
123 XxoObject *self = (XxoObject *)self_obj;
124 Py_VISIT(self->x_attr);
125 return 0;
126 }
127
128 static int
Xxo_clear(XxoObject * self)129 Xxo_clear(XxoObject *self)
130 {
131 Py_CLEAR(self->x_attr);
132 return 0;
133 }
134
135 static void
Xxo_finalize(PyObject * self_obj)136 Xxo_finalize(PyObject *self_obj)
137 {
138 XxoObject *self = (XxoObject *)self_obj;
139 Py_CLEAR(self->x_attr);
140 }
141
142 static void
Xxo_dealloc(PyObject * self)143 Xxo_dealloc(PyObject *self)
144 {
145 PyObject_GC_UnTrack(self);
146 Xxo_finalize(self);
147 PyTypeObject *tp = Py_TYPE(self);
148 freefunc free = PyType_GetSlot(tp, Py_tp_free);
149 free(self);
150 Py_DECREF(tp);
151 }
152
153
154 /* Xxo attribute handling */
155
156 static PyObject *
Xxo_getattro(XxoObject * self,PyObject * name)157 Xxo_getattro(XxoObject *self, PyObject *name)
158 {
159 if (self->x_attr != NULL) {
160 PyObject *v = PyDict_GetItemWithError(self->x_attr, name);
161 if (v != NULL) {
162 return Py_NewRef(v);
163 }
164 else if (PyErr_Occurred()) {
165 return NULL;
166 }
167 }
168 return PyObject_GenericGetAttr((PyObject *)self, name);
169 }
170
171 static int
Xxo_setattro(XxoObject * self,PyObject * name,PyObject * v)172 Xxo_setattro(XxoObject *self, PyObject *name, PyObject *v)
173 {
174 if (self->x_attr == NULL) {
175 // prepare the attribute dict
176 self->x_attr = PyDict_New();
177 if (self->x_attr == NULL) {
178 return -1;
179 }
180 }
181 if (v == NULL) {
182 // delete an attribute
183 int rv = PyDict_DelItem(self->x_attr, name);
184 if (rv < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) {
185 PyErr_SetString(PyExc_AttributeError,
186 "delete non-existing Xxo attribute");
187 return -1;
188 }
189 return rv;
190 }
191 else {
192 // set an attribute
193 return PyDict_SetItem(self->x_attr, name, v);
194 }
195 }
196
197 /* Xxo methods */
198
199 static PyObject *
Xxo_demo(XxoObject * self,PyTypeObject * defining_class,PyObject ** args,Py_ssize_t nargs,PyObject * kwnames)200 Xxo_demo(XxoObject *self, PyTypeObject *defining_class,
201 PyObject **args, Py_ssize_t nargs, PyObject *kwnames)
202 {
203 if (kwnames != NULL && PyObject_Length(kwnames)) {
204 PyErr_SetString(PyExc_TypeError, "demo() takes no keyword arguments");
205 return NULL;
206 }
207 if (nargs != 1) {
208 PyErr_SetString(PyExc_TypeError, "demo() takes exactly 1 argument");
209 return NULL;
210 }
211
212 PyObject *o = args[0];
213
214 /* Test if the argument is "str" */
215 if (PyUnicode_Check(o)) {
216 return Py_NewRef(o);
217 }
218
219 /* test if the argument is of the Xxo class */
220 if (PyObject_TypeCheck(o, defining_class)) {
221 return Py_NewRef(o);
222 }
223
224 return Py_NewRef(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 {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
398 {Py_mod_gil, Py_MOD_GIL_NOT_USED},
399 {0, NULL}
400 };
401
402 static int
xx_traverse(PyObject * module,visitproc visit,void * arg)403 xx_traverse(PyObject *module, visitproc visit, void *arg)
404 {
405 xx_state *state = PyModule_GetState(module);
406 Py_VISIT(state->Xxo_Type);
407 Py_VISIT(state->Error_Type);
408 return 0;
409 }
410
411 static int
xx_clear(PyObject * module)412 xx_clear(PyObject *module)
413 {
414 xx_state *state = PyModule_GetState(module);
415 Py_CLEAR(state->Xxo_Type);
416 Py_CLEAR(state->Error_Type);
417 return 0;
418 }
419
420 static struct PyModuleDef xxmodule = {
421 PyModuleDef_HEAD_INIT,
422 .m_name = "xxlimited",
423 .m_doc = module_doc,
424 .m_size = sizeof(xx_state),
425 .m_methods = xx_methods,
426 .m_slots = xx_slots,
427 .m_traverse = xx_traverse,
428 .m_clear = xx_clear,
429 /* m_free is not necessary here: xx_clear clears all references,
430 * and the module state is deallocated along with the module.
431 */
432 };
433
434
435 /* Export function for the module (*must* be called PyInit_xx) */
436
437 PyMODINIT_FUNC
PyInit_xxlimited(void)438 PyInit_xxlimited(void)
439 {
440 return PyModuleDef_Init(&xxmodule);
441 }
442