File: ext_attr_getter.srctree

package info (click to toggle)
cython 3.0.11%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 19,092 kB
  • sloc: python: 83,539; ansic: 18,831; cpp: 1,402; xml: 1,031; javascript: 511; makefile: 403; sh: 204; sed: 11
file content (368 lines) | stat: -rw-r--r-- 8,358 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# mode: run
# tag: cgetter, property

"""
PYTHON setup.py build_ext --inplace
PYTHON run_failure_tests.py
PYTHON runner.py
"""

######## setup.py ########

from Cython.Build.Dependencies import cythonize
from distutils.core import setup

# Enforce the right build order
setup(ext_modules = cythonize("foo_extension.pyx", language_level=3))
setup(ext_modules = cythonize("getter[0-9].pyx", language_level=3))


######## run_failure_tests.py ########

import glob
import sys

from Cython.Build.Dependencies import cythonize
from Cython.Compiler.Errors import CompileError

# Run the failure tests
failed_tests = []
passed_tests = []

def run_test(name):
    title = name
    with open(name, 'r') as f:
        for line in f:
            if 'TEST' in line:
                title = line.partition('TEST:')[2].strip()
                break
    sys.stderr.write("\n### TESTING: %s\n" % title)

    try:
        cythonize(name, language_level=3)
    except CompileError as e:
        sys.stderr.write("\nOK: got expected exception\n")
        passed_tests.append(name)
    else:
        sys.stderr.write("\nFAIL: compilation did not detect the error\n")
        failed_tests.append(name)

for name in sorted(glob.glob("getter_fail*.pyx")):
    run_test(name)

assert not failed_tests, "Failed tests: %s" % failed_tests
assert passed_tests  # check that tests were found at all


######## foo.h ########

#include <Python.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef struct {
    PyObject_HEAD
    int f0;
    int f1;
    int f2;
    int v[10];
} FooStructNominal;

typedef struct {
    PyObject_HEAD
} FooStructOpaque;


#define PyFoo_GET0M(a) (((FooStructNominal*)a)->f0)
#define PyFoo_GET1M(a) (((FooStructNominal*)a)->f1)
#define PyFoo_GET2M(a) (((FooStructNominal*)a)->f2)

int PyFoo_Get0F(FooStructOpaque *f)
{
    return PyFoo_GET0M(f);
}

int PyFoo_Get1F(FooStructOpaque *f)
{
    return PyFoo_GET1M(f);
}

int PyFoo_Get2F(FooStructOpaque *f)
{
    return PyFoo_GET2M(f);
}

int *PyFoo_GetV(FooStructOpaque *f)
{
    return ((FooStructNominal*)f)->v;
}

#ifdef __cplusplus
}
#endif


######## foo_extension.pyx ########

cdef class Foo:
    cdef public int _field0, _field1, _field2;
    cdef public int _vector[10];

    @property
    def field0(self):
        return self._field0

    @property
    def field1(self):
        return self._field1

    @property
    def field2(self):
        return self._field2

    def __init__(self, f0, f1, f2, vec=None):
        if vec is None:
            vec = ()
        if not isinstance(vec, tuple):
            raise ValueError("v must be None or a tuple")
        self._field0 = f0
        self._field1 = f1
        self._field2 = f2
        i = 0
        for v in vec:
            self._vector[i] = v
            if i > 9:
                break
            i += 1
        for j in range(i,10):
            self._vector[j] = 0

# A pure-python class that disallows direct access to fields
class OpaqueFoo(Foo):

    @property
    def field0(self):
        raise AttributeError('no direct access to field0')

    @property
    def field1(self):
        raise AttributeError('no direct access to field1')

    @property
    def field2(self):
        raise AttributeError('no direct access to field2')


######## getter0.pyx ########

# Access base Foo fields from C via aliased field names

cdef extern from "foo.h":

    ctypedef class foo_extension.Foo [object FooStructNominal]:
        cdef:
            int field0 "f0"
            int field1 "f1"
            int field2 "f2"

def sum(Foo f):
    # Note - not a cdef function but compiling the f.__getattr__('field0')
    # notices the alias and replaces the __getattr__ in c by f->f0 anyway
    return f.field0 + f.field1 + f.field2

def check_pyobj(Foo f):
    # compare the c code to the check_pyobj in getter2.pyx
    return bool(f.field1)


######## getter.pxd ########

# Access base Foo fields from C via getter functions


