Skip to content

Commit 55cad20

Browse files
authored
Merge pull request #17 from bogdandm/decode-unicode-keys
Decode unicode and remove non-words characters in models fields names
2 parents 1a8adde + bfeef0a commit 55cad20

File tree

5 files changed

+48
-14
lines changed

5 files changed

+48
-14
lines changed

TODO.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
- [ ] dataclasses
2323
- [ ] Decorator to mark class as exclude from models merge
2424
- Other features
25-
- [ ] Decode unicode in keys
2625
- [ ] Nesting models generation
2726
- [X] Cascade (default)
2827
- [X] Flat
@@ -38,6 +37,7 @@
3837
- [X] ISO time
3938
- [X] ISO datetime
4039
- [X] Don't create metadata (J2M_ORIGINAL_FIELD) if original_field == generated_field
40+
- [X] Decode unicode in keys
4141
- [X] Cli tool
4242

4343
- Testing

json_to_models/generator.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import keyword
21
import re
32
from collections import OrderedDict
43
from typing import Any, Callable, List, Optional, Pattern, Union
@@ -8,7 +7,6 @@
87
from .dynamic_typing import (ComplexType, DDict, DList, DOptional, DUnion, MetaData, ModelPtr, Null, SingleType,
98
StringSerializable, StringSerializableRegistry, Unknown, registry)
109

11-
keywords_set = set(keyword.kwlist)
1210
_static_types = {float, bool, int}
1311

1412
class MetadataGenerator:
@@ -51,8 +49,6 @@ def _convert(self, data: dict):
5149
# Crash does not produce any useful logs and can occur any time after bad string was processed
5250
# It can be reproduced on real_apis tests (openlibrary API)
5351
convert_dict = key not in self.dict_keys_fields
54-
if key in keywords_set:
55-
key += "_"
5652
fields[key] = self._detect_type(value if not isinstance(value, str) else unidecode(value), convert_dict)
5753
return fields
5854

json_to_models/models/base.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import keyword
2+
import re
13
from typing import Iterable, List, Tuple, Type
24

35
import inflection
46
from jinja2 import Template
7+
from unidecode import unidecode
58

69
from . import INDENT, ModelsStructureType, OBJECTS_DELIMITER, indent, sort_fields
710
from ..dynamic_typing import AbsoluteModelRef, ImportPathList, MetaData, ModelMeta, compile_imports, metadata_to_typing
@@ -12,6 +15,8 @@
1215
"{% if not loop.last %}, {% endif %}" \
1316
"{% endfor %}"
1417

18+
keywords_set = set(keyword.kwlist)
19+
ones = ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']
1520

1621
def template(pattern: str, indent: str = INDENT) -> Template:
1722
"""
@@ -80,6 +85,17 @@ def decorators(self) -> List[str]:
8085
"""
8186
return []
8287

88+
@classmethod
89+
def convert_field_name(cls, name):
90+
if name in keywords_set:
91+
name += "_"
92+
name = unidecode(name)
93+
name = re.sub(r"\W", "", name)
94+
if not ('a' <= name[0].lower() <= 'z'):
95+
if '0' <= name[0] <= '9':
96+
name = ones[int(name[0])] + "_" + name[1:]
97+
return inflection.underscore(name)
98+
8399
def field_data(self, name: str, meta: MetaData, optional: bool) -> Tuple[ImportPathList, dict]:
84100
"""
85101
Form field data for template
@@ -90,8 +106,9 @@ def field_data(self, name: str, meta: MetaData, optional: bool) -> Tuple[ImportP
90106
:return: imports, field data
91107
"""
92108
imports, typing = metadata_to_typing(meta)
109+
93110
data = {
94-
"name": inflection.underscore(name),
111+
"name": self.convert_field_name(name),
95112
"type": typing
96113
}
97114
return imports, data

test/test_code_generation/test_attrs_generation.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,38 +88,56 @@ class Test:
8888
"bar": DOptional(IntString),
8989
"qwerty": FloatString,
9090
"asdfg": DOptional(int),
91-
"dict": DDict(int)
91+
"dict": DDict(int),
92+
"not": bool,
93+
"1day": int,
94+
"день_недели": str,
9295
}),
9396
"fields_data": {
9497
"foo": {
9598
"name": "foo",
9699
"type": "int",
97-
"body": f"attr.ib()"
100+
"body": "attr.ib()"
98101
},
99102
"baz": {
100103
"name": "baz",
101104
"type": "Optional[List[List[str]]]",
102-
"body": f"attr.ib(factory=list)"
105+
"body": "attr.ib(factory=list)"
103106
},
104107
"bar": {
105108
"name": "bar",
106109
"type": "Optional[IntString]",
107-
"body": f"attr.ib(default=None, converter=optional(IntString))"
110+
"body": "attr.ib(default=None, converter=optional(IntString))"
108111
},
109112
"qwerty": {
110113
"name": "qwerty",
111114
"type": "FloatString",
112-
"body": f"attr.ib(converter=FloatString)"
115+
"body": "attr.ib(converter=FloatString)"
113116
},
114117
"asdfg": {
115118
"name": "asdfg",
116119
"type": "Optional[int]",
117-
"body": f"attr.ib(default=None)"
120+
"body": "attr.ib(default=None)"
118121
},
119122
"dict": {
120123
"name": "dict",
121124
"type": "Dict[str, int]",
122-
"body": f"attr.ib()"
125+
"body": "attr.ib()"
126+
},
127+
"not": {
128+
"name": "not_",
129+
"type": "bool",
130+
"body": f"attr.ib({field_meta('not')})"
131+
},
132+
"1day": {
133+
"name": "one_day",
134+
"type": "int",
135+
"body": f"attr.ib({field_meta('1day')})"
136+
},
137+
"день_недели": {
138+
"name": "den_nedeli",
139+
"type": "str",
140+
"body": f"attr.ib({field_meta('день_недели')})"
123141
}
124142
},
125143
"generated": trim(f"""
@@ -134,6 +152,9 @@ class Test:
134152
foo: int = attr.ib()
135153
qwerty: FloatString = attr.ib(converter=FloatString)
136154
dict: Dict[str, int] = attr.ib()
155+
not_: bool = attr.ib({field_meta('not')})
156+
one_day: int = attr.ib({field_meta('1day')})
157+
den_nedeli: str = attr.ib({field_meta('день_недели')})
137158
baz: Optional[List[List[str]]] = attr.ib(factory=list)
138159
bar: Optional[IntString] = attr.ib(default=None, converter=optional(IntString))
139160
asdfg: Optional[int] = attr.ib(default=None)

test/test_generator/test_detect_type.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,5 @@ def test_convert(models_generator: MetadataGenerator):
5353
"another_dict_field_2": DDict(int),
5454
"another_dict_field_3": DDict(int),
5555
"int_field": int,
56-
"not_": bool
56+
"not": bool
5757
}

0 commit comments

Comments
 (0)