1
|
|
|
from __future__ import print_function |
2
|
|
|
|
3
|
|
|
import pickle |
4
|
|
|
try: |
5
|
|
|
import cPickle |
6
|
|
|
except ImportError: |
7
|
|
|
import pickle as cPickle |
8
|
|
|
from functools import partial |
9
|
|
|
from itertools import chain |
10
|
|
|
|
11
|
|
|
from pytest import fixture |
12
|
|
|
from pytest import raises |
13
|
|
|
|
14
|
|
|
from fields import BareFields, make_init_func, InheritableFields |
15
|
|
|
from fields import ComparableMixin |
16
|
|
|
from fields import ConvertibleFields |
17
|
|
|
from fields import ConvertibleMixin |
18
|
|
|
from fields import Fields |
19
|
|
|
from fields import PrintableMixin |
20
|
|
|
from fields import SlotsFields |
21
|
|
|
from fields import Tuple |
22
|
|
|
from fields.extras import RegexValidate |
23
|
|
|
from fields.extras import ValidationError |
24
|
|
|
|
25
|
|
|
|
26
|
|
|
@fixture(params=[ |
27
|
|
|
partial(pickle.dumps, protocol=i) |
28
|
|
|
for i in range(pickle.HIGHEST_PROTOCOL) |
29
|
|
|
] + [ |
30
|
|
|
partial(cPickle.dumps, protocol=i) |
31
|
|
|
for i in range(cPickle.HIGHEST_PROTOCOL) |
32
|
|
|
]) |
33
|
|
|
def pickler(request): |
34
|
|
|
return request.param |
35
|
|
|
|
36
|
|
|
|
37
|
|
|
@fixture(params=[pickle.loads, cPickle.loads]) |
38
|
|
|
def unpickler(request): |
39
|
|
|
return request.param |
40
|
|
|
|
41
|
|
|
|
42
|
|
|
@fixture(params=[Fields, SlotsFields]) |
43
|
|
|
def impl(request): |
44
|
|
|
print(request.param) |
45
|
|
|
return request.param |
46
|
|
|
|
47
|
|
|
|
48
|
|
|
class G1(Tuple.a.b): |
49
|
|
|
pass |
50
|
|
|
|
51
|
|
|
|
52
|
|
|
class G2(Fields.a.b[1].c[2]): |
53
|
|
|
pass |
54
|
|
|
|
55
|
|
|
|
56
|
|
|
class G3(Fields.a.b[1].c[2]): |
57
|
|
|
pass |
58
|
|
|
|
59
|
|
|
|
60
|
|
|
def test_slots_class_has_slots(): |
61
|
|
|
class Slots(SlotsFields.a.b[1]): |
62
|
|
|
pass |
63
|
|
|
|
64
|
|
|
i = Slots(0) |
65
|
|
|
i.a = 1 |
66
|
|
|
assert Slots.__slots__ == ['a', 'b'] |
67
|
|
|
assert i.a == 1 |
68
|
|
|
assert not hasattr(i, "__dict__") |
69
|
|
|
raises(AttributeError, setattr, i, "bogus", 1) |
70
|
|
|
raises(AttributeError, getattr, i, "bogus") |
71
|
|
|
|
72
|
|
|
|
73
|
|
|
def test_slots_class_customizable_slots(): |
74
|
|
|
class Slots(SlotsFields.a.b[1]): |
75
|
|
|
__slots__ = ["a", "b", "foo", "bar"] |
76
|
|
|
|
77
|
|
|
i = Slots(0) |
78
|
|
|
i.a = 1 |
79
|
|
|
assert Slots.__slots__ == ["a", "b", "foo", "bar"] |
80
|
|
|
assert i.a == 1 |
81
|
|
|
assert not hasattr(i, "__dict__") |
82
|
|
|
i.foo = 2 |
83
|
|
|
assert i.foo == 2 |
84
|
|
|
i.bar = 3 |
85
|
|
|
assert i.bar == 3 |
86
|
|
|
raises(AttributeError, setattr, i, "bogus", 1) |
87
|
|
|
raises(AttributeError, getattr, i, "bogus") |
88
|
|
|
|
89
|
|
|
|
90
|
|
|
def test_defaults_on_tuples(impl): |
91
|
|
|
class Ok(impl.a.b['def']): |
92
|
|
|
pass |
93
|
|
|
assert Ok(1).b == 'def' |
94
|
|
|
assert Ok(1, 2).b == 2 |
95
|
|
|
|
96
|
|
|
|
97
|
|
|
def test_required_after_defaults(impl): |
98
|
|
|
def test(): |
99
|
|
|
class Fail(impl.a['def'].b): |
100
|
|
|
pass |
101
|
|
|
|
102
|
|
|
raises(TypeError, test) |
103
|
|
|
|
104
|
|
|
|
105
|
|
|
def test_extra_args(impl): |
106
|
|
|
raises(TypeError, impl.a.b, 1, 2, 3) |
107
|
|
|
|
108
|
|
|
|
109
|
|
|
def test_comparable(): |
110
|
|
|
class C(ComparableMixin.a): |
111
|
|
|
pass |
112
|
|
|
|
113
|
|
|
raises(TypeError, C, 1, 2, 3) |
114
|
|
|
|
115
|
|
|
class D(BareFields.a.b.c, ComparableMixin.a): |
116
|
|
|
pass |
117
|
|
|
|
118
|
|
|
assert D(1, 2, 3) == D(1, 3, 4) |
119
|
|
|
assert D(1, 2, 3) != D(2, 2, 3) |
120
|
|
|
|
121
|
|
|
|
122
|
|
|
def test_printable(): |
123
|
|
|
class D(BareFields.a.b.c, PrintableMixin.a.b): |
124
|
|
|
pass |
125
|
|
|
|
126
|
|
|
assert str(D(1, 2, 3)) == "D(a=1, b=2)" |
127
|
|
|
|
128
|
|
|
|
129
|
|
|
def test_extra_args_2(impl): |
130
|
|
|
class X1(impl.a.b): |
131
|
|
|
pass |
132
|
|
|
|
133
|
|
|
raises(TypeError, X1, 1, 2, 3) |
134
|
|
|
|
135
|
|
|
|
136
|
|
|
def test_missing_args(impl): |
137
|
|
|
raises(TypeError, impl.a.b, 1) |
138
|
|
|
|
139
|
|
|
|
140
|
|
|
def test_missing_args_2(impl): |
141
|
|
|
class X1(impl.a.b): |
142
|
|
|
pass |
143
|
|
|
|
144
|
|
|
raises(TypeError, X1, 1) |
145
|
|
|
|
146
|
|
|
|
147
|
|
|
def test_already_specified(impl): |
148
|
|
|
raises(TypeError, lambda: impl.a[1].a) |
149
|
|
|
|
150
|
|
|
|
151
|
|
|
def test_already_specified_2(impl): |
152
|
|
|
raises(TypeError, lambda: impl.a.a) |
153
|
|
|
|
154
|
|
|
|
155
|
|
|
def test_already_specified_3(impl): |
156
|
|
|
raises(TypeError, lambda: impl.a.a[2]) |
157
|
|
|
|
158
|
|
|
|
159
|
|
|
def test_already_specified_4(impl): |
160
|
|
|
raises(TypeError, lambda: impl.a.b.a) |
161
|
|
|
|
162
|
|
|
|
163
|
|
|
def test_no_field(impl): |
164
|
|
|
raises(TypeError, lambda: impl[123]) |
165
|
|
|
|
166
|
|
|
|
167
|
|
|
def test_tuple_pickle(pickler, unpickler): |
168
|
|
|
g = G1(1, 2) |
169
|
|
|
assert unpickler(pickler(g)) == g |
170
|
|
|
|
171
|
|
|
|
172
|
|
|
def test_class_pickle(pickler, unpickler): |
173
|
|
|
g = G2(1, c=0) |
174
|
|
|
assert unpickler(pickler(g)) == g |
175
|
|
|
|
176
|
|
|
|
177
|
|
|
def test_slots_class_pickle(pickler, unpickler): |
178
|
|
|
g = G3(1, c=0) |
179
|
|
|
assert unpickler(pickler(g)) == g |
180
|
|
|
|
181
|
|
|
|
182
|
|
|
def test_tuple_factory(): |
183
|
|
|
class Z1(Tuple.a.b): |
184
|
|
|
pass |
185
|
|
|
|
186
|
|
|
t = Z1(1, 2) |
187
|
|
|
assert repr(t) == "Z1(a=1, b=2)" |
188
|
|
|
|
189
|
|
|
|
190
|
|
|
def test_nosubclass(impl): |
191
|
|
|
T1 = impl.a.b.c[1].d[2] |
192
|
|
|
|
193
|
|
|
t = T1(1, 2) |
194
|
|
|
assert repr(t) == "FieldsBase(a=1, b=2, c=1, d=2)" |
195
|
|
|
|
196
|
|
|
|
197
|
|
|
def test_factory(impl): |
198
|
|
|
class T1(impl.a.b.c[1].d[2]): |
199
|
|
|
pass |
200
|
|
|
|
201
|
|
|
t = T1(1, 2) |
202
|
|
|
assert repr(t) == "T1(a=1, b=2, c=1, d=2)" |
203
|
|
|
|
204
|
|
|
|
205
|
|
|
def test_factory_all_defaults(impl): |
206
|
|
|
class T2(impl.a[0].b[1].c[2].d[3]): |
207
|
|
|
pass |
208
|
|
|
|
209
|
|
|
t = T2() |
210
|
|
|
assert repr(t) == "T2(a=0, b=1, c=2, d=3)" |
211
|
|
|
|
212
|
|
|
|
213
|
|
|
def test_factory_no_required_after_defaults(impl): |
214
|
|
|
raises(TypeError, getattr, impl.a[0].b, 'c') |
215
|
|
|
|
216
|
|
|
|
217
|
|
|
def test_factory_no_required_after_defaults_2(impl): |
218
|
|
|
raises(TypeError, type, 'Broken', (impl.a[0].b,), {}) |
219
|
|
|
|
220
|
|
|
|
221
|
|
|
def test_factory_t3(impl): |
222
|
|
|
class T3(impl.a): |
223
|
|
|
pass |
224
|
|
|
|
225
|
|
|
t = T3(1) |
226
|
|
|
assert repr(t) == "T3(a=1)" |
227
|
|
|
|
228
|
|
|
|
229
|
|
|
def test_factory_empty_raise(impl): |
230
|
|
|
raises(TypeError, type, "T5", (impl,), {}) |
231
|
|
|
|
232
|
|
|
|
233
|
|
|
@fixture |
234
|
|
|
def CmpC(request, impl): |
235
|
|
|
class CmpC(impl.a.b): |
236
|
|
|
pass |
237
|
|
|
|
238
|
|
|
return CmpC |
239
|
|
|
|
240
|
|
|
|
241
|
|
|
def test_equal(CmpC): |
242
|
|
|
""" |
243
|
|
|
Equal objects are detected as equal. |
244
|
|
|
""" |
245
|
|
|
assert CmpC(1, 2) == CmpC(1, 2) |
246
|
|
|
assert not (CmpC(1, 2) != CmpC(1, 2)) |
247
|
|
|
|
248
|
|
|
|
249
|
|
|
def test_unequal_same_class(CmpC): |
250
|
|
|
""" |
251
|
|
|
Unequal objects of correct type are detected as unequal. |
252
|
|
|
""" |
253
|
|
|
assert CmpC(1, 2) != CmpC(2, 1) |
254
|
|
|
assert not (CmpC(1, 2) == CmpC(2, 1)) |
255
|
|
|
|
256
|
|
|
|
257
|
|
|
def test_unequal_different_class(CmpC): |
258
|
|
|
""" |
259
|
|
|
Unequal objects of differnt type are detected even if their attributes |
260
|
|
|
match. |
261
|
|
|
""" |
262
|
|
|
class NotCmpC(object): |
263
|
|
|
a = 1 |
264
|
|
|
b = 2 |
265
|
|
|
assert CmpC(1, 2) != NotCmpC() |
266
|
|
|
assert not (CmpC(1, 2) == NotCmpC()) |
267
|
|
|
|
268
|
|
|
|
269
|
|
|
def test_lt(CmpC): |
|
|
|
|
270
|
|
|
""" |
271
|
|
|
__lt__ compares objects as tuples of attribute values. |
272
|
|
|
""" |
273
|
|
|
for a, b in [ |
274
|
|
|
((1, 2), (2, 1)), |
275
|
|
|
((1, 2), (1, 3)), |
276
|
|
|
(("a", "b"), ("b", "a")), |
277
|
|
|
]: |
278
|
|
|
assert CmpC(*a) < CmpC(*b) |
279
|
|
|
|
280
|
|
|
|
281
|
|
|
def test_lt_unordable(CmpC): |
282
|
|
|
""" |
283
|
|
|
__lt__ returns NotImplemented if classes differ. |
284
|
|
|
""" |
285
|
|
|
assert NotImplemented == (CmpC(1, 2).__lt__(42)) |
286
|
|
|
|
287
|
|
|
|
288
|
|
|
def test_le(CmpC): |
|
|
|
|
289
|
|
|
""" |
290
|
|
|
__le__ compares objects as tuples of attribute values. |
291
|
|
|
""" |
292
|
|
|
for a, b in [ |
293
|
|
|
((1, 2), (2, 1)), |
294
|
|
|
((1, 2), (1, 3)), |
295
|
|
|
((1, 1), (1, 1)), |
296
|
|
|
(("a", "b"), ("b", "a")), |
297
|
|
|
(("a", "b"), ("a", "b")), |
298
|
|
|
]: |
299
|
|
|
assert CmpC(*a) <= CmpC(*b) |
300
|
|
|
|
301
|
|
|
|
302
|
|
|
def test_le_unordable(CmpC): |
303
|
|
|
""" |
304
|
|
|
__le__ returns NotImplemented if classes differ. |
305
|
|
|
""" |
306
|
|
|
assert NotImplemented == (CmpC(1, 2).__le__(42)) |
307
|
|
|
|
308
|
|
|
|
309
|
|
|
def test_gt(CmpC): |
|
|
|
|
310
|
|
|
""" |
311
|
|
|
__gt__ compares objects as tuples of attribute values. |
312
|
|
|
""" |
313
|
|
|
for a, b in [ |
314
|
|
|
((2, 1), (1, 2)), |
315
|
|
|
((1, 3), (1, 2)), |
316
|
|
|
(("b", "a"), ("a", "b")), |
317
|
|
|
]: |
318
|
|
|
assert CmpC(*a) > CmpC(*b) |
319
|
|
|
|
320
|
|
|
|
321
|
|
|
def test_gt_unordable(CmpC): |
322
|
|
|
""" |
323
|
|
|
__gt__ returns NotImplemented if classes differ. |
324
|
|
|
""" |
325
|
|
|
assert NotImplemented == (CmpC(1, 2).__gt__(42)) |
326
|
|
|
|
327
|
|
|
|
328
|
|
|
def test_ne_unordable(CmpC): |
329
|
|
|
""" |
330
|
|
|
__gt__ returns NotImplemented if classes differ. |
331
|
|
|
""" |
332
|
|
|
assert NotImplemented == (CmpC(1, 2).__ne__(42)) |
333
|
|
|
|
334
|
|
|
|
335
|
|
|
def test_ge(CmpC): |
|
|
|
|
336
|
|
|
""" |
337
|
|
|
__ge__ compares objects as tuples of attribute values. |
338
|
|
|
""" |
339
|
|
|
for a, b in [ |
340
|
|
|
((2, 1), (1, 2)), |
341
|
|
|
((1, 3), (1, 2)), |
342
|
|
|
((1, 1), (1, 1)), |
343
|
|
|
(("b", "a"), ("a", "b")), |
344
|
|
|
(("a", "b"), ("a", "b")), |
345
|
|
|
]: |
346
|
|
|
assert CmpC(*a) >= CmpC(*b) |
347
|
|
|
|
348
|
|
|
|
349
|
|
|
def test_ge_unordable(CmpC): |
350
|
|
|
""" |
351
|
|
|
__ge__ returns NotImplemented if classes differ. |
352
|
|
|
""" |
353
|
|
|
assert NotImplemented == (CmpC(1, 2).__ge__(42)) |
354
|
|
|
|
355
|
|
|
|
356
|
|
|
def test_hash(CmpC): |
357
|
|
|
""" |
358
|
|
|
__hash__ returns different hashes for different values. |
359
|
|
|
""" |
360
|
|
|
assert hash(CmpC(1, 2)) != hash(CmpC(1, 1)) |
361
|
|
|
|
362
|
|
|
|
363
|
|
|
@fixture |
364
|
|
|
def InitC(request, impl): |
365
|
|
|
class InitC(impl.a.b): |
366
|
|
|
def __init__(self, *args, **kwargs): |
367
|
|
|
super(InitC, self).__init__(*args, **kwargs) |
368
|
|
|
if self.a == self.b: |
369
|
|
|
raise ValueError |
370
|
|
|
|
371
|
|
|
return InitC |
372
|
|
|
|
373
|
|
|
|
374
|
|
|
def test_sets_attributes(InitC): |
375
|
|
|
""" |
376
|
|
|
The attributes are initialized using the passed keywords. |
377
|
|
|
""" |
378
|
|
|
obj = InitC(a=1, b=2) |
379
|
|
|
assert 1 == obj.a |
380
|
|
|
assert 2 == obj.b |
381
|
|
|
|
382
|
|
|
|
383
|
|
|
def test_custom_init(InitC): |
384
|
|
|
""" |
385
|
|
|
The class initializer is called too. |
386
|
|
|
""" |
387
|
|
|
with raises(ValueError): |
388
|
|
|
InitC(a=1, b=1) |
389
|
|
|
|
390
|
|
|
|
391
|
|
|
def test_passes_args(InitC): |
392
|
|
|
""" |
393
|
|
|
All positional parameters are passed to the original initializer. |
394
|
|
|
""" |
395
|
|
|
class InitWithArg(Fields.a): |
396
|
|
|
def __init__(self, arg, **kwargs): |
397
|
|
|
super(InitWithArg, self).__init__(**kwargs) |
398
|
|
|
self.arg = arg |
399
|
|
|
|
400
|
|
|
obj = InitWithArg(42, a=1) |
401
|
|
|
assert 42 == obj.arg |
402
|
|
|
assert 1 == obj.a |
403
|
|
|
|
404
|
|
|
|
405
|
|
|
def test_missing_arg(InitC): |
406
|
|
|
""" |
407
|
|
|
Raises `ValueError` if a value isn't passed. |
408
|
|
|
""" |
409
|
|
|
with raises(TypeError): |
410
|
|
|
InitC(a=1) |
411
|
|
|
|
412
|
|
|
|
413
|
|
|
def test_regex_validator(): |
414
|
|
|
class Test(RegexValidate.value['aa+'], Fields.value): |
415
|
|
|
pass |
416
|
|
|
|
417
|
|
|
raises(ValidationError, Test, 'a') |
418
|
|
|
raises(ValidationError, Test, '') |
419
|
|
|
raises(ValidationError, Test, 'bb') |
420
|
|
|
|
421
|
|
|
t = Test('aa') |
422
|
|
|
assert t.value == 'aa' |
423
|
|
|
|
424
|
|
|
|
425
|
|
|
def test_regex_validator_rev(): |
426
|
|
|
class Test(Fields.value, RegexValidate.value['aa+']): |
427
|
|
|
pass |
428
|
|
|
|
429
|
|
|
raises(ValidationError, Test, 'a') |
430
|
|
|
raises(ValidationError, Test, '') |
431
|
|
|
raises(ValidationError, Test, 'bb') |
432
|
|
|
|
433
|
|
|
t = Test('aa') |
434
|
|
|
assert t.value == 'aa' |
435
|
|
|
|
436
|
|
|
|
437
|
|
|
def test_regex_validator_incompatible_layout(): |
438
|
|
|
def test(): |
439
|
|
|
class Test(RegexValidate.value['aa+'].extra['x'], Fields.value.extar['2'].a[3].b[4].c[5].d[6]): |
440
|
|
|
pass |
441
|
|
|
raises(TypeError, test) |
442
|
|
|
|
443
|
|
|
|
444
|
|
|
def test_regex_validator_incompatible_layout_2(): |
445
|
|
|
def test(): |
446
|
|
|
class Test(RegexValidate.value['aa+'], Fields.other): |
447
|
|
|
pass |
448
|
|
|
raises(TypeError, test) |
449
|
|
|
|
450
|
|
|
|
451
|
|
|
def test_regex_validator_bad_declaration(): |
452
|
|
|
def test(): |
453
|
|
|
class Test(RegexValidate.a.b['aa+']): |
454
|
|
|
pass |
455
|
|
|
raises(TypeError, test) |
456
|
|
|
|
457
|
|
|
|
458
|
|
|
def test_regex_validator_fail_validation(): |
459
|
|
|
class Test(RegexValidate.a['aa+']): |
460
|
|
|
pass |
461
|
|
|
raises(ValidationError, Test, 'a') |
462
|
|
|
|
463
|
|
|
|
464
|
|
|
def test_regex_validator_rev_incompatible_layout(impl): |
465
|
|
|
def test(): |
466
|
|
|
class Test(impl.value, RegexValidate.balue['aa+']): |
467
|
|
|
pass |
468
|
|
|
raises(TypeError, test) |
469
|
|
|
|
470
|
|
|
|
471
|
|
|
def test_init_default_args_as_positional_args(impl): |
472
|
|
|
class MyContainer(impl.a.b[2].c[3]): |
473
|
|
|
pass |
474
|
|
|
|
475
|
|
|
assert repr(MyContainer(0, 1, 2)) == 'MyContainer(a=0, b=1, c=2)' |
476
|
|
|
|
477
|
|
|
|
478
|
|
|
def test_init_default_args_as_positional_misaligned(impl): |
479
|
|
|
class MyContainer(impl.a.b[2].c[3]): |
480
|
|
|
pass |
481
|
|
|
|
482
|
|
|
raises(TypeError, MyContainer, 0, 1, b=2) |
483
|
|
|
|
484
|
|
|
|
485
|
|
|
def test_init_default_args_as_positional_partial(impl): |
486
|
|
|
class MyContainer(impl.a.b[2].c[3]): |
487
|
|
|
pass |
488
|
|
|
|
489
|
|
|
assert repr(MyContainer(0, 1, c=2)) == 'MyContainer(a=0, b=1, c=2)' |
490
|
|
|
|
491
|
|
|
|
492
|
|
|
def test_init_unknown_kwargs(impl): |
493
|
|
|
class MyContainer(impl.a.b[2].c[3]): |
494
|
|
|
pass |
495
|
|
|
|
496
|
|
|
raises(TypeError, MyContainer, 0, 1, x=2) |
497
|
|
|
|
498
|
|
|
|
499
|
|
|
def test_init_too_many_positional(impl): |
500
|
|
|
class MyContainer(impl.a.b[2].c[3]): |
501
|
|
|
pass |
502
|
|
|
raises(TypeError, MyContainer, 0, 1, 2, 3) |
503
|
|
|
|
504
|
|
|
|
505
|
|
|
def test_convertible(): |
506
|
|
|
class TestContainer(ConvertibleFields.a.b): |
507
|
|
|
pass |
508
|
|
|
|
509
|
|
|
assert TestContainer(1, 2).as_dict == dict(a=1, b=2) |
510
|
|
|
|
511
|
|
|
|
512
|
|
|
def test_convertible_mixin(): |
513
|
|
|
class TestContainer(BareFields.a.b.c, ConvertibleMixin.a.b): |
514
|
|
|
pass |
515
|
|
|
|
516
|
|
|
assert TestContainer(1, 2, 3).as_tuple == (1, 2) |
517
|
|
|
|
518
|
|
|
|
519
|
|
|
def test_bad_make_init_func(): |
520
|
|
|
exc = raises(ValueError, make_init_func, ['a', 'b', 'c'], {'b': 1}) |
521
|
|
|
assert exc.value.args == ("Cannot have positional fields after fields with defaults." |
522
|
|
|
" Field 'c' is missing a default value!",) |
523
|
|
|
|
524
|
|
|
|
525
|
|
|
def test_multiple_inheritance(): |
526
|
|
|
class A(InheritableFields.name): |
527
|
|
|
pass |
528
|
|
|
|
529
|
|
|
class B(InheritableFields.age): |
530
|
|
|
pass |
531
|
|
|
|
532
|
|
|
class Person(A, B): |
533
|
|
|
pass |
534
|
|
|
|
535
|
|
|
Person(name='hans', age='43') |
536
|
|
|
raises(TypeError, Person, name='hans', age='43', bogus='crappo') |
537
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.