diff --git a/TODO.md b/TODO.md index 45216fd..202f2f7 100644 --- a/TODO.md +++ b/TODO.md @@ -13,10 +13,9 @@ - [ ] (Maybe in future) Extract to another module (by serializers for each dynamic typing class) - [X] attrs - complex StringSerializable converters (based on dataclass post_init converter) + - [x] post_init converters for StringSerializable types - [X] dataclasses - - [ ] post_init converters for StringSerializable types - - [X] Nested converters - - [ ] Cli argument + - [x] post_init converters for StringSerializable types - [ ] generate from_json/to_json converters - [ ] Model class -> Meta format converter - [ ] attrs @@ -26,7 +25,7 @@ - [ ] dataclasses - [ ] Decorator to mark class as exclude from models merge - Other features - - [ ] Nesting models generation + - [X] Nesting models generation - [X] Cascade (default) - [X] Flat - [ ] OptionalFieldsPolicy diff --git a/json_to_models/cli.py b/json_to_models/cli.py index 25f6230..8144e90 100644 --- a/json_to_models/cli.py +++ b/json_to_models/cli.py @@ -45,9 +45,10 @@ class Cli: } def __init__(self): - self.initialize = False + self.initialized = False self.models_data: Dict[str, Iterable[dict]] = {} # -m/-l self.enable_datetime: bool = False # --datetime + self.strings_converters: bool = False # --strings-converters self.merge_policy: List[ModelCmp] = [] # --merge self.structure_fn: STRUCTURE_FN_TYPE = None # -s self.model_generator: Type[GenericModelCodeGenerator] = None # -f & --code-generator @@ -75,6 +76,7 @@ def parse_args(self, args: List[str] = None): for model_name, lookup, path in namespace.list or () ] self.enable_datetime = namespace.datetime + self.strings_converters = namespace.strings_converters merge_policy = [m.split("_") if "_" in m else m for m in namespace.merge] structure = namespace.structure framework = namespace.framework @@ -172,7 +174,7 @@ def set_args(self, merge_policy: List[Union[List[str], str]], m = importlib.import_module(module) self.model_generator = getattr(m, cls) - self.model_generator_kwargs = {} + self.model_generator_kwargs = {} if not self.strings_converters else {'post_init_converters': True} if code_generator_kwargs_raw: for item in code_generator_kwargs_raw: if item[0] == '"': @@ -185,7 +187,7 @@ def set_args(self, merge_policy: List[Union[List[str], str]], self.dict_keys_regex = [re.compile(rf"^{r}$") for r in dict_keys_regex] if dict_keys_regex else () self.dict_keys_fields = dict_keys_fields or () - self.initialize = True + self.initialized = True @classmethod def _create_argparser(cls) -> argparse.ArgumentParser: @@ -236,6 +238,11 @@ def _create_argparser(cls) -> argparse.ArgumentParser: "Warn.: This can lead to 6-7 times slowdown on large datasets.\n" " Be sure that you really need this option.\n\n" ) + parser.add_argument( + "--strings-converters", + action="store_true", + help="Enable generation of string types converters (i.e. IsoDatetimeString or BooleanString).\n\n" + ) default_percent = f"{ModelFieldsPercentMatch.DEFAULT * 100:.0f}" default_number = f"{ModelFieldsNumberMatch.DEFAULT:.0f}" diff --git a/json_to_models/models/attr.py b/json_to_models/models/attr.py index 7cb02ed..75bc84b 100644 --- a/json_to_models/models/attr.py +++ b/json_to_models/models/attr.py @@ -15,14 +15,15 @@ class AttrsModelCodeGenerator(GenericModelCodeGenerator): ATTRS = template(f"attr.s{{% if kwargs %}}({KWAGRS_TEMPLATE}){{% endif %}}") ATTRIB = template(f"attr.ib({KWAGRS_TEMPLATE})") - def __init__(self, model: ModelMeta, meta=False, attrs_kwargs: dict = None, **kwargs): + def __init__(self, model: ModelMeta, meta=False, post_init_converters=False, attrs_kwargs: dict = None): """ :param model: ModelMeta instance :param meta: Enable generation of metadata as attrib argument + :param post_init_converters: Enable generation of type converters in __post_init__ methods :param attrs_kwargs: kwargs for @attr.s() decorators :param kwargs: """ - super().__init__(model, **kwargs) + super().__init__(model, post_init_converters) self.no_meta = not meta self.attrs_kwargs = attrs_kwargs or {} diff --git a/json_to_models/models/base.py b/json_to_models/models/base.py index afeca35..7b983a0 100644 --- a/json_to_models/models/base.py +++ b/json_to_models/models/base.py @@ -71,7 +71,7 @@ class {{ name }}: % KWAGRS_TEMPLATE) FIELD: Template = template("{{name}}: {{type}}{% if body %} = {{ body }}{% endif %}") - def __init__(self, model: ModelMeta, post_init_converters=False, **kwargs): + def __init__(self, model: ModelMeta, post_init_converters=False): self.model = model self.post_init_converters = post_init_converters diff --git a/json_to_models/models/string_converters.py b/json_to_models/models/string_converters.py index 20e86c8..04326b7 100644 --- a/json_to_models/models/string_converters.py +++ b/json_to_models/models/string_converters.py @@ -5,6 +5,7 @@ from . import ClassType from ..dynamic_typing import (BaseType, DDict, DList, DOptional, DUnion, MetaData, ModelMeta, ModelPtr, StringSerializable) +from ..dynamic_typing.base import NoneType def convert_strings(str_field_paths: List[str], class_type: Optional[ClassType] = None, @@ -165,6 +166,8 @@ def get_string_field_paths(model: ModelMeta) -> List[Tuple[str, List[str]]]: # We could not resolve Union paths = [] break + elif cls is NoneType: + continue else: raise TypeError(f"Unsupported meta-type for converter path {cls}") diff --git a/test/test_cli/test_script.py b/test/test_cli/test_script.py index f674b13..87e7e0b 100644 --- a/test/test_cli/test_script.py +++ b/test/test_cli/test_script.py @@ -70,6 +70,11 @@ def test_help(): id="gists_merge_policy"), pytest.param(f"""{executable} -m Gist "{tmp_path / '*.gist'}" --dkf files --merge exact""", id="gists_no_merge"), + pytest.param(f"""{executable} -m Gist "{tmp_path / '*.gist'}" --dkf files --datetime --strings-converters""", + id="gists_strings_converters"), + + pytest.param(f"""{executable} -l User - "{test_data_path / 'users.json'}" --strings-converters""", + id="users_strings_converters"), ]