|
11 | 11 | from typing import List
|
12 | 12 | from typing import Literal
|
13 | 13 | from typing import Optional
|
| 14 | +from typing import Set |
14 | 15 | from typing import Tuple
|
15 | 16 | from typing import Type
|
16 | 17 | from typing import Union
|
@@ -70,6 +71,159 @@ def split_at_given_level(
|
70 | 71 | NoneType = type(None)
|
71 | 72 |
|
72 | 73 |
|
| 74 | +def list_parser( |
| 75 | + cls: Type, type_: TypeAlias, parsers: Optional[Dict[type, Callable[[str], Any]]] = None |
| 76 | +) -> Callable[[str], List[Any]]: |
| 77 | + """ |
| 78 | + Returns a function that parses a stringified list into a `List` of the correct type. |
| 79 | +
|
| 80 | + Args: |
| 81 | + cls: the type of the class object this is being parsed for (used to get default val for |
| 82 | + parsers) |
| 83 | + type_: the type of the attribute to be parsed |
| 84 | + parsers: an optional mapping from type to the function to use for parsing that type (allows |
| 85 | + for parsing of more complex types) |
| 86 | + """ |
| 87 | + subtypes = typing.get_args(type_) |
| 88 | + assert len(subtypes) == 1, "Lists are allowed only one subtype per PEP specification!" |
| 89 | + subtype_parser = _get_parser( |
| 90 | + cls, |
| 91 | + subtypes[0], |
| 92 | + parsers, |
| 93 | + ) |
| 94 | + return functools.partial( |
| 95 | + lambda s: list( |
| 96 | + [] |
| 97 | + if s == "" |
| 98 | + else [subtype_parser(item) for item in list(split_at_given_level(s, split_delim=","))] |
| 99 | + ) |
| 100 | + ) |
| 101 | + |
| 102 | + |
| 103 | +def set_parser( |
| 104 | + cls: Type, type_: TypeAlias, parsers: Optional[Dict[type, Callable[[str], Any]]] = None |
| 105 | +) -> Callable[[str], Set[Any]]: |
| 106 | + """ |
| 107 | + Returns a function that parses a stringified set into a `Set` of the correct type. |
| 108 | +
|
| 109 | + Args: |
| 110 | + cls: the type of the class object this is being parsed for (used to get default val for |
| 111 | + parsers) |
| 112 | + type_: the type of the attribute to be parsed |
| 113 | + parsers: an optional mapping from type to the function to use for parsing that type (allows |
| 114 | + for parsing of more complex types) |
| 115 | + """ |
| 116 | + subtypes = typing.get_args(type_) |
| 117 | + assert len(subtypes) == 1, "Sets are allowed only one subtype per PEP specification!" |
| 118 | + subtype_parser = _get_parser( |
| 119 | + cls, |
| 120 | + subtypes[0], |
| 121 | + parsers, |
| 122 | + ) |
| 123 | + return functools.partial( |
| 124 | + lambda s: set( |
| 125 | + set({}) |
| 126 | + if s == "{}" |
| 127 | + else [ |
| 128 | + subtype_parser(item) |
| 129 | + for item in set(split_at_given_level(s[1:-1], split_delim=",")) |
| 130 | + ] |
| 131 | + ) |
| 132 | + ) |
| 133 | + |
| 134 | + |
| 135 | +def tuple_parser( |
| 136 | + cls: Type, type_: TypeAlias, parsers: Optional[Dict[type, Callable[[str], Any]]] = None |
| 137 | +) -> Callable[[str], Tuple[Any, ...]]: |
| 138 | + """ |
| 139 | + Returns a function that parses a stringified tuple into a `Tuple` of the correct type. |
| 140 | +
|
| 141 | + Args: |
| 142 | + cls: the type of the class object this is being parsed for (used to get default val for |
| 143 | + parsers) |
| 144 | + type_: the type of the attribute to be parsed |
| 145 | + parsers: an optional mapping from type to the function to use for parsing that type (allows |
| 146 | + for parsing of more complex types) |
| 147 | + """ |
| 148 | + subtype_parsers = [ |
| 149 | + _get_parser( |
| 150 | + cls, |
| 151 | + subtype, |
| 152 | + parsers, |
| 153 | + ) |
| 154 | + for subtype in typing.get_args(type_) |
| 155 | + ] |
| 156 | + |
| 157 | + def tuple_parse(tuple_string: str) -> Tuple[Any, ...]: |
| 158 | + """ |
| 159 | + Parses a dictionary value (can do so recursively) |
| 160 | + Note that this tool will fail on tuples containing strings containing |
| 161 | + unpaired '{', or '}' characters |
| 162 | + """ |
| 163 | + assert tuple_string[0] == "(", "Tuple val improperly formatted" |
| 164 | + assert tuple_string[-1] == ")", "Tuple val improperly formatted" |
| 165 | + tuple_string = tuple_string[1:-1] |
| 166 | + if len(tuple_string) == 0: |
| 167 | + return () |
| 168 | + else: |
| 169 | + val_strings = split_at_given_level(tuple_string, split_delim=",") |
| 170 | + return tuple(parser(val_str) for parser, val_str in zip(subtype_parsers, val_strings)) |
| 171 | + |
| 172 | + return functools.partial(tuple_parse) |
| 173 | + |
| 174 | + |
| 175 | +def dict_parser( |
| 176 | + cls: Type, type_: TypeAlias, parsers: Optional[Dict[type, Callable[[str], Any]]] = None |
| 177 | +) -> Callable[[str], Dict[Any, Any]]: |
| 178 | + """ |
| 179 | + Returns a function that parses a stringified dict into a `Dict` of the correct type. |
| 180 | +
|
| 181 | + Args: |
| 182 | + cls: the type of the class object this is being parsed for (used to get default val for |
| 183 | + parsers) |
| 184 | + type_: the type of the attribute to be parsed |
| 185 | + parsers: an optional mapping from type to the function to use for parsing that type (allows |
| 186 | + for parsing of more complex types) |
| 187 | + """ |
| 188 | + subtypes = typing.get_args(type_) |
| 189 | + assert len(subtypes) == 2, "Dict object must have exactly 2 subtypes per PEP specification!" |
| 190 | + (key_parser, val_parser) = ( |
| 191 | + _get_parser( |
| 192 | + cls, |
| 193 | + subtypes[0], |
| 194 | + parsers, |
| 195 | + ), |
| 196 | + _get_parser( |
| 197 | + cls, |
| 198 | + subtypes[1], |
| 199 | + parsers, |
| 200 | + ), |
| 201 | + ) |
| 202 | + |
| 203 | + def dict_parse(dict_string: str) -> Dict[Any, Any]: |
| 204 | + """ |
| 205 | + Parses a dictionary value (can do so recursively) |
| 206 | + """ |
| 207 | + assert dict_string[0] == "{", "Dict val improperly formatted" |
| 208 | + assert dict_string[-1] == "}", "Dict val improprly formatted" |
| 209 | + dict_string = dict_string[1:-1] |
| 210 | + if len(dict_string) == 0: |
| 211 | + return {} |
| 212 | + else: |
| 213 | + outer_splits = split_at_given_level(dict_string, split_delim=",") |
| 214 | + out_dict = {} |
| 215 | + for outer_split in outer_splits: |
| 216 | + inner_splits = split_at_given_level(outer_split, split_delim=";") |
| 217 | + assert ( |
| 218 | + len(inner_splits) % 2 == 0 |
| 219 | + ), "Inner splits of dict didn't have matched key val pairs" |
| 220 | + for i in range(0, len(inner_splits), 2): |
| 221 | + out_dict[key_parser(inner_splits[i])] = val_parser(inner_splits[i + 1]) |
| 222 | + return out_dict |
| 223 | + |
| 224 | + return functools.partial(dict_parse) |
| 225 | + |
| 226 | + |
73 | 227 | def _get_parser(
|
74 | 228 | cls: Type, type_: TypeAlias, parsers: Optional[Dict[type, Callable[[str], Any]]] = None
|
75 | 229 | ) -> partial:
|
@@ -110,118 +264,13 @@ def get_parser() -> partial:
|
110 | 264 | elif type_ == dict:
|
111 | 265 | raise ValueError("Unable to parse dict (try typing.Mapping[type])")
|
112 | 266 | elif typing.get_origin(type_) == list:
|
113 |
| - subtypes = typing.get_args(type_) |
114 |
| - |
115 |
| - assert ( |
116 |
| - len(subtypes) == 1 |
117 |
| - ), "Lists are allowed only one subtype per PEP specification!" |
118 |
| - subtype_parser = _get_parser( |
119 |
| - cls, |
120 |
| - subtypes[0], |
121 |
| - parsers, |
122 |
| - ) |
123 |
| - return functools.partial( |
124 |
| - lambda s: list( |
125 |
| - [] |
126 |
| - if s == "" |
127 |
| - else [ |
128 |
| - subtype_parser(item) |
129 |
| - for item in list(split_at_given_level(s, split_delim=",")) |
130 |
| - ] |
131 |
| - ) |
132 |
| - ) |
| 267 | + return list_parser(cls, type_, parsers) |
133 | 268 | elif typing.get_origin(type_) == set:
|
134 |
| - subtypes = typing.get_args(type_) |
135 |
| - assert ( |
136 |
| - len(subtypes) == 1 |
137 |
| - ), "Sets are allowed only one subtype per PEP specification!" |
138 |
| - subtype_parser = _get_parser( |
139 |
| - cls, |
140 |
| - subtypes[0], |
141 |
| - parsers, |
142 |
| - ) |
143 |
| - return functools.partial( |
144 |
| - lambda s: set( |
145 |
| - set({}) |
146 |
| - if s == "{}" |
147 |
| - else [ |
148 |
| - subtype_parser(item) |
149 |
| - for item in set(split_at_given_level(s[1:-1], split_delim=",")) |
150 |
| - ] |
151 |
| - ) |
152 |
| - ) |
| 269 | + return set_parser(cls, type_, parsers) |
153 | 270 | elif typing.get_origin(type_) == tuple:
|
154 |
| - subtype_parsers = [ |
155 |
| - _get_parser( |
156 |
| - cls, |
157 |
| - subtype, |
158 |
| - parsers, |
159 |
| - ) |
160 |
| - for subtype in typing.get_args(type_) |
161 |
| - ] |
162 |
| - |
163 |
| - def tuple_parse(tuple_string: str) -> Tuple[Any, ...]: |
164 |
| - """ |
165 |
| - Parses a dictionary value (can do so recursively) |
166 |
| - Note that this tool will fail on tuples containing strings containing |
167 |
| - unpaired '{', or '}' characters |
168 |
| - """ |
169 |
| - assert tuple_string[0] == "(", "Tuple val improperly formatted" |
170 |
| - assert tuple_string[-1] == ")", "Tuple val improperly formatted" |
171 |
| - tuple_string = tuple_string[1:-1] |
172 |
| - if len(tuple_string) == 0: |
173 |
| - return () |
174 |
| - else: |
175 |
| - val_strings = split_at_given_level(tuple_string, split_delim=",") |
176 |
| - return tuple( |
177 |
| - parser(val_str) |
178 |
| - for parser, val_str in zip(subtype_parsers, val_strings) |
179 |
| - ) |
180 |
| - |
181 |
| - return functools.partial(tuple_parse) |
182 |
| - |
| 271 | + return tuple_parser(cls, type_, parsers) |
183 | 272 | elif typing.get_origin(type_) == dict:
|
184 |
| - subtypes = typing.get_args(type_) |
185 |
| - assert ( |
186 |
| - len(subtypes) == 2 |
187 |
| - ), "Dict object must have exactly 2 subtypes per PEP specification!" |
188 |
| - (key_parser, val_parser) = ( |
189 |
| - _get_parser( |
190 |
| - cls, |
191 |
| - subtypes[0], |
192 |
| - parsers, |
193 |
| - ), |
194 |
| - _get_parser( |
195 |
| - cls, |
196 |
| - subtypes[1], |
197 |
| - parsers, |
198 |
| - ), |
199 |
| - ) |
200 |
| - |
201 |
| - def dict_parse(dict_string: str) -> Dict[Any, Any]: |
202 |
| - """ |
203 |
| - Parses a dictionary value (can do so recursively) |
204 |
| - """ |
205 |
| - assert dict_string[0] == "{", "Dict val improperly formatted" |
206 |
| - assert dict_string[-1] == "}", "Dict val improprly formatted" |
207 |
| - dict_string = dict_string[1:-1] |
208 |
| - if len(dict_string) == 0: |
209 |
| - return {} |
210 |
| - else: |
211 |
| - outer_splits = split_at_given_level(dict_string, split_delim=",") |
212 |
| - out_dict = {} |
213 |
| - for outer_split in outer_splits: |
214 |
| - inner_splits = split_at_given_level(outer_split, split_delim=";") |
215 |
| - assert ( |
216 |
| - len(inner_splits) % 2 == 0 |
217 |
| - ), "Inner splits of dict didn't have matched key val pairs" |
218 |
| - for i in range(0, len(inner_splits), 2): |
219 |
| - out_dict[key_parser(inner_splits[i])] = val_parser( |
220 |
| - inner_splits[i + 1] |
221 |
| - ) |
222 |
| - return out_dict |
223 |
| - |
224 |
| - return functools.partial(dict_parse) |
| 273 | + return dict_parser(cls, type_, parsers) |
225 | 274 | elif isinstance(type_, type) and issubclass(type_, Enum):
|
226 | 275 | return types.make_enum_parser(type_)
|
227 | 276 | elif types.is_constructible_from_str(type_):
|
|
0 commit comments