Skip to content

Commit 94086ba

Browse files
committed
Implement skip_if field attribute
1 parent 1c62ee9 commit 94086ba

File tree

5 files changed

+76
-23
lines changed

5 files changed

+76
-23
lines changed

README.md

+23-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ From Json: Hoge(i=10, s='hoge', f=100.0, b=True)
6161
* [Case conversion](#case-conversion) e.g. camelCase, kebab-case
6262
* Field attributes
6363
* [Rename](#rename-field)
64-
* [Skip](#skip-field)
64+
* [Skip](#skip)
65+
* [Skip-if](#skip-if)
6566
* [Skip if value is evaluated as False](#skip-if-value-is-evaluated-as-false)
6667

6768
### Case conversion
@@ -91,7 +92,7 @@ From Json: Hoge(i=10, s='hoge', f=100.0, b=True)
9192

9293
For complete example, please see [./examples/rename.py](./examples/rename.py)
9394

94-
### Skip field
95+
### Skip
9596

9697
```python
9798
>>> @serialize
@@ -110,6 +111,26 @@ For complete example, please see [./examples/rename.py](./examples/rename.py)
110111

111112
For complete example, please see [./examples/skip.py](./examples/skip.py)
112113

114+
### Skip if
115+
116+
```python
117+
>>> @serialize
118+
... @dataclass
119+
... class World:
120+
... player: str
121+
... buddy: str = field(default='', metadata={'serde_skip_if': lambda v: v == 'Pikachu'})
122+
123+
>>> world = World('satoshi', 'Pikachu')
124+
>>> to_json(world)
125+
'{"player": "satoshi"}'
126+
127+
>>> world = World('green', 'Charmander')
128+
>>> print(to_json(world))
129+
'{"player": "green", "buddy": "Charmander"}'
130+
```
131+
132+
For complete example, please see [./examples/skip.py](./examples/skip.py)
133+
113134
### Skip if value is evaluated as False
114135

115136
```python

examples/skip.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class Resource:
2727
class World:
2828
player: str
2929
enemies: List[str] = field(default_factory=list, metadata={'serde_skip_if_false': True})
30+
buddy: str = field(default='', metadata={'serde_skip_if': lambda v: v == 'Pikachu'})
3031

3132

3233
def main():
@@ -36,10 +37,10 @@ def main():
3637
]
3738
print(to_json(resources))
3839

39-
world = World('satoshi', ['Rattata', 'Pidgey'])
40+
world = World('satoshi', ['Rattata', 'Pidgey'], 'Pikachu')
4041
print(to_json(world))
4142

42-
world = World('green', [])
43+
world = World('green', [], 'Charmander')
4344
print(to_json(world))
4445

4546

examples/yamlfile.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
$ pipenv run yamlfile.py
99
"""
1010
from typing import List, Dict, Optional, Union
11-
from dataclasses import dataclass
11+
from dataclasses import dataclass, field
1212
from serde import deserialize
1313
from serde.yaml import from_yaml
1414

@@ -25,8 +25,7 @@ class Info:
2525
@dataclass
2626
class Parameter:
2727
name: str
28-
# in is reserved keyword.
29-
# in: str
28+
infield: str = field(metadata={'serde_rename': 'in'})
3029
type: str
3130
required: bool
3231

serde/se.py

+47-15
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
from dataclasses import Field as DataclassField
99
from dataclasses import asdict as _asdict
1010
from dataclasses import astuple as _astuple
11-
from dataclasses import dataclass, fields as dataclass_fields, is_dataclass
12-
from typing import Any, ClassVar, Dict, List, Optional, Tuple, Type
11+
from dataclasses import dataclass
12+
from dataclasses import fields as dataclass_fields
13+
from dataclasses import is_dataclass
14+
from typing import Any, Callable, ClassVar, Dict, Iterator, List, Optional, Tuple, Type
1315

1416
import jinja2
1517
import stringcase
@@ -75,8 +77,13 @@ def serialize(self, ser, **opts) -> None:
7577
return ser.serialize(self, **opts)
7678

7779
setattr(cls, SE_NAME, serialize)
78-
cls = se_func(cls, TO_ITER, render_astuple(cls))
79-
cls = se_func(cls, TO_DICT, render_asdict(cls, rename_all))
80+
81+
g: Dict[str, Any] = globals().copy()
82+
for f in fields(cls):
83+
if f.skip_if:
84+
g[f.skip_if.mangled] = f.skip_if
85+
cls = se_func(cls, TO_ITER, render_astuple(cls), g)
86+
cls = se_func(cls, TO_DICT, render_asdict(cls, rename_all), g)
8087
return cls
8188

8289
if _cls is None:
@@ -185,11 +192,28 @@ class Field:
185192
case: Optional[str] = None
186193
rename: Optional[str] = None
187194
skip: Optional[bool] = None
195+
skip_if: Optional[Callable[[Any], bool]] = None
188196
skip_if_false: Optional[bool] = None
189197

190198
@staticmethod
191199
def from_dataclass(f: DataclassField) -> '':
192-
return Field(f.type, f.name, rename = f.metadata.get('serde_rename'), skip = f.metadata.get('serde_skip'), skip_if_false = f.metadata.get('serde_skip_if_false'))
200+
if f.metadata.get('serde_skip_if_false'):
201+
skip_if_false = lambda v: not bool(v)
202+
skip_if_false.mangled = Field.mangle(f, 'skip_if')
203+
else:
204+
skip_if_false = None
205+
206+
skip_if = f.metadata.get('serde_skip_if')
207+
if skip_if:
208+
skip_if.mangled = Field.mangle(f, 'skip_if')
209+
210+
return Field(
211+
f.type,
212+
f.name,
213+
rename=f.metadata.get('serde_rename'),
214+
skip=f.metadata.get('serde_skip'),
215+
skip_if=skip_if or skip_if_false,
216+
)
193217

194218
@property
195219
def varname(self) -> str:
@@ -203,9 +227,13 @@ def __getitem__(self, n) -> 'Field':
203227
typ = type_args(self.type)[n]
204228
return Field(typ, None)
205229

230+
@staticmethod
231+
def mangle(field: DataclassField, name: str) -> str:
232+
return f'{field.name}_{name}'
233+
206234

207-
def fields(cls: Type) -> List[Field]:
208-
return [Field.from_dataclass(f) for f in dataclass_fields(cls)]
235+
def fields(cls: Type) -> Iterator[Field]:
236+
return iter(Field.from_dataclass(f) for f in dataclass_fields(cls))
209237

210238

211239
def to_arg(f: Field) -> Field:
@@ -248,14 +276,16 @@ def {{func}}(obj):
248276
{% if cls|is_dataclass %}
249277
res = {}
250278
{% for f in cls|fields -%}
251-
{% if not f.skip|default(False) %}
252-
{% if f.skip_if_false|default(False) %}
253-
if {{f|arg|rvalue()}}:
279+
280+
{% if not f.skip %}
281+
{% if f.skip_if %}
282+
if not {{f.skip_if.mangled}}({{f|arg|rvalue()}}):
254283
res["{{f|case}}"] = {{f|arg|rvalue()}}
255284
{% else %}
256285
res["{{f|case}}"] = {{f|arg|rvalue()}}
257286
{% endif %}
258287
{% endif %}
288+
259289
{% endfor -%}
260290
return res
261291
{% endif %}
@@ -359,16 +389,18 @@ def primitive(self, arg: Field) -> str:
359389
return f'{arg.varname}'
360390

361391

362-
def se_func(cls: Type[T], func: str, code: str) -> Type[T]:
392+
def se_func(cls: Type[T], func: str, code: str, g: Dict = None, local: Dict = None) -> Type[T]:
363393
"""
364394
Generate function to serialize into an object.
365395
"""
366-
g: Dict[str, Any] = globals().copy()
367-
368396
# Generate serialize function.
369-
code = gen(code, g, cls=cls)
397+
if not g:
398+
g = globals().copy()
399+
if not local:
400+
local = locals().copy()
401+
code = gen(code, g, local, cls=cls)
370402

371-
setattr(cls, func, g[func])
403+
setattr(cls, func, local[func])
372404
if SETTINGS['debug']:
373405
hidden = getattr(cls, HIDDEN_NAME)
374406
hidden.code[func] = code

serde/toml.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def deserialize(cls, s, **opts):
2020
return toml.loads(s)
2121

2222

23-
def to_toml(obj, se: Type[TomlSerializer]=TomlSerializer) -> str:
23+
def to_toml(obj, se: Type[TomlSerializer] = TomlSerializer) -> str:
2424
"""
2525
Take an object and return toml string.
2626

0 commit comments

Comments
 (0)