-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
#8 feat empty dict/list
- Loading branch information
Showing
15 changed files
with
893 additions
and
926 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,3 @@ | ||
from .parser import NestedParser | ||
|
||
__all__ = [ | ||
'NestedParser' | ||
] | ||
__all__ = ["NestedParser"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
class NestedDeclare: | ||
"""Create ditc/list wihout order""" | ||
|
||
def __init__(self, _type=None, options=None): | ||
self._elements = {} | ||
self._options = options or {} | ||
self.set_type(_type) | ||
|
||
def __repr__(self): | ||
return f"{type(self).__name__}({self._type.__name__})" | ||
|
||
def set_type(self, _type): | ||
self._type = _type | ||
self._is_dict = _type is dict | ||
self._is_list = _type is list | ||
self._is_none = _type is None | ||
|
||
def get_type(self): | ||
return self._type | ||
|
||
def set_type_from_key(self, key): | ||
self.set_type(list if isinstance(key, int) else dict) | ||
|
||
def conv_value(self, value): | ||
if isinstance(value, type(self)): | ||
value = value.convert() | ||
return value | ||
|
||
def __setitem__(self, key, value): | ||
if self._is_none: | ||
self.set_type_from_key(key) | ||
if isinstance(key, int) and not self._is_list: | ||
raise ValueError("int key cant be integer for dict object") | ||
if not isinstance(key, int) and self._is_list: | ||
raise ValueError("need integer key for list elements") | ||
|
||
if key in self._elements: | ||
if ( | ||
isinstance(value, type(self)) | ||
and isinstance(self._elements[key], type(self)) | ||
and self._elements[key].get_type() == value.get_type() | ||
): | ||
return | ||
|
||
if self._options.get("raise_duplicate"): | ||
raise ValueError("key is already set") | ||
|
||
if not self._options.get("assign_duplicate"): | ||
return | ||
|
||
self._elements[key] = value | ||
|
||
def __getitem__(self, key): | ||
if key not in self._elements: | ||
self[key] = type(self)(options=self._options) | ||
return self._elements[key] | ||
|
||
def _convert_list(self): | ||
keys = sorted(self._elements.keys()) | ||
if keys != list(range(len(keys))): | ||
raise ValueError("invalid format list keys") | ||
|
||
return [self.conv_value(self._elements[key]) for key in keys] | ||
|
||
def _convert_dict(self): | ||
return {key: self.conv_value(value) for key, value in self._elements.items()} | ||
|
||
def convert(self): | ||
if self._is_none: | ||
return None | ||
if self._is_list: | ||
return self._convert_list() | ||
return self._convert_dict() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
import re | ||
|
||
|
||
class InvalidFormat(Exception): | ||
"""key is invalid formated""" | ||
|
||
def __init__(self, key): | ||
super().__init__(f"invaid key format: {key}") | ||
|
||
|
||
class NestedParserOptionsType(type): | ||
def __new__(cls, cls_name, ns, childs): | ||
if cls_name != "NestedParserOptionsAbstract" and cls_name: | ||
if "sanitize" not in childs: | ||
raise ValueError("you need to define sanitize methods") | ||
return super().__new__(cls, cls_name, ns, childs) | ||
|
||
|
||
TOKEN_PARSER = ("[", "]", ".") | ||
|
||
|
||
class NestedParserOptionsAbstract(metaclass=NestedParserOptionsType): | ||
def check(self, key, keys): | ||
if len(keys) == 0: | ||
raise InvalidFormat(key) | ||
|
||
first = keys[0] | ||
for token in TOKEN_PARSER: | ||
if token in first: | ||
raise InvalidFormat(key) | ||
|
||
for key in keys: | ||
if not isinstance(key, str): | ||
continue | ||
for c in key: | ||
if c.isspace(): | ||
raise InvalidFormat(key) | ||
|
||
def split(self, key): | ||
contents = list(filter(None, self._reg_spliter.split(key))) | ||
if not contents: | ||
raise ValueError(f"invalid form key: {key}") | ||
|
||
lst = [contents[0]] | ||
if len(contents) >= 2: | ||
lst.extend(self._reg_options.split(contents[1])) | ||
if len(contents) == 3: | ||
lst.append(contents[2]) | ||
|
||
return list(filter(None, lst)) | ||
|
||
|
||
REGEX_SEPARATOR = { | ||
"dot": r"(\.[^\.]+)", | ||
"bracket": r"([^\[\]]+)", | ||
"mixed": r"(\[\d+\])|([^\[\]]+)", | ||
"mixed-dot": r"(\[\d+\])|(\.[^\[\]\.]+)", | ||
} | ||
|
||
|
||
class NestedParserOptionsDot(NestedParserOptionsAbstract): | ||
def __init__(self): | ||
self._reg_spliter = re.compile(r"^([^\.]+)(.*?)(\.)?$") | ||
self._reg_options = re.compile(r"(\.[^\.]+)") | ||
|
||
def sanitize(self, key, value): | ||
contents = self.split(key) | ||
lst = contents[1:] | ||
keys = [contents[0]] | ||
for idx, k in enumerate(lst): | ||
if k.startswith("."): | ||
k = k[1:] | ||
if not k: | ||
if len(lst) != idx + 1: | ||
raise InvalidFormat(key) | ||
value = {} | ||
break | ||
try: | ||
k = int(k) | ||
except Exception: | ||
pass | ||
else: | ||
raise InvalidFormat(key) | ||
keys.append(k) | ||
|
||
return keys, value | ||
|
||
|
||
class NestedParserOptionsBracket(NestedParserOptionsAbstract): | ||
def __init__(self): | ||
self._reg_spliter = re.compile(r"^([^\[\]]+)(.*?)(\[\])?$") | ||
self._reg_options = re.compile(r"(\[[^\[\]]+\])") | ||
|
||
def sanitize(self, key, value): | ||
first, *lst = self.split(key) | ||
keys = [first] | ||
|
||
for idx, k in enumerate(lst): | ||
if k.startswith("[") or k.endswith("]"): | ||
if not k.startswith("[") or not k.endswith("]"): | ||
raise InvalidFormat(key) | ||
k = k[1:-1] | ||
if not k: | ||
if len(lst) != idx + 1: | ||
raise InvalidFormat(key) | ||
value = [] | ||
break | ||
try: | ||
k = int(k) | ||
except Exception: | ||
pass | ||
else: | ||
raise InvalidFormat(key) | ||
keys.append(k) | ||
return keys, value | ||
|
||
|
||
class NestedParserOptionsMixedDot(NestedParserOptionsAbstract): | ||
def __init__(self): | ||
self._reg_spliter = re.compile(r"^([^\[\]\.]+)(.*?)((?:\.)|(?:\[\]))?$") | ||
self._reg_options = re.compile(r"(\[\d+\])|(\.[^\[\]\.]+)") | ||
|
||
def sanitize(self, key, value): | ||
first, *lst = self.split(key) | ||
keys = [first] | ||
|
||
for idx, k in enumerate(lst): | ||
if k.startswith("."): | ||
k = k[1:] | ||
# empty dict | ||
if not k: | ||
if len(lst) != idx + 1: | ||
raise InvalidFormat(key) | ||
value = {} | ||
break | ||
elif k.startswith("[") or k.endswith("]"): | ||
if not k.startswith("[") or not k.endswith("]"): | ||
raise InvalidFormat(key) | ||
k = k[1:-1] | ||
if not k: | ||
if len(lst) != idx + 1: | ||
raise InvalidFormat(key) | ||
value = [] | ||
break | ||
k = int(k) | ||
else: | ||
raise InvalidFormat(key) | ||
keys.append(k) | ||
|
||
return keys, value | ||
|
||
|
||
class NestedParserOptionsMixed(NestedParserOptionsMixedDot): | ||
def __init__(self): | ||
self._reg_spliter = re.compile(r"^([^\[\]\.]+)(.*?)((?:\.)|(?:\[\]))?$") | ||
self._reg_options = re.compile(r"(\[\d+\])|(\.?[^\[\]\.]+)") | ||
|
||
def sanitize(self, key, value): | ||
first, *lst = self.split(key) | ||
keys = [first] | ||
|
||
for idx, k in enumerate(lst): | ||
if k.startswith("."): | ||
k = k[1:] | ||
# empty dict | ||
if not k: | ||
if len(lst) != idx + 1: | ||
raise InvalidFormat(key) | ||
value = {} | ||
break | ||
elif k.startswith("[") or k.endswith("]"): | ||
if not k.startswith("[") or not k.endswith("]"): | ||
raise InvalidFormat(key) | ||
k = k[1:-1] | ||
if not k: | ||
if len(lst) != idx + 1: | ||
raise InvalidFormat(key) | ||
value = [] | ||
break | ||
k = int(k) | ||
keys.append(k) | ||
|
||
return keys, value |
Oops, something went wrong.