• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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