Skip to content

Commit 51b6a3f

Browse files
committed
add new separator mixed witout dot
1 parent a62d535 commit 51b6a3f

File tree

3 files changed

+314
-11
lines changed

3 files changed

+314
-11
lines changed

README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,10 @@ Attributes where sub keys are other than full numbers are converted into Python
144144
data = {
145145
'the[0].chained.key[0].are.awesome[0][0]': 'im here !!'
146146
}
147+
# with "mixed-dot" separator option (same as 'mixed' but without dot after list to object):
148+
data = {
149+
'the[0]chained.key[0]are.awesome[0][0]': 'im here !!'
150+
}
147151
```
148152

149153

@@ -167,10 +171,11 @@ For this to work perfectly, you must follow the following rules:
167171
```python
168172
{
169173
# Separators:
170-
# with bracket: article[title][authors][0]: "jhon doe"
171-
# with dot: article.title.authors.0: "jhon doe"
172-
# with mixed: article.title.authors[0]: "jhon doe"
173-
'separator': 'bracket' or 'dot' or 'mixed', # default is bracket
174+
# with bracket: article[0][title][authors][0]: "jhon doe"
175+
# with dot: article.0.title.authors.0: "jhon doe"
176+
# with mixed: article[0].title.authors[0]: "jhon doe"
177+
# with mixed-dot: article[0]title.authors[0]: "jhon doe"
178+
'separator': 'bracket' or 'dot' or 'mixed' or 'mixed-dot', # default is bracket
174179

175180

176181
# raise a expections when you have duplicate keys

nested_multipart_parser/parser.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,21 @@ def _merge_options(self, options):
2020
self._options = options
2121

2222
assert self._options.get("separator", "dot") in [
23-
"dot", "bracket", "mixed"]
23+
"dot", "bracket", "mixed", "mixed-dot"]
2424
assert isinstance(self._options.get("raise_duplicate", False), bool)
2525
assert isinstance(self._options.get("assign_duplicate", False), bool)
2626

2727
self.__is_dot = False
2828
self.__is_mixed = False
2929
self.__is_bracket = False
30+
self.__is_mixed_dot = False
3031
if self._options["separator"] == "dot":
3132
self.__is_dot = True
3233
elif self._options["separator"] == "mixed":
3334
self.__is_mixed = True
35+
elif self._options["separator"] == "mixed-dot":
36+
self.__is_mixed_dot = True
37+
self.__is_mixed = True
3438
else:
3539
self.__is_bracket = True
3640
self._reg = re.compile(r"\[|\]")
@@ -54,13 +58,9 @@ def span(key, i):
5458
key = key[idx:]
5559

5660
i = 0
61+
last_is_list = False
5762
while i < len(key):
58-
if key[i] == '.':
59-
i += 1
60-
idx = span(key, i)
61-
keys.append(key[i: idx])
62-
i = idx
63-
elif key[i] == '[':
63+
if key[i] == '[':
6464
i += 1
6565
idx = span(key, i)
6666
if key[idx] != ']':
@@ -72,9 +72,22 @@ def span(key, i):
7272
f"invalid format key '{full_keys}', list key is not a valid number at position {i + pos}")
7373
keys.append(int(key[i: idx]))
7474
i = idx + 1
75+
last_is_list = True
7576
elif key[i] == ']':
7677
raise ValueError(
7778
f"invalid format key '{full_keys}', not start with bracket at position {i + pos}")
79+
elif (key[i] == '.' and not self.__is_mixed_dot) or (
80+
self.__is_mixed_dot and (
81+
(key[i] != '.' and last_is_list) or
82+
(key[i] == '.' and not last_is_list)
83+
)
84+
):
85+
if not self.__is_mixed_dot or not last_is_list:
86+
i += 1
87+
idx = span(key, i)
88+
keys.append(key[i: idx])
89+
i = idx
90+
last_is_list = False
7891
else:
7992
raise ValueError(
8093
f"invalid format key '{full_keys}', invalid char at position {i + pos}")

