4
4
5
5
from datetime import datetime
6
6
7
+ import pytest
8
+
7
9
import attr
8
10
9
11
@@ -30,7 +32,7 @@ class C:
30
32
y = attr .ib (type = int )
31
33
z : float = attr .ib ()
32
34
33
- assert results == [("x" , None ), ("y" , int ), ("z" , float )]
35
+ assert [("x" , None ), ("y" , int ), ("z" , float )] == results
34
36
35
37
def test_hook_applied_auto_attrib (self ):
36
38
"""
@@ -49,7 +51,7 @@ class C:
49
51
x : int
50
52
y : str = attr .ib ()
51
53
52
- assert results == [("x" , int ), ("y" , str )]
54
+ assert [("x" , int ), ("y" , str )] == results
53
55
54
56
def test_hook_applied_modify_attrib (self ):
55
57
"""
@@ -66,7 +68,8 @@ class C:
66
68
y : float
67
69
68
70
c = C (x = "3" , y = "3.14" )
69
- assert c == C (x = 3 , y = 3.14 )
71
+
72
+ assert C (x = 3 , y = 3.14 ) == c
70
73
71
74
def test_hook_remove_field (self ):
72
75
"""
@@ -82,7 +85,7 @@ class C:
82
85
x : int
83
86
y : float
84
87
85
- assert attr .asdict (C (2.7 )) == { "y" : 2.7 }
88
+ assert { "y" : 2.7 } == attr .asdict (C (2.7 ))
86
89
87
90
def test_hook_add_field (self ):
88
91
"""
@@ -98,7 +101,7 @@ def hook(cls, attribs):
98
101
class C :
99
102
x : int
100
103
101
- assert attr . asdict ( C ( 1 , 2 )) == {"x" : 1 , "new" : 2 }
104
+ assert {"x" : 1 , "new" : 2 } == attr . asdict ( C ( 1 , 2 ))
102
105
103
106
def test_hook_override_alias (self ):
104
107
"""
@@ -118,13 +121,73 @@ class NameCase:
118
121
1 , 2 , 3
119
122
)
120
123
124
+ def test_hook_reorder_fields (self ):
125
+ """
126
+ It is possible to reorder fields via the hook.
127
+ """
128
+
129
+ def hook (cls , attribs ):
130
+ return sorted (attribs , key = lambda x : x .metadata ["field_order" ])
131
+
132
+ @attr .s (field_transformer = hook )
133
+ class C :
134
+ x : int = attr .ib (metadata = {"field_order" : 1 })
135
+ y : int = attr .ib (metadata = {"field_order" : 0 })
136
+
137
+ assert {"x" : 0 , "y" : 1 } == attr .asdict (C (1 , 0 ))
138
+
139
+ def test_hook_reorder_fields_before_order_check (self ):
140
+ """
141
+ It is possible to reorder fields via the hook before order-based errors are raised.
142
+
143
+ Regression test for #1147.
144
+ """
145
+
146
+ def hook (cls , attribs ):
147
+ return sorted (attribs , key = lambda x : x .metadata ["field_order" ])
148
+
149
+ @attr .s (field_transformer = hook )
150
+ class C :
151
+ x : int = attr .ib (metadata = {"field_order" : 1 }, default = 0 )
152
+ y : int = attr .ib (metadata = {"field_order" : 0 })
153
+
154
+ assert {"x" : 0 , "y" : 1 } == attr .asdict (C (1 ))
155
+
156
+ def test_hook_conflicting_defaults_after_reorder (self ):
157
+ """
158
+ Raises `ValueError` if attributes with defaults are followed by
159
+ mandatory attributes after the hook reorders fields.
160
+
161
+ Regression test for #1147.
162
+ """
163
+
164
+ def hook (cls , attribs ):
165
+ return sorted (attribs , key = lambda x : x .metadata ["field_order" ])
166
+
167
+ with pytest .raises (ValueError ) as e :
168
+
169
+ @attr .s (field_transformer = hook )
170
+ class C :
171
+ x : int = attr .ib (metadata = {"field_order" : 1 })
172
+ y : int = attr .ib (metadata = {"field_order" : 0 }, default = 0 )
173
+
174
+ assert (
175
+ "No mandatory attributes allowed after an attribute with a "
176
+ "default value or factory. Attribute in question: Attribute"
177
+ "(name='x', default=NOTHING, validator=None, repr=True, "
178
+ "eq=True, eq_key=None, order=True, order_key=None, "
179
+ "hash=None, init=True, "
180
+ "metadata=mappingproxy({'field_order': 1}), type='int', converter=None, "
181
+ "kw_only=False, inherited=False, on_setattr=None, alias=None)" ,
182
+ ) == e .value .args
183
+
121
184
def test_hook_with_inheritance (self ):
122
185
"""
123
186
The hook receives all fields from base classes.
124
187
"""
125
188
126
189
def hook (cls , attribs ):
127
- assert [a .name for a in attribs ] == [ "x" , "y" ]
190
+ assert ["x" , "y" ] == [ a .name for a in attribs ]
128
191
# Remove Base' "x"
129
192
return attribs [1 :]
130
193
@@ -136,7 +199,7 @@ class Base:
136
199
class Sub (Base ):
137
200
y : int
138
201
139
- assert attr .asdict (Sub (2 )) == { "y" : 2 }
202
+ assert { "y" : 2 } == attr .asdict (Sub (2 ))
140
203
141
204
def test_attrs_attrclass (self ):
142
205
"""
@@ -151,7 +214,7 @@ class C:
151
214
x : int
152
215
153
216
fields_type = type (attr .fields (C ))
154
- assert fields_type . __name__ == "CAttributes"
217
+ assert "CAttributes" == fields_type . __name__
155
218
assert issubclass (fields_type , tuple )
156
219
157
220
@@ -187,12 +250,12 @@ class Parent:
187
250
)
188
251
189
252
result = attr .asdict (inst , value_serializer = hook )
190
- assert result == {
253
+ assert {
191
254
"a" : {"x" : 1 , "y" : ["2020-07-01T00:00:00" ]},
192
255
"b" : [{"x" : 2 , "y" : ["2020-07-02T00:00:00" ]}],
193
256
"c" : {"spam" : {"x" : 3 , "y" : ["2020-07-03T00:00:00" ]}},
194
257
"d" : {"eggs" : "2020-07-04T00:00:00" },
195
- }
258
+ } == result
196
259
197
260
def test_asdict_calls (self ):
198
261
"""
@@ -217,12 +280,12 @@ class Parent:
217
280
inst = Parent (a = Child (1 ), b = [Child (2 )], c = {"spam" : Child (3 )})
218
281
219
282
attr .asdict (inst , value_serializer = hook )
220
- assert calls == [
283
+ assert [
221
284
(inst , "a" , inst .a ),
222
285
(inst .a , "x" , inst .a .x ),
223
286
(inst , "b" , inst .b ),
224
287
(inst .b [0 ], "x" , inst .b [0 ].x ),
225
288
(inst , "c" , inst .c ),
226
289
(None , None , "spam" ),
227
290
(inst .c ["spam" ], "x" , inst .c ["spam" ].x ),
228
- ]
291
+ ] == calls
0 commit comments