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
|