tests/test_mixed_dot_separator.py

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
from nested_multipart_parser import NestedParser
2+
from unittest import TestCase
3+
4+
5+
class TestSettingsSeparatorMixed(TestCase):
6+
7+
def test_assign_duplicate_list(self):
8+
data = {
9+
"title": 42,
10+
"title[0]": 101
11+
}
12+
p = NestedParser(
13+
data, {"raise_duplicate": False, "assign_duplicate": True, "separator": "mixed-dot"})
14+
self.assertTrue(p.is_valid())
15+
expected = {
16+
"title": [101]
17+
}
18+
self.assertEqual(p.validate_data, expected)
19+
20+
def test_assign_duplicate_number_after_list(self):
21+
data = {
22+
"title[0]": 101,
23+
"title": 42,
24+
}
25+
p = NestedParser(
26+
data, {"raise_duplicate": False, "assign_duplicate": True, "separator": "mixed-dot"})
27+
self.assertTrue(p.is_valid())
28+
expected = {
29+
"title": 42
30+
}
31+
self.assertEqual(p.validate_data, expected)
32+
33+
def test_assign_nested_duplicate_number_after_list(self):
34+
data = {
35+
"title[0]sub[0]": 101,
36+
"title[0]sub": 42,
37+
}
38+
p = NestedParser(
39+
data, {"raise_duplicate": False, "assign_duplicate": True, "separator": "mixed-dot"})
40+
self.assertTrue(p.is_valid())
41+
expected = {
42+
"title": [
43+
{
44+
"sub": 42
45+
}
46+
]
47+
}
48+
self.assertEqual(p.validate_data, expected)
49+
50+
def test_assign_nested_duplicate_number_after_list2(self):
51+
data = {
52+
"title[0]sub": 42,
53+
"title[0]sub[0]": 101,
54+
}
55+
p = NestedParser(
56+
data, {"raise_duplicate": False, "assign_duplicate": True, "separator": "mixed-dot"})
57+
self.assertTrue(p.is_valid())
58+
expected = {
59+
"title": [
60+
{
61+
"sub": [101]
62+
}
63+
]
64+
}
65+
self.assertEqual(p.validate_data, expected)
66+
67+
def test_assign_nested_duplicate_number_after_dict(self):
68+
data = {
69+
"title[0]sub": 42,
70+
"title[0]sub.title": 101,
71+
}
72+
p = NestedParser(
73+
data, {"raise_duplicate": False, "assign_duplicate": True, "separator": "mixed-dot"})
74+
self.assertTrue(p.is_valid())
75+
expected = {
76+
"title": [
77+
{
78+
"sub": {
79+
"title": 101
80+
}
81+
}
82+
]
83+
}
84+
self.assertEqual(p.validate_data, expected)
85+
86+
def test_assign_nested_duplicate_number_after_dict2(self):
87+
data = {
88+
"title[0]sub.title": 101,
89+
"title[0]sub": 42,
90+
}
91+
p = NestedParser(
92+
data, {"raise_duplicate": False, "assign_duplicate": True, "separator": "mixed-dot"})
93+
self.assertTrue(p.is_valid())
94+
expected = {
95+
"title": [
96+
{
97+
"sub": 42
98+
}
99+
]
100+
}
101+
self.assertEqual(p.validate_data, expected)
102+
103+
def test_mixed_spearator(self):
104+
data = {
105+
'title': 'lalal',
106+
'article.object': 'lalal',
107+
}
108+
parser = NestedParser(data, {"separator": "mixed-dot"})
109+
self.assertTrue(parser.is_valid())
110+
expected = {
111+
"title": 'lalal',
112+
"article": {
113+
"object": "lalal"
114+
}
115+
}
116+
self.assertEqual(expected, parser.validate_data)
117+
118+
def test_mixed_int_object(self):
119+
data = {
120+
'title': 'lalal',
121+
'article.0': 'lalal',
122+
}
123+
parser = NestedParser(data, {"separator": "mixed-dot"})
124+
self.assertTrue(parser.is_valid())
125+
expected = {
126+
"title": 'lalal',
127+
"article": {
128+
"0": "lalal"
129+
}
130+
}
131+
self.assertEqual(expected, parser.validate_data)
132+
133+
def test_mixed_int_list(self):
134+
data = {
135+
'title': 'lalal',
136+
'article[0]': 'lalal',
137+
}
138+
parser = NestedParser(data, {"separator": "mixed-dot"})
139+
self.assertTrue(parser.is_valid())
140+
expected = {
141+
"title": 'lalal',
142+
"article": [
143+
"lalal"
144+
]
145+
}
146+
self.assertEqual(expected, parser.validate_data)
147+
148+
def test_real(self):
149+
data = {
150+
'title': 'title',
151+
'date': "time",
152+
'langs[0]id': "id",
153+
'langs[0]title': 'title',
154+
'langs[0]description': 'description',
155+
'langs[0]language': "language",
156+
'langs[1]id': "id1",
157+
'langs[1]title': 'title1',
158+
'langs[1]description': 'description1',
159+
'langs[1]language': "language1"
160+
}
161+
parser = NestedParser(data, {"separator": "mixed-dot"})
162+
self.assertTrue(parser.is_valid())
163+
expected = {
164+
'title': 'title',
165+
'date': "time",
166+
'langs': [
167+
{
168+
'id': 'id',
169+
'title': 'title',
170+
'description': 'description',
171+
'language': 'language'
172+
},
173+
{
174+
'id': 'id1',
175+
'title': 'title1',
176+
'description': 'description1',
177+
'language': 'language1'
178+
}
179+
]
180+
}
181+
self.assertDictEqual(parser.validate_data, expected)
182+
183+
def test_mixed_invalid_list_index(self):
184+
data = {
185+
'title': 'lalal',
186+
'article[0f]': 'lalal',
187+
}
188+
parser = NestedParser(data, {"separator": "mixed-dot"})
189+
self.assertFalse(parser.is_valid())
190+
191+
def test_mixed_invalid_list_empty_index(self):
192+
data = {
193+
'title': 'lalal',
194+
'article[]': 'lalal',
195+
}
196+
parser = NestedParser(data, {"separator": "mixed-dot"})
197+
self.assertFalse(parser.is_valid())
198+
199+
def test_mixed_invalid_bracket(self):
200+
data = {
201+
'title': 'lalal',
202+
'article[': 'lalal',
203+
}
204+
parser = NestedParser(data, {"separator": "mixed-dot"})
205+
self.assertFalse(parser.is_valid())
206+
207+
def test_mixed_invalid_bracket2(self):
208+
data = {
209+
'title': 'lalal',
210+
'article]': 'lalal',
211+
}
212+
parser = NestedParser(data, {"separator": "mixed-dot"})
213+
self.assertFalse(parser.is_valid())
214+
215+
def test_mixed_invalid_list_dot(self):
216+
data = {
217+
'title': 'lalal',
218+
'article[3.]': 'lalal',
219+
}
220+
parser = NestedParser(data, {"separator": "mixed-dot"})
221+
self.assertFalse(parser.is_valid())
222+
223+
def test_mixed_invalid_list_negative_index(self):
224+
data = {
225+
'title': 'lalal',
226+
'article[-3]': 'lalal',
227+
}
228+
parser = NestedParser(data, {"separator": "mixed-dot"})
229+
self.assertFalse(parser.is_valid())
230+
231+
def test_mixed_invalid_object(self):
232+
data = {
233+
'title': 'lalal',
234+
'article..op': 'lalal',
235+
}
236+
parser = NestedParser(data, {"separator": "mixed-dot"})
237+
self.assertFalse(parser.is_valid())
238+
239+
def test_mixed_invalid_object2(self):
240+
data = {
241+
'title': 'lalal',
242+
'article.op.': 'lalal',
243+
}
244+
parser = NestedParser(data, {"separator": "mixed-dot"})
245+
self.assertFalse(parser.is_valid())
246+
247+
def test_mixed_invalid_object3(self):
248+
data = {
249+
'title': 'lalal',
250+
'article[0].op': 'lalal',
251+
}
252+
parser = NestedParser(data, {"separator": "mixed-dot"})
253+
self.assertFalse(parser.is_valid())
254+
255+
def test_mixed_invalid_object4(self):
256+
data = {
257+
'title': 'lalal',
258+
'article.op..': 'lalal',
259+
}
260+
parser = NestedParser(data, {"separator": "mixed-dot"})
261+
self.assertFalse(parser.is_valid())
262+
263+
def test_mixed_invalid_list_with_object_dot(self):
264+
data = {
265+
'title': 'lalal',
266+
'article[0].op..': 'lalal',
267+
}
268+
parser = NestedParser(data, {"separator": "mixed-dot"})
269+
self.assertFalse(parser.is_valid())
270+
271+
def test_mixed_invalid_list_with_object_dot2(self):
272+
data = {
273+
'title': 'lalal',
274+
'article[0]op[0]e.': 'lalal',
275+
}
276+
parser = NestedParser(data, {"separator": "mixed-dot"})
277+
self.assertFalse(parser.is_valid())
278+
279+
def test_mixed_invalid_list_with_object_dot3(self):
280+
data = {
281+
'title': 'lalal',
282+
'article.op.[0]': 'lalal',
283+
}
284+
parser = NestedParser(data, {"separator": "mixed-dot"})
285+
self.assertFalse(parser.is_valid())

0 commit comments

Comments
 (0)