cdef extern from "foo.h":
    ctypedef class foo_extension.Foo [object FooStructOpaque, check_size ignore]:
        @property
        cdef inline int fieldM0(self):
            return PyFoo_GET0M(self)

        @property
        cdef inline int fieldF1(self) except -123:
            return PyFoo_Get1F(self)

        @property
        cdef inline int fieldM2(self):
            return PyFoo_GET2M(self)

        @property
        cdef inline int *vector(self):
            return PyFoo_GetV(self)

        @property
        cdef inline int meaning_of_life(self) except -99:
            cdef int ret = 21
            ret *= 2
            return ret

    int PyFoo_GET0M(Foo);  # this is actually a macro !
    int PyFoo_Get1F(Foo);
    int PyFoo_GET2M(Foo);  # this is actually a macro !
    int *PyFoo_GetV(Foo);


######## getter1.pyx ########

cimport getter

def sum(getter.Foo f):
    # Note - not a cdef function but compiling the f.__getattr__('field0')
    # notices the getter and replaces the __getattr__ in c by PyFoo_GET anyway
    return f.fieldM0 + f.fieldF1 + f.fieldM2

def check_10(getter.Foo f):
    return f.fieldF1 != 10

def vec0(getter.Foo f):
    return f.vector[0]

def check_binop(getter.Foo f):
    return f.fieldF1 / 10


######## getter2.pyx ########

cimport getter

def check_pyobj(getter.Foo f):
    return bool(f.fieldF1)
 
def check_unary(getter.Foo f):
    return -f.fieldF1

def check_meaning_of_life(getter.Foo f):
    return f.meaning_of_life


######## getter_fail_classmethod.pyx ########

# TEST: Make sure not all decorators are accepted.

cdef extern from "foo.h":
    ctypedef class foo_extension.Foo [object FooStructOpaque]:
        @property
        @classmethod
        cdef inline int field0(cls):
            print('in classmethod of Foo')


######## getter_fail_dot_getter.pyx ########

# TEST: Make sure not all decorators are accepted.

cdef extern from "foo.h":
    ctypedef class foo_extension.Foo [object FooStructOpaque]:
        @property
        cdef inline int field0(self):
            pass

        @field0.getter
        cdef inline void field1(self):
            pass


######## getter_fail_no_inline.pyx ########

# TEST: Properties must be declared "inline".

cdef extern from "foo.h":
    ctypedef class foo_extension.Foo [object FooStructOpaque]:
        @property
        cdef int field0(self):
            pass


######## getter_fail_void.pyx ########

# TEST: Properties must have a non-void return type.

cdef extern from "foo.h":
    ctypedef class foo_extension.Foo [object FooStructOpaque]:
        @property
        cdef void field0(self):
            pass


######## getter_fail_no_args.pyx ########

# TEST: Properties must have the right signature.

cdef extern from "foo.h":
    ctypedef class foo_extension.Foo [object FooStructOpaque]:
        @property
        cdef int field0():
            pass


######## getter_fail_too_many_args.pyx ########

# TEST: Properties must have the right signature.

cdef extern from "foo.h":
    ctypedef class foo_extension.Foo [object FooStructOpaque]:
        @property
        cdef int field0(x, y):
            pass


######## runner.py ########

import warnings
import foo_extension, getter0, getter1, getter2

def sum(f):
    # pure python field access, but code is identical to cython cdef sum
    return f.field0 + f.field1 + f.field2

# Baseline test: if this fails something else is wrong
foo = foo_extension.Foo(23, 123, 1023)

assert foo.field0 == 23
assert foo.field1 == 123
assert foo.field2 == 1023

ret =  getter0.sum(foo)
assert ret == sum(foo)

# Aliasing test. Check 'cdef int field0 "f0" works as advertised:
# - C can access the fields through the aliases
# - Python cannot access the fields at all

opaque_foo = foo_extension.OpaqueFoo(23, 123, 1023)

opaque_ret = getter0.sum(opaque_foo)
assert opaque_ret == ret

val = getter2.check_pyobj(opaque_foo)
assert val is True
val = getter2.check_unary(opaque_foo)
assert val == -123
val = getter2.check_meaning_of_life(opaque_foo)
assert val == 42

try:
    f0 = opaque_ret.field0
    assert False
except AttributeError as e:
    pass

# Getter test. Check C-level getter works as advertised:
# - C accesses the fields through getter calls (maybe macros)
# - Python accesses the fields through attribute lookup

opaque_foo = foo_extension.OpaqueFoo(23, 123, 1023, (1, 2, 3))

opaque_ret = getter1.sum(opaque_foo)
assert opaque_ret == ret