temparse 1.0.0__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {temparse-1.0.0 → temparse-1.1.0}/PKG-INFO +54 -1
- {temparse-1.0.0 → temparse-1.1.0}/README.md +53 -0
- temparse-1.1.0/pyproject.toml +33 -0
- temparse-1.0.0/pyproject.toml → temparse-1.1.0/pyproject.toml.orig +4 -4
- temparse-1.1.0/src/temparse/__init__.py +276 -0
- temparse-1.0.0/src/temparse/__init__.py +0 -174
- {temparse-1.0.0 → temparse-1.1.0}/src/temparse/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: temparse
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: temparse is template + parse
|
|
5
5
|
Author: jiang-zhexin
|
|
6
6
|
Author-email: jiang-zhexin <162887873+jiang-zhexin@users.noreply.github.com>
|
|
@@ -116,6 +116,59 @@ assert parser.parse("foo + 3 = 3.14") == ("foo", 3, 3.14)
|
|
|
116
116
|
assert parser.parse("bar + 7 = 2.72") == ("bar", 7, 2.72)
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
+
## `Matcher` (Pattern Matching & Structured Parsing)
|
|
120
|
+
|
|
121
|
+
Subclass `Matcher` with a `Parser` to match strings in `match/case` statements or parse strings directly into structured instances:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from temparse import Matcher, MatchStr, Parser
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class OkResp(Matcher, parser=Parser(t'code={int};message="{str}";')):
|
|
128
|
+
code: int
|
|
129
|
+
message: str
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class ErrResp(Matcher, parser=Parser(t'code={int};error="{str}";')):
|
|
133
|
+
code: int
|
|
134
|
+
error: str
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def handle_response(raw: str) -> str:
|
|
138
|
+
# Wrap the string with MatchStr to enable pattern matching
|
|
139
|
+
match MatchStr(raw):
|
|
140
|
+
case OkResp(code, message):
|
|
141
|
+
return f"OK ({code}): {message}"
|
|
142
|
+
case ErrResp(code, error):
|
|
143
|
+
return f"Error ({code}): {error}"
|
|
144
|
+
case _:
|
|
145
|
+
return "Unknown response"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
assert handle_response('code=200;message="success";') == "OK (200): success"
|
|
149
|
+
assert handle_response('code=400;error="bad request";') == "Error (400): bad request"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`Matcher` subclasses are also dataclasses that support keyword matching and `.parse()`:
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
from temparse import Matcher, MatchStr, Parser
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class Point(Matcher, parser=Parser(t"x={int}, y={int}")):
|
|
159
|
+
x: int
|
|
160
|
+
y: int
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
p = Point.parse("x=1, y=2")
|
|
164
|
+
assert p == Point(x=1, y=2)
|
|
165
|
+
|
|
166
|
+
# Wrap the string with MatchStr to enable pattern matching
|
|
167
|
+
match MatchStr("x=0, y=5"):
|
|
168
|
+
case Point(x=0, y=y):
|
|
169
|
+
assert y == 5
|
|
170
|
+
```
|
|
171
|
+
|
|
119
172
|
## License
|
|
120
173
|
|
|
121
174
|
MIT
|
|
@@ -100,6 +100,59 @@ assert parser.parse("foo + 3 = 3.14") == ("foo", 3, 3.14)
|
|
|
100
100
|
assert parser.parse("bar + 7 = 2.72") == ("bar", 7, 2.72)
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
+
## `Matcher` (Pattern Matching & Structured Parsing)
|
|
104
|
+
|
|
105
|
+
Subclass `Matcher` with a `Parser` to match strings in `match/case` statements or parse strings directly into structured instances:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from temparse import Matcher, MatchStr, Parser
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class OkResp(Matcher, parser=Parser(t'code={int};message="{str}";')):
|
|
112
|
+
code: int
|
|
113
|
+
message: str
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class ErrResp(Matcher, parser=Parser(t'code={int};error="{str}";')):
|
|
117
|
+
code: int
|
|
118
|
+
error: str
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def handle_response(raw: str) -> str:
|
|
122
|
+
# Wrap the string with MatchStr to enable pattern matching
|
|
123
|
+
match MatchStr(raw):
|
|
124
|
+
case OkResp(code, message):
|
|
125
|
+
return f"OK ({code}): {message}"
|
|
126
|
+
case ErrResp(code, error):
|
|
127
|
+
return f"Error ({code}): {error}"
|
|
128
|
+
case _:
|
|
129
|
+
return "Unknown response"
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
assert handle_response('code=200;message="success";') == "OK (200): success"
|
|
133
|
+
assert handle_response('code=400;error="bad request";') == "Error (400): bad request"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`Matcher` subclasses are also dataclasses that support keyword matching and `.parse()`:
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from temparse import Matcher, MatchStr, Parser
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class Point(Matcher, parser=Parser(t"x={int}, y={int}")):
|
|
143
|
+
x: int
|
|
144
|
+
y: int
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
p = Point.parse("x=1, y=2")
|
|
148
|
+
assert p == Point(x=1, y=2)
|
|
149
|
+
|
|
150
|
+
# Wrap the string with MatchStr to enable pattern matching
|
|
151
|
+
match MatchStr("x=0, y=5"):
|
|
152
|
+
case Point(x=0, y=y):
|
|
153
|
+
assert y == 5
|
|
154
|
+
```
|
|
155
|
+
|
|
103
156
|
## License
|
|
104
157
|
|
|
105
158
|
MIT
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "temparse"
|
|
3
|
+
version = "1.1.0"
|
|
4
|
+
description = "temparse is template + parse"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.14"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
classifiers = [
|
|
9
|
+
"Environment :: Web Environment",
|
|
10
|
+
"Intended Audience :: Developers",
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"Topic :: Software Development :: Code Generators",
|
|
13
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
14
|
+
]
|
|
15
|
+
dependencies = []
|
|
16
|
+
|
|
17
|
+
[[project.authors]]
|
|
18
|
+
name = "jiang-zhexin"
|
|
19
|
+
email = "162887873+jiang-zhexin@users.noreply.github.com"
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
homepage = "https://github.com/jiang-zhexin/temparse"
|
|
23
|
+
|
|
24
|
+
[dependency-groups]
|
|
25
|
+
dev = [
|
|
26
|
+
"pytest>=9.1.1",
|
|
27
|
+
"ruff>=0.17.2",
|
|
28
|
+
"ty>=0.0.92",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[build-system]
|
|
32
|
+
requires = ["uv_build>=0.13.0,<0.14"]
|
|
33
|
+
build-backend = "uv_build"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "temparse"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.1.0"
|
|
4
4
|
description = "temparse is template + parse"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.14"
|
|
@@ -24,10 +24,10 @@ homepage = "https://github.com/jiang-zhexin/temparse"
|
|
|
24
24
|
[dependency-groups]
|
|
25
25
|
dev = [
|
|
26
26
|
"pytest>=9.1.1",
|
|
27
|
-
"ruff>=0.
|
|
28
|
-
"ty>=0.0.
|
|
27
|
+
"ruff>=0.17.2",
|
|
28
|
+
"ty>=0.0.92",
|
|
29
29
|
]
|
|
30
30
|
|
|
31
31
|
[build-system]
|
|
32
|
-
requires = ["uv_build>=0.
|
|
32
|
+
requires = ["uv_build>=0.13.0,<0.14"]
|
|
33
33
|
build-backend = "uv_build"
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import builtins
|
|
2
|
+
import dataclasses
|
|
3
|
+
import datetime
|
|
4
|
+
import functools
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from string.templatelib import Interpolation, Template
|
|
9
|
+
from typing import (
|
|
10
|
+
Any,
|
|
11
|
+
Literal,
|
|
12
|
+
Self,
|
|
13
|
+
cast,
|
|
14
|
+
dataclass_transform,
|
|
15
|
+
final,
|
|
16
|
+
get_origin,
|
|
17
|
+
is_typeddict,
|
|
18
|
+
overload,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = ["Conversion", "FormatConversion", "MatchStr", "Matcher", "Parser", "parse"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@final
|
|
25
|
+
class Parser[*T]:
|
|
26
|
+
"""
|
|
27
|
+
A compiled template parser that can be reused to parse multiple strings.
|
|
28
|
+
|
|
29
|
+
example:
|
|
30
|
+
```python
|
|
31
|
+
parser = Parser[str, int, float](t"{str} + {int} = {float}")
|
|
32
|
+
|
|
33
|
+
parser.parse("foo + 3 = 3.14")
|
|
34
|
+
```
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, template: Template, flags: re.RegexFlag = re.NOFLAG) -> None:
|
|
38
|
+
if len(template.interpolations) == 0:
|
|
39
|
+
raise ValueError("template must contain at least one interpolation")
|
|
40
|
+
|
|
41
|
+
self._template = template
|
|
42
|
+
self._flags = flags
|
|
43
|
+
self._expression = "".join(
|
|
44
|
+
re.escape(item) if isinstance(item, str) else r"(.+?)" for item in template
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
@functools.cached_property
|
|
48
|
+
def _match_re(self) -> re.Pattern[str]:
|
|
49
|
+
return re.compile(self._expression, self._flags)
|
|
50
|
+
|
|
51
|
+
@overload
|
|
52
|
+
def parse(self, s: str, *, return_none: Literal[False] = False) -> tuple[*T]: ...
|
|
53
|
+
|
|
54
|
+
@overload
|
|
55
|
+
def parse(self, s: str, *, return_none: bool) -> tuple[*T] | None: ...
|
|
56
|
+
|
|
57
|
+
def parse(self, s: str, *, return_none: bool = False) -> tuple[*T] | None:
|
|
58
|
+
m = self._match_re.fullmatch(s)
|
|
59
|
+
if m is None:
|
|
60
|
+
if return_none:
|
|
61
|
+
return None
|
|
62
|
+
raise ValueError(f"invalid format: {s!r}")
|
|
63
|
+
|
|
64
|
+
return cast(
|
|
65
|
+
tuple[*T],
|
|
66
|
+
tuple(
|
|
67
|
+
_convert(text, interpolation)
|
|
68
|
+
for text, interpolation in zip(
|
|
69
|
+
m.groups(), self._template.interpolations
|
|
70
|
+
)
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@final
|
|
76
|
+
class parse[*T]:
|
|
77
|
+
"""
|
|
78
|
+
Parse string by template.
|
|
79
|
+
|
|
80
|
+
example:
|
|
81
|
+
```python
|
|
82
|
+
city, year = parse[str, int](
|
|
83
|
+
t"I live in {str}, since {int}", "I live in Tokyo, since 2010"
|
|
84
|
+
)
|
|
85
|
+
assert city == "Tokyo"
|
|
86
|
+
assert year == 2010
|
|
87
|
+
```
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def __new__(cls, t: Template, s: str, flags: re.RegexFlag = re.NOFLAG) -> tuple[*T]:
|
|
91
|
+
"""
|
|
92
|
+
Used to replace this function until [PEP 718](https://peps.python.org/pep-0718/) is accepted.
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
def parse[*T](t: Template, s: str, flags: re.RegexFlag = re.NOFLAG) -> tuple[*T]:
|
|
96
|
+
return Parser(t).parse(s)
|
|
97
|
+
```
|
|
98
|
+
"""
|
|
99
|
+
return Parser[*T](t, flags).parse(s)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass_transform()
|
|
103
|
+
class _MatcherMeta(type):
|
|
104
|
+
_parser: Parser
|
|
105
|
+
__match_args__: tuple[str, ...]
|
|
106
|
+
|
|
107
|
+
def __instancecheck__(cls, instance: object) -> bool:
|
|
108
|
+
if super().__instancecheck__(instance):
|
|
109
|
+
return True
|
|
110
|
+
return isinstance(instance, MatchStr) and instance._match(cls)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class Matcher(metaclass=_MatcherMeta):
|
|
114
|
+
"""
|
|
115
|
+
Base class for structural pattern matching and structured template parsing.
|
|
116
|
+
|
|
117
|
+
example:
|
|
118
|
+
```python
|
|
119
|
+
class OkResp(Matcher, parser=Parser(t'code={int};message="{str}";')):
|
|
120
|
+
code: int
|
|
121
|
+
message: str
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class ErrResp(Matcher, parser=Parser(t'code={int};error="{str}";')):
|
|
125
|
+
code: int
|
|
126
|
+
error: str
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
match MatchStr('code=400;error="bad request";'):
|
|
130
|
+
case OkResp(code, message):
|
|
131
|
+
print(f"OK ({code}): {message}")
|
|
132
|
+
case ErrResp(code, error):
|
|
133
|
+
print(f"Error ({code}): {error}")
|
|
134
|
+
```
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
|
|
138
|
+
|
|
139
|
+
def __init_subclass__(cls, parser: Parser) -> None:
|
|
140
|
+
super().__init_subclass__()
|
|
141
|
+
dataclasses.dataclass(cls)
|
|
142
|
+
cls._parser = parser
|
|
143
|
+
if len(cls.__match_args__) != len(parser._template.interpolations):
|
|
144
|
+
raise ValueError("number of fields must match template interpolations")
|
|
145
|
+
|
|
146
|
+
@classmethod
|
|
147
|
+
def parse(cls, s: str) -> Self:
|
|
148
|
+
"""
|
|
149
|
+
Parse a string using the class's parser and return an instance of the class.
|
|
150
|
+
|
|
151
|
+
example:
|
|
152
|
+
```python
|
|
153
|
+
p = Point.parse("x=1, y=2")
|
|
154
|
+
assert p == Point(x=1, y=2)
|
|
155
|
+
```
|
|
156
|
+
"""
|
|
157
|
+
return cls(*cls._parser.parse(s))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class MatchStr(str):
|
|
161
|
+
"""
|
|
162
|
+
A `str` subclass that enables structural pattern matching with `Matcher` subclasses.
|
|
163
|
+
|
|
164
|
+
example:
|
|
165
|
+
```python
|
|
166
|
+
match MatchStr("x=1, y=2"):
|
|
167
|
+
case Point(x, y):
|
|
168
|
+
assert x == 1 and y == 2
|
|
169
|
+
```
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
def __init__(self, _: str, /) -> None:
|
|
173
|
+
self._cache: dict[_MatcherMeta, dict[str, Any] | None] = {}
|
|
174
|
+
|
|
175
|
+
def _match(self, cls: _MatcherMeta) -> bool:
|
|
176
|
+
if cls not in self._cache:
|
|
177
|
+
res = cls._parser.parse(self, return_none=True)
|
|
178
|
+
self._cache[cls] = (
|
|
179
|
+
dict(zip(cls.__match_args__, res)) if res is not None else None
|
|
180
|
+
)
|
|
181
|
+
if (fields := self._cache[cls]) is None:
|
|
182
|
+
return False
|
|
183
|
+
self.__dict__.update(fields)
|
|
184
|
+
return True
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@final
|
|
188
|
+
class Conversion[T]:
|
|
189
|
+
"""
|
|
190
|
+
Convert the string to your object.
|
|
191
|
+
|
|
192
|
+
example:
|
|
193
|
+
```python
|
|
194
|
+
@Conversion
|
|
195
|
+
def percent(s: str) -> float:
|
|
196
|
+
return float(s.rstrip("%")) / 100
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
(result,) = parse[float](t"x = {percent}", "x = 30%")
|
|
200
|
+
assert result == 0.3
|
|
201
|
+
```
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
def __init__(self, func: Callable[[str], T]) -> None:
|
|
205
|
+
self._convert = func
|
|
206
|
+
functools.update_wrapper(self, func)
|
|
207
|
+
|
|
208
|
+
def __call__(self, s: str) -> T:
|
|
209
|
+
return self._convert(s)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@final
|
|
213
|
+
class FormatConversion[T]:
|
|
214
|
+
"""
|
|
215
|
+
Convert the string to your object with format.
|
|
216
|
+
|
|
217
|
+
example:
|
|
218
|
+
```python
|
|
219
|
+
@FormatConversion
|
|
220
|
+
def between(s: str, spec: str) -> str:
|
|
221
|
+
lo, hi = spec.split(",")
|
|
222
|
+
return s[int(lo) : int(hi)]
|
|
223
|
+
|
|
224
|
+
(result,) = parse[str](t"{between:2,5}", "abcdefg")
|
|
225
|
+
assert result == "cde"
|
|
226
|
+
```
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
def __init__(self, func: Callable[[str, str], T]) -> None:
|
|
230
|
+
self._convert = func
|
|
231
|
+
functools.update_wrapper(self, func)
|
|
232
|
+
|
|
233
|
+
def __call__(self, s: str, format: str) -> T:
|
|
234
|
+
return self._convert(s, format)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _convert(s: str, interp: Interpolation[Any]) -> Any:
|
|
238
|
+
origin = get_origin(interp.value) or interp.value
|
|
239
|
+
match origin:
|
|
240
|
+
case Conversion():
|
|
241
|
+
return interp.value(s)
|
|
242
|
+
|
|
243
|
+
case FormatConversion():
|
|
244
|
+
return interp.value(s, interp.format_spec)
|
|
245
|
+
|
|
246
|
+
case datetime.datetime:
|
|
247
|
+
return datetime.datetime.strptime(s, interp.format_spec).astimezone(
|
|
248
|
+
datetime.UTC
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
case datetime.date:
|
|
252
|
+
return datetime.date.strptime(s, interp.format_spec)
|
|
253
|
+
|
|
254
|
+
case datetime.time:
|
|
255
|
+
return datetime.time.strptime(s, interp.format_spec)
|
|
256
|
+
|
|
257
|
+
case builtins.int:
|
|
258
|
+
return int(s, base=int(interp.format_spec) if interp.format_spec else 0)
|
|
259
|
+
|
|
260
|
+
case builtins.float:
|
|
261
|
+
return float(s)
|
|
262
|
+
|
|
263
|
+
case builtins.complex:
|
|
264
|
+
return complex(s)
|
|
265
|
+
|
|
266
|
+
case builtins.str:
|
|
267
|
+
return s
|
|
268
|
+
|
|
269
|
+
case builtins.list | builtins.dict:
|
|
270
|
+
return json.loads(s)
|
|
271
|
+
|
|
272
|
+
case _ if origin is json or is_typeddict(origin):
|
|
273
|
+
return json.loads(s)
|
|
274
|
+
|
|
275
|
+
case _:
|
|
276
|
+
raise TypeError(f"unkown object: {interp.value}")
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import datetime
|
|
2
|
-
import functools
|
|
3
|
-
import json
|
|
4
|
-
import re
|
|
5
|
-
from collections.abc import Callable
|
|
6
|
-
from string.templatelib import Interpolation, Template
|
|
7
|
-
from typing import Any, cast, final, get_origin, is_typeddict
|
|
8
|
-
|
|
9
|
-
__all__ = ["Conversion", "FormatConversion", "Parser", "parse"]
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@final
|
|
13
|
-
class Parser[*T]:
|
|
14
|
-
"""
|
|
15
|
-
A compiled template parser that can be reused to parse multiple strings.
|
|
16
|
-
|
|
17
|
-
example:
|
|
18
|
-
```python
|
|
19
|
-
parser = Parser[str, int, float](t"{str} + {int} = {float}")
|
|
20
|
-
|
|
21
|
-
parser.parse("foo + 3 = 3.14")
|
|
22
|
-
```
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
def __init__(self, template: Template, flags: re.RegexFlag = re.NOFLAG) -> None:
|
|
26
|
-
if len(template.interpolations) == 0:
|
|
27
|
-
raise ValueError("template must contain at least one interpolation")
|
|
28
|
-
|
|
29
|
-
self._template = template
|
|
30
|
-
self._flags = flags
|
|
31
|
-
self._expression = "".join(
|
|
32
|
-
re.escape(item) if isinstance(item, str) else r"(.+?)" for item in template
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
@functools.cached_property
|
|
36
|
-
def _match_re(self) -> re.Pattern[str]:
|
|
37
|
-
return re.compile(self._expression, self._flags)
|
|
38
|
-
|
|
39
|
-
def parse(self, s: str) -> tuple[*T]:
|
|
40
|
-
m = self._match_re.fullmatch(s)
|
|
41
|
-
if m is None:
|
|
42
|
-
raise ValueError(f"invalid format: {s!r}")
|
|
43
|
-
|
|
44
|
-
return cast(
|
|
45
|
-
tuple[*T],
|
|
46
|
-
tuple(
|
|
47
|
-
_convert(text, interpolation)
|
|
48
|
-
for text, interpolation in zip(
|
|
49
|
-
m.groups(), self._template.interpolations
|
|
50
|
-
)
|
|
51
|
-
),
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
@final
|
|
56
|
-
class parse[*T]:
|
|
57
|
-
"""
|
|
58
|
-
Parse string by template.
|
|
59
|
-
|
|
60
|
-
example:
|
|
61
|
-
```python
|
|
62
|
-
city, year = parse[str, int](
|
|
63
|
-
t"I live in {str}, since {int}", "I live in Tokyo, since 2010"
|
|
64
|
-
)
|
|
65
|
-
assert city == "Tokyo"
|
|
66
|
-
assert year == 2010
|
|
67
|
-
```
|
|
68
|
-
"""
|
|
69
|
-
|
|
70
|
-
def __new__(cls, t: Template, s: str, flags: re.RegexFlag = re.NOFLAG) -> tuple[*T]:
|
|
71
|
-
"""
|
|
72
|
-
Used to replace this function until [PEP 718](https://peps.python.org/pep-0718/) is accepted.
|
|
73
|
-
|
|
74
|
-
```python
|
|
75
|
-
def parse[*T](t: Template, s: str, flags: re.RegexFlag = re.NOFLAG) -> tuple[*T]:
|
|
76
|
-
return Parser(t).parse(s)
|
|
77
|
-
```
|
|
78
|
-
"""
|
|
79
|
-
return Parser[*T](t, flags).parse(s)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
@final
|
|
83
|
-
class Conversion[T]:
|
|
84
|
-
"""
|
|
85
|
-
Convert the string to your object.
|
|
86
|
-
|
|
87
|
-
example:
|
|
88
|
-
```python
|
|
89
|
-
@Conversion
|
|
90
|
-
def percent(s: str) -> float:
|
|
91
|
-
return float(s.rstrip("%")) / 100
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
(result,) = parse[float](t"x = {percent}", "x = 30%")
|
|
95
|
-
assert result == 0.3
|
|
96
|
-
```
|
|
97
|
-
"""
|
|
98
|
-
|
|
99
|
-
__name__: str
|
|
100
|
-
__qualname__: str
|
|
101
|
-
|
|
102
|
-
def __init__(self, func: Callable[[str], T]) -> None:
|
|
103
|
-
self._convert = func
|
|
104
|
-
functools.update_wrapper(self, func)
|
|
105
|
-
|
|
106
|
-
def __call__(self, s: str) -> T:
|
|
107
|
-
return self._convert(s)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
@final
|
|
111
|
-
class FormatConversion[T]:
|
|
112
|
-
"""
|
|
113
|
-
Convert the string to your object with format.
|
|
114
|
-
|
|
115
|
-
example:
|
|
116
|
-
```python
|
|
117
|
-
@FormatConversion
|
|
118
|
-
def between(s: str, spec: str) -> str:
|
|
119
|
-
lo, hi = spec.split(",")
|
|
120
|
-
return s[int(lo) : int(hi)]
|
|
121
|
-
|
|
122
|
-
(result,) = parse[str](t"{between:2,5}", "abcdefg")
|
|
123
|
-
assert result == "cde"
|
|
124
|
-
```
|
|
125
|
-
"""
|
|
126
|
-
|
|
127
|
-
__name__: str
|
|
128
|
-
__qualname__: str
|
|
129
|
-
|
|
130
|
-
def __init__(self, func: Callable[[str, str], T]) -> None:
|
|
131
|
-
self._convert = func
|
|
132
|
-
functools.update_wrapper(self, func)
|
|
133
|
-
|
|
134
|
-
def __call__(self, s: str, format: str) -> T:
|
|
135
|
-
return self._convert(s, format)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def _convert(s: str, i: Interpolation[Any]) -> Any:
|
|
139
|
-
origin = get_origin(i.value) or i.value
|
|
140
|
-
match origin:
|
|
141
|
-
case Conversion():
|
|
142
|
-
return i.value(s)
|
|
143
|
-
|
|
144
|
-
case FormatConversion():
|
|
145
|
-
return i.value(s, i.format_spec)
|
|
146
|
-
|
|
147
|
-
case datetime.datetime:
|
|
148
|
-
return datetime.datetime.strptime(s, i.format_spec).astimezone(datetime.UTC)
|
|
149
|
-
|
|
150
|
-
case datetime.date:
|
|
151
|
-
return datetime.date.strptime(s, i.format_spec)
|
|
152
|
-
|
|
153
|
-
case datetime.time:
|
|
154
|
-
return datetime.time.strptime(s, i.format_spec)
|
|
155
|
-
|
|
156
|
-
case _ if origin is int:
|
|
157
|
-
return int(s, base=int(i.format_spec) if i.format_spec else 0)
|
|
158
|
-
|
|
159
|
-
case _ if origin is float:
|
|
160
|
-
return float(s)
|
|
161
|
-
|
|
162
|
-
case _ if origin is complex:
|
|
163
|
-
return complex(s)
|
|
164
|
-
|
|
165
|
-
case _ if origin is str:
|
|
166
|
-
return s
|
|
167
|
-
|
|
168
|
-
case _ if (
|
|
169
|
-
origin is list or origin is dict or origin is json or is_typeddict(origin)
|
|
170
|
-
):
|
|
171
|
-
return json.loads(s)
|
|
172
|
-
|
|
173
|
-
case _:
|
|
174
|
-
raise TypeError(f"unkown object: {i.value}")
|
|
File without changes
|