Skip to content

Commit

Permalink
Attrs converters
Browse files Browse the repository at this point in the history
  • Loading branch information
bogdandm committed Apr 24, 2019
1 parent b02197c commit a46865d
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 8 deletions.
19 changes: 16 additions & 3 deletions json_to_models/models/attr.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ def __init__(self, model: ModelMeta, meta=False, attrs_kwargs: dict = None, **kw

@property
def decorators(self) -> Tuple[ImportPathList, List[str]]:
return [('attr', None)], [self.ATTRS.render(kwargs=self.attrs_kwargs)]
imports, decorators = super().decorators
imports.append(('attr', None))
decorators.insert(0, self.ATTRS.render(kwargs=self.attrs_kwargs))
return imports, decorators

def field_data(self, name: str, meta: MetaData, optional: bool) -> Tuple[ImportPathList, dict]:
"""
Expand All @@ -49,13 +52,23 @@ def field_data(self, name: str, meta: MetaData, optional: bool) -> Tuple[ImportP
body_kwargs["factory"] = "dict"
else:
body_kwargs["default"] = "None"
if isclass(meta.type) and issubclass(meta.type, StringSerializable):
if isclass(meta.type) and issubclass(meta.type, StringSerializable) and not self.post_init_converters:
body_kwargs["converter"] = f"optional({meta.type.__name__})"
imports.append(("attr.converter", "optional"))
elif isclass(meta) and issubclass(meta, StringSerializable):
elif isclass(meta) and issubclass(meta, StringSerializable) and not self.post_init_converters:
body_kwargs["converter"] = meta.__name__

if not self.no_meta and name != data["name"]:
body_kwargs["metadata"] = {METADATA_FIELD_NAME: name}
data["body"] = self.ATTRIB.render(kwargs=sort_kwargs(body_kwargs, DEFAULT_ORDER))
return imports, data

@property
def convert_strings_kwargs(self) -> Tuple[ImportPathList, dict]:
"""
:return: Imports and Dict with kw-arguments for `json_to_models.models.string_converters.convert_strings` decorator.
"""
imports, kwargs = super().convert_strings_kwargs
imports.append(('json_to_models.models', ['ClassType']))
kwargs["class_type"] = 'ClassType.Attrs'
return imports, kwargs
41 changes: 36 additions & 5 deletions test/test_code_generation/test_attrs_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import pytest

from json_to_models.dynamic_typing import (DDict, DList, DOptional, FloatString, IntString, ModelMeta, compile_imports)
from json_to_models.dynamic_typing import (DDict, DList, DOptional, DUnion, FloatString, IntString, ModelMeta,
compile_imports)
from json_to_models.models.attr import AttrsModelCodeGenerator, DEFAULT_ORDER
from json_to_models.models.base import METADATA_FIELD_NAME, generate_code, sort_kwargs
from json_to_models.models.structure import sort_fields
Expand Down Expand Up @@ -142,23 +143,53 @@ class Test:
},
"generated": trim(f"""
import attr
from attr.converter import optional
from json_to_models.dynamic_typing import FloatString, IntString
from json_to_models.models import ClassType
from json_to_models.models.string_converters import convert_strings
from typing import Dict, List, Optional
@attr.s
@convert_strings(['bar#O.S', 'qwerty'], class_type=ClassType.Attrs)
class Test:
foo: int = attr.ib()
qwerty: FloatString = attr.ib(converter=FloatString)
qwerty: FloatString = attr.ib()
dict: Dict[str, int] = attr.ib()
not_: bool = attr.ib({field_meta('not')})
one_day: int = attr.ib({field_meta('1day')})
den_nedeli: str = attr.ib({field_meta('день_недели')})
baz: Optional[List[List[str]]] = attr.ib(factory=list)
bar: Optional[IntString] = attr.ib(default=None, converter=optional(IntString))
bar: Optional[IntString] = attr.ib(default=None)
asdfg: Optional[int] = attr.ib(default=None)
""")
},
"converters": {
"model": ("Test", {
"a": int,
"b": IntString,
"c": DOptional(FloatString),
"d": DList(DList(DList(IntString))),
"e": DDict(IntString),
"u": DUnion(DDict(IntString), DList(DList(IntString))),
}),
"generated": trim("""
import attr
from json_to_models.dynamic_typing import FloatString, IntString
from json_to_models.models import ClassType
from json_to_models.models.string_converters import convert_strings
from typing import Dict, List, Optional, Union
@attr.s
@convert_strings(['b', 'c#O.S', 'd#L.L.L.S', 'e#D.S'], class_type=ClassType.Attrs)
class Test:
a: int = attr.ib()
b: IntString = attr.ib()
d: List[List[List[IntString]]] = attr.ib()
e: Dict[str, IntString] = attr.ib()
u: Union[Dict[str, IntString], List[List[IntString]]] = attr.ib()
c: Optional[FloatString] = attr.ib(default=None)
""")
}
}

Expand Down Expand Up @@ -200,5 +231,5 @@ def test_fields_attr(value: ModelMeta, expected: dict):
@pytest.mark.parametrize("value,expected", test_data_unzip["generated"])
def test_generated_attr(value: ModelMeta, expected: str):
generated = generate_code(([{"model": value, "nested": []}], {}), AttrsModelCodeGenerator,
class_generator_kwargs={'meta': True})
class_generator_kwargs={'meta': True, 'post_init_converters': True})
assert generated.rstrip() == expected, generated
15 changes: 15 additions & 0 deletions test/test_code_generation/test_string_converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,18 @@ class A:

assert a == A(1, [1, 2, 3, 4], {'s': 2, 'w': 3}, None,
[{'a': [1, 2]}, {'b': [3, 2]}])

@attr.s
@convert_strings(['x', 'y#L.S', 'z#D.S', 'a#O.S', 'b#O.L.D.L.S'], class_type=ClassType.Attrs)
class A:
x: IntString = attr.ib()
y: List[IntString] = attr.ib()
z: Dict[str, IntString] = attr.ib()
a: Optional[IntString] = attr.ib(default=None)
b: Optional[List[Dict[str, List[IntString]]]] = attr.ib(default=None)

a = A('1', '1234', {'s': '2', 'w': '3'}, None,
[{'a': ['1', '2']}, {'b': ['3', '2']}])

assert a == A(1, [1, 2, 3, 4], {'s': 2, 'w': 3}, None,
[{'a': [1, 2]}, {'b': [3, 2]}])

0 comments on commit a46865d

Please sign in to comment.