langroid 0.16.5__py3-none-any.whl → 0.16.7__py3-none-any.whl
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.
- langroid/agent/md_tool_message_grammar.py +455 -0
- langroid/agent/tools/code_file_tool_parse.py +150 -0
- langroid/agent/tools/code_file_tool_pyparsing.py +194 -0
- langroid/agent/tools/code_file_tool_pyparsing2.py +199 -0
- langroid/agent/tools/formatted_model_custom.py +150 -0
- langroid/agent/tools/formatted_model_custom2.py +168 -0
- langroid/agent/tools/formatted_model_custom3.py +279 -0
- langroid/agent/tools/formatted_model_custom4.py +395 -0
- langroid/agent/tools/formatted_model_jinja.py +133 -0
- langroid/agent/tools/formatted_model_jinja.py-e +122 -0
- langroid/agent/tools/formatted_model_jinja2.py +145 -0
- langroid/agent/tools/formatted_model_jinja2.py-e +135 -0
- langroid/agent/tools/formatted_model_lark.py +0 -0
- langroid/agent/tools/formatted_model_lark2.py +168 -0
- langroid/agent/tools/formatted_model_parse.py +105 -0
- langroid/agent/tools/formatted_model_parse.py-e +98 -0
- langroid/agent/tools/formatted_model_parse2.py +113 -0
- langroid/agent/tools/formatted_model_parse2.py-e +109 -0
- langroid/agent/tools/formatted_model_parse3.py +114 -0
- langroid/agent/tools/formatted_model_parse3.py-e +110 -0
- langroid/agent/tools/formatted_model_parsimon.py +194 -0
- langroid/agent/tools/formatted_model_parsimon.py-e +186 -0
- langroid/agent/tools/formatted_model_pyparsing.py +169 -0
- langroid/agent/tools/formatted_model_pyparsing.py-e +149 -0
- langroid/agent/tools/formatted_model_pyparsing2.py +159 -0
- langroid/agent/tools/formatted_model_pyparsing2.py-e +143 -0
- langroid/agent/tools/formatted_model_pyparsing3.py +133 -0
- langroid/agent/tools/formatted_model_pyparsing3.py-e +121 -0
- langroid/agent/tools/formatted_model_pyparsing4.py +213 -0
- langroid/agent/tools/formatted_model_pyparsing4.py-e +176 -0
- langroid/agent/tools/formatted_model_pyparsing5.py +173 -0
- langroid/agent/tools/formatted_model_pyparsing5.py-e +142 -0
- langroid/agent/tools/formatted_model_regex.py +246 -0
- langroid/agent/tools/formatted_model_regex.py-e +248 -0
- langroid/agent/tools/formatted_model_regex2.py +250 -0
- langroid/agent/tools/formatted_model_regex2.py-e +253 -0
- langroid/agent/tools/formatted_model_tatsu.py +172 -0
- langroid/agent/tools/formatted_model_tatsu.py-e +160 -0
- langroid/agent/tools/formatted_model_template.py +217 -0
- langroid/agent/tools/formatted_model_template.py-e +200 -0
- langroid/agent/tools/formatted_model_xml.py +178 -0
- langroid/agent/tools/formatted_model_xml2.py +178 -0
- langroid/agent/tools/formatted_model_xml3.py +132 -0
- langroid/agent/tools/formatted_model_xml4.py +130 -0
- langroid/agent/tools/formatted_model_xml5.py +130 -0
- langroid/agent/tools/formatted_model_xml6.py +113 -0
- langroid/agent/tools/formatted_model_xml7.py +117 -0
- langroid/agent/tools/formatted_model_xml8.py +164 -0
- langroid/agent/tools/generic_tool.py +165 -0
- langroid/agent/tools/generic_tool_tatsu.py +275 -0
- langroid/agent/tools/grammar_based_model.py +132 -0
- langroid/agent/tools/grammar_based_model.py-e +128 -0
- langroid/agent/tools/grammar_based_model_lark.py +156 -0
- langroid/agent/tools/grammar_based_model_lark.py-e +153 -0
- langroid/agent/tools/grammar_based_model_parse.py +86 -0
- langroid/agent/tools/grammar_based_model_parse.py-e +80 -0
- langroid/agent/tools/grammar_based_model_parsimonious.py +129 -0
- langroid/agent/tools/grammar_based_model_parsimonious.py-e +120 -0
- langroid/agent/tools/grammar_based_model_pyparsing.py +105 -0
- langroid/agent/tools/grammar_based_model_pyparsing.py-e +103 -0
- langroid/agent/tools/grammar_based_model_regex.py +139 -0
- langroid/agent/tools/grammar_based_model_regex.py-e +130 -0
- langroid/agent/tools/grammar_based_model_regex2.py +124 -0
- langroid/agent/tools/grammar_based_model_regex2.py-e +116 -0
- langroid/agent/tools/grammar_based_model_tatsu.py +80 -0
- langroid/agent/tools/grammar_based_model_tatsu.py-e +77 -0
- langroid/agent/tools/lark_earley_example.py +135 -0
- langroid/agent/tools/lark_earley_example.py-e +117 -0
- langroid/agent/tools/lark_example.py +72 -0
- langroid/agent/tools/parse_example.py +76 -0
- langroid/agent/tools/parse_example2.py +87 -0
- langroid/agent/tools/parse_example3.py +42 -0
- langroid/agent/tools/parse_test.py +791 -0
- langroid/agent/xml_tool_message.py +106 -0
- langroid/language_models/openai_gpt.py +6 -1
- {langroid-0.16.5.dist-info → langroid-0.16.7.dist-info}/METADATA +1 -1
- {langroid-0.16.5.dist-info → langroid-0.16.7.dist-info}/RECORD +80 -6
- pyproject.toml +1 -1
- {langroid-0.16.5.dist-info → langroid-0.16.7.dist-info}/LICENSE +0 -0
- {langroid-0.16.5.dist-info → langroid-0.16.7.dist-info}/WHEEL +0 -0
@@ -0,0 +1,791 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
import sys
|
3
|
+
from datetime import date, datetime, time
|
4
|
+
|
5
|
+
import parse
|
6
|
+
import pytest
|
7
|
+
|
8
|
+
|
9
|
+
def test_no_match():
|
10
|
+
# string does not match format
|
11
|
+
assert parse.parse("{{hello}}", "hello") is None
|
12
|
+
|
13
|
+
|
14
|
+
def test_nothing():
|
15
|
+
# do no actual parsing
|
16
|
+
r = parse.parse("{{hello}}", "{hello}")
|
17
|
+
assert r.fixed == ()
|
18
|
+
assert r.named == {}
|
19
|
+
|
20
|
+
|
21
|
+
def test_no_evaluate_result():
|
22
|
+
# pull a fixed value out of string
|
23
|
+
match = parse.parse("hello {}", "hello world", evaluate_result=False)
|
24
|
+
r = match.evaluate_result()
|
25
|
+
assert r.fixed == ("world",)
|
26
|
+
|
27
|
+
|
28
|
+
def test_regular_expression():
|
29
|
+
# match an actual regular expression
|
30
|
+
s = r"^(hello\s[wW]{}!+.*)$"
|
31
|
+
e = s.replace("{}", "orld")
|
32
|
+
r = parse.parse(s, e)
|
33
|
+
assert r.fixed == ("orld",)
|
34
|
+
e = s.replace("{}", ".*?")
|
35
|
+
r = parse.parse(s, e)
|
36
|
+
assert r.fixed == (".*?",)
|
37
|
+
|
38
|
+
|
39
|
+
def test_question_mark():
|
40
|
+
# issue9: make sure a ? in the parse string is handled correctly
|
41
|
+
r = parse.parse('"{}"?', '"teststr"?')
|
42
|
+
assert r[0] == "teststr"
|
43
|
+
|
44
|
+
|
45
|
+
def test_pipe():
|
46
|
+
# issue22: make sure a | in the parse string is handled correctly
|
47
|
+
r = parse.parse("| {}", "| teststr")
|
48
|
+
assert r[0] == "teststr"
|
49
|
+
|
50
|
+
|
51
|
+
def test_unicode():
|
52
|
+
# issue29: make sure unicode is parsable
|
53
|
+
r = parse.parse("{}", "t€ststr")
|
54
|
+
assert r[0] == "t€ststr"
|
55
|
+
|
56
|
+
|
57
|
+
def test_hexadecimal():
|
58
|
+
# issue42: make sure bare hexadecimal isn't matched as "digits"
|
59
|
+
r = parse.parse("{:d}", "abcdef")
|
60
|
+
assert r is None
|
61
|
+
|
62
|
+
|
63
|
+
def test_fixed():
|
64
|
+
# pull a fixed value out of string
|
65
|
+
r = parse.parse("hello {}", "hello world")
|
66
|
+
assert r.fixed == ("world",)
|
67
|
+
|
68
|
+
|
69
|
+
def test_left():
|
70
|
+
# pull left-aligned text out of string
|
71
|
+
r = parse.parse("{:<} world", "hello world")
|
72
|
+
assert r.fixed == ("hello",)
|
73
|
+
|
74
|
+
|
75
|
+
def test_right():
|
76
|
+
# pull right-aligned text out of string
|
77
|
+
r = parse.parse("hello {:>}", "hello world ")
|
78
|
+
assert r.fixed == ("world",)
|
79
|
+
|
80
|
+
|
81
|
+
def test_center():
|
82
|
+
# pull center-aligned text out of string
|
83
|
+
r = parse.parse("hello {:^} world", "hello there world")
|
84
|
+
assert r.fixed == ("there",)
|
85
|
+
|
86
|
+
|
87
|
+
def test_center_named():
|
88
|
+
# pull center-aligned text out of string
|
89
|
+
r = parse.parse("hello {blah:^} world", "hello there world")
|
90
|
+
assert r.named == {"blah": "there"}
|
91
|
+
|
92
|
+
|
93
|
+
def test_typed():
|
94
|
+
# pull a named, typed values out of string
|
95
|
+
r = parse.parse("hello {:d} {:w}", "hello 12 people")
|
96
|
+
assert r.fixed == (12, "people")
|
97
|
+
r = parse.parse("hello {:w} {:w}", "hello 12 people")
|
98
|
+
assert r.fixed == ("12", "people")
|
99
|
+
|
100
|
+
|
101
|
+
def test_sign():
|
102
|
+
# sign is ignored
|
103
|
+
r = parse.parse("Pi = {:.7f}", "Pi = 3.1415926")
|
104
|
+
assert r.fixed == (3.1415926,)
|
105
|
+
r = parse.parse("Pi = {:+.7f}", "Pi = 3.1415926")
|
106
|
+
assert r.fixed == (3.1415926,)
|
107
|
+
r = parse.parse("Pi = {:-.7f}", "Pi = 3.1415926")
|
108
|
+
assert r.fixed == (3.1415926,)
|
109
|
+
r = parse.parse("Pi = {: .7f}", "Pi = 3.1415926")
|
110
|
+
assert r.fixed == (3.1415926,)
|
111
|
+
|
112
|
+
|
113
|
+
def test_precision():
|
114
|
+
# pull a float out of a string
|
115
|
+
r = parse.parse("Pi = {:.7f}", "Pi = 3.1415926")
|
116
|
+
assert r.fixed == (3.1415926,)
|
117
|
+
r = parse.parse("Pi/10 = {:8.5f}", "Pi/10 = 0.31415")
|
118
|
+
assert r.fixed == (0.31415,)
|
119
|
+
# float may have not leading zero
|
120
|
+
r = parse.parse("Pi/10 = {:8.5f}", "Pi/10 = .31415")
|
121
|
+
assert r.fixed == (0.31415,)
|
122
|
+
r = parse.parse("Pi/10 = {:8.5f}", "Pi/10 = -.31415")
|
123
|
+
assert r.fixed == (-0.31415,)
|
124
|
+
|
125
|
+
|
126
|
+
def test_custom_type():
|
127
|
+
# use a custom type
|
128
|
+
r = parse.parse(
|
129
|
+
"{:shouty} {:spam}",
|
130
|
+
"hello world",
|
131
|
+
{"shouty": lambda s: s.upper(), "spam": lambda s: "".join(reversed(s))},
|
132
|
+
)
|
133
|
+
assert r.fixed == ("HELLO", "dlrow")
|
134
|
+
r = parse.parse("{:d}", "12", {"d": lambda s: int(s) * 2})
|
135
|
+
assert r.fixed == (24,)
|
136
|
+
r = parse.parse("{:d}", "12")
|
137
|
+
assert r.fixed == (12,)
|
138
|
+
|
139
|
+
|
140
|
+
def test_typed_fail():
|
141
|
+
# pull a named, typed values out of string
|
142
|
+
assert parse.parse("hello {:d} {:w}", "hello people 12") is None
|
143
|
+
|
144
|
+
|
145
|
+
def test_named():
|
146
|
+
# pull a named value out of string
|
147
|
+
r = parse.parse("hello {name}", "hello world")
|
148
|
+
assert r.named == {"name": "world"}
|
149
|
+
|
150
|
+
|
151
|
+
def test_named_repeated():
|
152
|
+
# test a name may be repeated
|
153
|
+
r = parse.parse("{n} {n}", "x x")
|
154
|
+
assert r.named == {"n": "x"}
|
155
|
+
|
156
|
+
|
157
|
+
def test_named_repeated_type():
|
158
|
+
# test a name may be repeated with type conversion
|
159
|
+
r = parse.parse("{n:d} {n:d}", "1 1")
|
160
|
+
assert r.named == {"n": 1}
|
161
|
+
|
162
|
+
|
163
|
+
def test_named_repeated_fail_value():
|
164
|
+
# test repeated name fails if value mismatches
|
165
|
+
r = parse.parse("{n} {n}", "x y")
|
166
|
+
assert r is None
|
167
|
+
|
168
|
+
|
169
|
+
def test_named_repeated_type_fail_value():
|
170
|
+
# test repeated name with type conversion fails if value mismatches
|
171
|
+
r = parse.parse("{n:d} {n:d}", "1 2")
|
172
|
+
assert r is None
|
173
|
+
|
174
|
+
|
175
|
+
def test_named_repeated_type_mismatch():
|
176
|
+
# test repeated name with mismatched type
|
177
|
+
with pytest.raises(parse.RepeatedNameError):
|
178
|
+
parse.compile("{n:d} {n:w}")
|
179
|
+
|
180
|
+
|
181
|
+
def test_mixed():
|
182
|
+
# pull a fixed and named values out of string
|
183
|
+
r = parse.parse("hello {} {name} {} {spam}", "hello world and other beings")
|
184
|
+
assert r.fixed == ("world", "other")
|
185
|
+
assert r.named == {"name": "and", "spam": "beings"}
|
186
|
+
|
187
|
+
|
188
|
+
def test_named_typed():
|
189
|
+
# pull a named, typed values out of string
|
190
|
+
r = parse.parse("hello {number:d} {things}", "hello 12 people")
|
191
|
+
assert r.named == {"number": 12, "things": "people"}
|
192
|
+
r = parse.parse("hello {number:w} {things}", "hello 12 people")
|
193
|
+
assert r.named == {"number": "12", "things": "people"}
|
194
|
+
|
195
|
+
|
196
|
+
def test_named_aligned_typed():
|
197
|
+
# pull a named, typed values out of string
|
198
|
+
r = parse.parse("hello {number:<d} {things}", "hello 12 people")
|
199
|
+
assert r.named == {"number": 12, "things": "people"}
|
200
|
+
r = parse.parse("hello {number:>d} {things}", "hello 12 people")
|
201
|
+
assert r.named == {"number": 12, "things": "people"}
|
202
|
+
r = parse.parse("hello {number:^d} {things}", "hello 12 people")
|
203
|
+
assert r.named == {"number": 12, "things": "people"}
|
204
|
+
|
205
|
+
|
206
|
+
def test_multiline():
|
207
|
+
r = parse.parse("hello\n{}\nworld", "hello\nthere\nworld")
|
208
|
+
assert r.fixed[0] == "there"
|
209
|
+
|
210
|
+
|
211
|
+
def test_spans():
|
212
|
+
# test the string sections our fields come from
|
213
|
+
string = "hello world"
|
214
|
+
r = parse.parse("hello {}", string)
|
215
|
+
assert r.spans == {0: (6, 11)}
|
216
|
+
start, end = r.spans[0]
|
217
|
+
assert string[start:end] == r.fixed[0]
|
218
|
+
|
219
|
+
string = "hello world"
|
220
|
+
r = parse.parse("hello {:>}", string)
|
221
|
+
assert r.spans == {0: (10, 15)}
|
222
|
+
start, end = r.spans[0]
|
223
|
+
assert string[start:end] == r.fixed[0]
|
224
|
+
|
225
|
+
string = "hello 0x12 world"
|
226
|
+
r = parse.parse("hello {val:x} world", string)
|
227
|
+
assert r.spans == {"val": (6, 10)}
|
228
|
+
start, end = r.spans["val"]
|
229
|
+
assert string[start:end] == "0x%x" % r.named["val"]
|
230
|
+
|
231
|
+
string = "hello world and other beings"
|
232
|
+
r = parse.parse("hello {} {name} {} {spam}", string)
|
233
|
+
assert r.spans == {0: (6, 11), "name": (12, 15), 1: (16, 21), "spam": (22, 28)}
|
234
|
+
|
235
|
+
|
236
|
+
def test_numbers():
|
237
|
+
# pull a numbers out of a string
|
238
|
+
def y(fmt, s, e, str_equals=False):
|
239
|
+
p = parse.compile(fmt)
|
240
|
+
r = p.parse(s)
|
241
|
+
assert r is not None
|
242
|
+
r = r.fixed[0]
|
243
|
+
if str_equals:
|
244
|
+
assert str(r) == str(e)
|
245
|
+
else:
|
246
|
+
assert r == e
|
247
|
+
|
248
|
+
def n(fmt, s, e):
|
249
|
+
assert parse.parse(fmt, s) is None
|
250
|
+
|
251
|
+
y("a {:d} b", "a 0 b", 0)
|
252
|
+
y("a {:d} b", "a 12 b", 12)
|
253
|
+
y("a {:5d} b", "a 12 b", 12)
|
254
|
+
y("a {:5d} b", "a -12 b", -12)
|
255
|
+
y("a {:d} b", "a -12 b", -12)
|
256
|
+
y("a {:d} b", "a +12 b", 12)
|
257
|
+
y("a {:d} b", "a 12 b", 12)
|
258
|
+
y("a {:d} b", "a 0b1000 b", 8)
|
259
|
+
y("a {:d} b", "a 0o1000 b", 512)
|
260
|
+
y("a {:d} b", "a 0x1000 b", 4096)
|
261
|
+
y("a {:d} b", "a 0xabcdef b", 0xABCDEF)
|
262
|
+
|
263
|
+
y("a {:%} b", "a 100% b", 1)
|
264
|
+
y("a {:%} b", "a 50% b", 0.5)
|
265
|
+
y("a {:%} b", "a 50.1% b", 0.501)
|
266
|
+
|
267
|
+
y("a {:n} b", "a 100 b", 100)
|
268
|
+
y("a {:n} b", "a 1,000 b", 1000)
|
269
|
+
y("a {:n} b", "a 1.000 b", 1000)
|
270
|
+
y("a {:n} b", "a -1,000 b", -1000)
|
271
|
+
y("a {:n} b", "a 10,000 b", 10000)
|
272
|
+
y("a {:n} b", "a 100,000 b", 100000)
|
273
|
+
n("a {:n} b", "a 100,00 b", None)
|
274
|
+
y("a {:n} b", "a 100.000 b", 100000)
|
275
|
+
y("a {:n} b", "a 1.000.000 b", 1000000)
|
276
|
+
|
277
|
+
y("a {:f} b", "a 12.0 b", 12.0)
|
278
|
+
y("a {:f} b", "a -12.1 b", -12.1)
|
279
|
+
y("a {:f} b", "a +12.1 b", 12.1)
|
280
|
+
y("a {:f} b", "a .121 b", 0.121)
|
281
|
+
y("a {:f} b", "a -.121 b", -0.121)
|
282
|
+
n("a {:f} b", "a 12 b", None)
|
283
|
+
|
284
|
+
y("a {:e} b", "a 1.0e10 b", 1.0e10)
|
285
|
+
y("a {:e} b", "a .0e10 b", 0.0e10)
|
286
|
+
y("a {:e} b", "a 1.0E10 b", 1.0e10)
|
287
|
+
y("a {:e} b", "a 1.10000e10 b", 1.1e10)
|
288
|
+
y("a {:e} b", "a 1.0e-10 b", 1.0e-10)
|
289
|
+
y("a {:e} b", "a 1.0e+10 b", 1.0e10)
|
290
|
+
# can't actually test this one on values 'cos nan != nan
|
291
|
+
y("a {:e} b", "a nan b", float("nan"), str_equals=True)
|
292
|
+
y("a {:e} b", "a NAN b", float("nan"), str_equals=True)
|
293
|
+
y("a {:e} b", "a inf b", float("inf"))
|
294
|
+
y("a {:e} b", "a +inf b", float("inf"))
|
295
|
+
y("a {:e} b", "a -inf b", float("-inf"))
|
296
|
+
y("a {:e} b", "a INF b", float("inf"))
|
297
|
+
y("a {:e} b", "a +INF b", float("inf"))
|
298
|
+
y("a {:e} b", "a -INF b", float("-inf"))
|
299
|
+
|
300
|
+
y("a {:g} b", "a 1 b", 1)
|
301
|
+
y("a {:g} b", "a 1e10 b", 1e10)
|
302
|
+
y("a {:g} b", "a 1.0e10 b", 1.0e10)
|
303
|
+
y("a {:g} b", "a 1.0E10 b", 1.0e10)
|
304
|
+
|
305
|
+
y("a {:b} b", "a 1000 b", 8)
|
306
|
+
y("a {:b} b", "a 0b1000 b", 8)
|
307
|
+
y("a {:o} b", "a 12345670 b", int("12345670", 8))
|
308
|
+
y("a {:o} b", "a 0o12345670 b", int("12345670", 8))
|
309
|
+
y("a {:x} b", "a 1234567890abcdef b", 0x1234567890ABCDEF)
|
310
|
+
y("a {:x} b", "a 1234567890ABCDEF b", 0x1234567890ABCDEF)
|
311
|
+
y("a {:x} b", "a 0x1234567890abcdef b", 0x1234567890ABCDEF)
|
312
|
+
y("a {:x} b", "a 0x1234567890ABCDEF b", 0x1234567890ABCDEF)
|
313
|
+
|
314
|
+
y("a {:05d} b", "a 00001 b", 1)
|
315
|
+
y("a {:05d} b", "a -00001 b", -1)
|
316
|
+
y("a {:05d} b", "a +00001 b", 1)
|
317
|
+
y("a {:02d} b", "a 10 b", 10)
|
318
|
+
|
319
|
+
y("a {:=d} b", "a 000012 b", 12)
|
320
|
+
y("a {:x=5d} b", "a xxx12 b", 12)
|
321
|
+
y("a {:x=5d} b", "a -xxx12 b", -12)
|
322
|
+
|
323
|
+
# Test that hex numbers that ambiguously start with 0b / 0B are parsed correctly
|
324
|
+
# See issue #65 (https://github.com/r1chardj0n3s/parse/issues/65)
|
325
|
+
y("a {:x} b", "a 0B b", 0xB)
|
326
|
+
y("a {:x} b", "a 0B1 b", 0xB1)
|
327
|
+
y("a {:x} b", "a 0b b", 0xB)
|
328
|
+
y("a {:x} b", "a 0b1 b", 0xB1)
|
329
|
+
|
330
|
+
# Test that number signs are understood correctly
|
331
|
+
y("a {:d} b", "a -0o10 b", -8)
|
332
|
+
y("a {:d} b", "a -0b1010 b", -10)
|
333
|
+
y("a {:d} b", "a -0x1010 b", -0x1010)
|
334
|
+
y("a {:o} b", "a -10 b", -8)
|
335
|
+
y("a {:b} b", "a -1010 b", -10)
|
336
|
+
y("a {:x} b", "a -1010 b", -0x1010)
|
337
|
+
y("a {:d} b", "a +0o10 b", 8)
|
338
|
+
y("a {:d} b", "a +0b1010 b", 10)
|
339
|
+
y("a {:d} b", "a +0x1010 b", 0x1010)
|
340
|
+
y("a {:o} b", "a +10 b", 8)
|
341
|
+
y("a {:b} b", "a +1010 b", 10)
|
342
|
+
y("a {:x} b", "a +1010 b", 0x1010)
|
343
|
+
|
344
|
+
|
345
|
+
def test_two_datetimes():
|
346
|
+
r = parse.parse("a {:ti} {:ti} b", "a 1997-07-16 2012-08-01 b")
|
347
|
+
assert len(r.fixed) == 2
|
348
|
+
assert r[0] == datetime(1997, 7, 16)
|
349
|
+
assert r[1] == datetime(2012, 8, 1)
|
350
|
+
|
351
|
+
|
352
|
+
def test_flexible_datetimes():
|
353
|
+
r = parse.parse("a {:%Y-%m-%d} b", "a 1997-07-16 b")
|
354
|
+
assert len(r.fixed) == 1
|
355
|
+
assert r[0] == date(1997, 7, 16)
|
356
|
+
|
357
|
+
r = parse.parse("a {:%Y-%b-%d} b", "a 1997-Feb-16 b")
|
358
|
+
assert len(r.fixed) == 1
|
359
|
+
assert r[0] == date(1997, 2, 16)
|
360
|
+
|
361
|
+
r = parse.parse("a {:%Y-%b-%d} {:d} b", "a 1997-Feb-16 8 b")
|
362
|
+
assert len(r.fixed) == 2
|
363
|
+
assert r[0] == date(1997, 2, 16)
|
364
|
+
|
365
|
+
r = parse.parse("a {my_date:%Y-%b-%d} {num:d} b", "a 1997-Feb-16 8 b")
|
366
|
+
assert (r.named["my_date"]) == date(1997, 2, 16)
|
367
|
+
assert (r.named["num"]) == 8
|
368
|
+
|
369
|
+
r = parse.parse("a {:%Y-%B-%d} b", "a 1997-February-16 b")
|
370
|
+
assert r[0] == date(1997, 2, 16)
|
371
|
+
|
372
|
+
r = parse.parse("a {:%Y%m%d} b", "a 19970716 b")
|
373
|
+
assert r[0] == date(1997, 7, 16)
|
374
|
+
|
375
|
+
|
376
|
+
def test_flexible_datetime_with_colon():
|
377
|
+
r = parse.parse("{dt:%Y-%m-%d %H:%M:%S}", "2023-11-21 13:23:27")
|
378
|
+
assert r.named["dt"] == datetime(2023, 11, 21, 13, 23, 27)
|
379
|
+
|
380
|
+
|
381
|
+
def test_datetime_with_various_subsecond_precision():
|
382
|
+
r = parse.parse("{dt:%Y-%m-%d %H:%M:%S.%f}", "2023-11-21 13:23:27.123456")
|
383
|
+
assert r.named["dt"] == datetime(2023, 11, 21, 13, 23, 27, 123456)
|
384
|
+
|
385
|
+
r = parse.parse("{dt:%Y-%m-%d %H:%M:%S.%f}", "2023-11-21 13:23:27.12345")
|
386
|
+
assert r.named["dt"] == datetime(2023, 11, 21, 13, 23, 27, 123450)
|
387
|
+
|
388
|
+
r = parse.parse("{dt:%Y-%m-%d %H:%M:%S.%f}", "2023-11-21 13:23:27.1234")
|
389
|
+
assert r.named["dt"] == datetime(2023, 11, 21, 13, 23, 27, 123400)
|
390
|
+
|
391
|
+
r = parse.parse("{dt:%Y-%m-%d %H:%M:%S.%f}", "2023-11-21 13:23:27.123")
|
392
|
+
assert r.named["dt"] == datetime(2023, 11, 21, 13, 23, 27, 123000)
|
393
|
+
|
394
|
+
r = parse.parse("{dt:%Y-%m-%d %H:%M:%S.%f}", "2023-11-21 13:23:27.0")
|
395
|
+
assert r.named["dt"] == datetime(2023, 11, 21, 13, 23, 27, 0)
|
396
|
+
|
397
|
+
|
398
|
+
@pytest.mark.skipif(
|
399
|
+
sys.version_info[0] < 3, reason="Python 3+ required for timezone support"
|
400
|
+
)
|
401
|
+
def test_flexible_datetime_with_timezone():
|
402
|
+
from datetime import timezone
|
403
|
+
|
404
|
+
r = parse.parse("{dt:%Y-%m-%d %H:%M:%S %z}", "2023-11-21 13:23:27 +0000")
|
405
|
+
assert r.named["dt"] == datetime(2023, 11, 21, 13, 23, 27, tzinfo=timezone.utc)
|
406
|
+
|
407
|
+
|
408
|
+
@pytest.mark.skipif(
|
409
|
+
sys.version_info[0] < 3, reason="Python 3+ required for timezone support"
|
410
|
+
)
|
411
|
+
def test_flexible_datetime_with_timezone_that_has_colons():
|
412
|
+
from datetime import timezone
|
413
|
+
|
414
|
+
r = parse.parse("{dt:%Y-%m-%d %H:%M:%S %z}", "2023-11-21 13:23:27 +00:00:00")
|
415
|
+
assert r.named["dt"] == datetime(2023, 11, 21, 13, 23, 27, tzinfo=timezone.utc)
|
416
|
+
|
417
|
+
|
418
|
+
def test_flexible_time():
|
419
|
+
r = parse.parse("a {time:%H:%M:%S} b", "a 13:23:27 b")
|
420
|
+
assert r.named["time"] == time(13, 23, 27)
|
421
|
+
|
422
|
+
|
423
|
+
def test_flexible_time_no_hour():
|
424
|
+
r = parse.parse("a {time:%M:%S} b", "a 23:27 b")
|
425
|
+
assert r.named["time"] == time(0, 23, 27)
|
426
|
+
|
427
|
+
|
428
|
+
def test_flexible_time_ms():
|
429
|
+
r = parse.parse("a {time:%M:%S:%f} b", "a 23:27:123456 b")
|
430
|
+
assert r.named["time"] == time(0, 23, 27, 123456)
|
431
|
+
|
432
|
+
|
433
|
+
def test_flexible_dates_single_digit():
|
434
|
+
r = parse.parse("{dt:%Y/%m/%d}", "2023/1/1")
|
435
|
+
assert r.named["dt"] == date(2023, 1, 1)
|
436
|
+
|
437
|
+
|
438
|
+
def test_flexible_dates_j():
|
439
|
+
r = parse.parse("{dt:%Y/%j}", "2023/9")
|
440
|
+
assert r.named["dt"] == date(2023, 1, 9)
|
441
|
+
|
442
|
+
r = parse.parse("{dt:%Y/%j}", "2023/009")
|
443
|
+
assert r.named["dt"] == date(2023, 1, 9)
|
444
|
+
|
445
|
+
|
446
|
+
def test_flexible_dates_year_current_year_inferred():
|
447
|
+
r = parse.parse("{dt:%j}", "9")
|
448
|
+
assert r.named["dt"] == date(datetime.today().year, 1, 9)
|
449
|
+
|
450
|
+
|
451
|
+
def test_datetimes():
|
452
|
+
def y(fmt, s, e, tz=None):
|
453
|
+
p = parse.compile(fmt)
|
454
|
+
r = p.parse(s)
|
455
|
+
assert r is not None
|
456
|
+
r = r.fixed[0]
|
457
|
+
assert r == e
|
458
|
+
assert tz is None or r.tzinfo == tz
|
459
|
+
|
460
|
+
utc = parse.FixedTzOffset(0, "UTC")
|
461
|
+
assert repr(utc) == "<FixedTzOffset UTC 0:00:00>"
|
462
|
+
aest = parse.FixedTzOffset(10 * 60, "+1000")
|
463
|
+
tz60 = parse.FixedTzOffset(60, "+01:00")
|
464
|
+
|
465
|
+
# ISO 8660 variants
|
466
|
+
# YYYY-MM-DD (eg 1997-07-16)
|
467
|
+
y("a {:ti} b", "a 1997-07-16 b", datetime(1997, 7, 16))
|
468
|
+
|
469
|
+
# YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
|
470
|
+
y("a {:ti} b", "a 1997-07-16 19:20 b", datetime(1997, 7, 16, 19, 20, 0))
|
471
|
+
y("a {:ti} b", "a 1997-07-16T19:20 b", datetime(1997, 7, 16, 19, 20, 0))
|
472
|
+
y(
|
473
|
+
"a {:ti} b",
|
474
|
+
"a 1997-07-16T19:20Z b",
|
475
|
+
datetime(1997, 7, 16, 19, 20, tzinfo=utc),
|
476
|
+
)
|
477
|
+
y(
|
478
|
+
"a {:ti} b",
|
479
|
+
"a 1997-07-16T19:20+0100 b",
|
480
|
+
datetime(1997, 7, 16, 19, 20, tzinfo=tz60),
|
481
|
+
)
|
482
|
+
y(
|
483
|
+
"a {:ti} b",
|
484
|
+
"a 1997-07-16T19:20+01:00 b",
|
485
|
+
datetime(1997, 7, 16, 19, 20, tzinfo=tz60),
|
486
|
+
)
|
487
|
+
y(
|
488
|
+
"a {:ti} b",
|
489
|
+
"a 1997-07-16T19:20 +01:00 b",
|
490
|
+
datetime(1997, 7, 16, 19, 20, tzinfo=tz60),
|
491
|
+
)
|
492
|
+
|
493
|
+
# YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
|
494
|
+
y("a {:ti} b", "a 1997-07-16 19:20:30 b", datetime(1997, 7, 16, 19, 20, 30))
|
495
|
+
y("a {:ti} b", "a 1997-07-16T19:20:30 b", datetime(1997, 7, 16, 19, 20, 30))
|
496
|
+
y(
|
497
|
+
"a {:ti} b",
|
498
|
+
"a 1997-07-16T19:20:30Z b",
|
499
|
+
datetime(1997, 7, 16, 19, 20, 30, tzinfo=utc),
|
500
|
+
)
|
501
|
+
y(
|
502
|
+
"a {:ti} b",
|
503
|
+
"a 1997-07-16T19:20:30+01:00 b",
|
504
|
+
datetime(1997, 7, 16, 19, 20, 30, tzinfo=tz60),
|
505
|
+
)
|
506
|
+
y(
|
507
|
+
"a {:ti} b",
|
508
|
+
"a 1997-07-16T19:20:30 +01:00 b",
|
509
|
+
datetime(1997, 7, 16, 19, 20, 30, tzinfo=tz60),
|
510
|
+
)
|
511
|
+
|
512
|
+
# YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
|
513
|
+
y(
|
514
|
+
"a {:ti} b",
|
515
|
+
"a 1997-07-16 19:20:30.500000 b",
|
516
|
+
datetime(1997, 7, 16, 19, 20, 30, 500000),
|
517
|
+
)
|
518
|
+
y(
|
519
|
+
"a {:ti} b",
|
520
|
+
"a 1997-07-16T19:20:30.500000 b",
|
521
|
+
datetime(1997, 7, 16, 19, 20, 30, 500000),
|
522
|
+
)
|
523
|
+
y(
|
524
|
+
"a {:ti} b",
|
525
|
+
"a 1997-07-16T19:20:30.5Z b",
|
526
|
+
datetime(1997, 7, 16, 19, 20, 30, 500000, tzinfo=utc),
|
527
|
+
)
|
528
|
+
y(
|
529
|
+
"a {:ti} b",
|
530
|
+
"a 1997-07-16T19:20:30.5+01:00 b",
|
531
|
+
datetime(1997, 7, 16, 19, 20, 30, 500000, tzinfo=tz60),
|
532
|
+
)
|
533
|
+
|
534
|
+
aest_d = datetime(2011, 11, 21, 10, 21, 36, tzinfo=aest)
|
535
|
+
dt = datetime(2011, 11, 21, 10, 21, 36)
|
536
|
+
dt00 = datetime(2011, 11, 21, 10, 21)
|
537
|
+
d = datetime(2011, 11, 21)
|
538
|
+
|
539
|
+
# te RFC2822 e-mail format datetime
|
540
|
+
y("a {:te} b", "a Mon, 21 Nov 2011 10:21:36 +1000 b", aest_d)
|
541
|
+
y("a {:te} b", "a Mon, 21 Nov 2011 10:21:36 +10:00 b", aest_d)
|
542
|
+
y("a {:te} b", "a 21 Nov 2011 10:21:36 +1000 b", aest_d)
|
543
|
+
|
544
|
+
# tg global (day/month) format datetime
|
545
|
+
y("a {:tg} b", "a 21/11/2011 10:21:36 AM +1000 b", aest_d)
|
546
|
+
y("a {:tg} b", "a 21/11/2011 10:21:36 AM +10:00 b", aest_d)
|
547
|
+
y("a {:tg} b", "a 21-11-2011 10:21:36 AM +1000 b", aest_d)
|
548
|
+
y("a {:tg} b", "a 21/11/2011 10:21:36 +1000 b", aest_d)
|
549
|
+
y("a {:tg} b", "a 21/11/2011 10:21:36 b", dt)
|
550
|
+
y("a {:tg} b", "a 21/11/2011 10:21 b", dt00)
|
551
|
+
y("a {:tg} b", "a 21-11-2011 b", d)
|
552
|
+
y("a {:tg} b", "a 21-Nov-2011 10:21:36 AM +1000 b", aest_d)
|
553
|
+
y("a {:tg} b", "a 21-November-2011 10:21:36 AM +1000 b", aest_d)
|
554
|
+
|
555
|
+
# ta US (month/day) format datetime
|
556
|
+
y("a {:ta} b", "a 11/21/2011 10:21:36 AM +1000 b", aest_d)
|
557
|
+
y("a {:ta} b", "a 11/21/2011 10:21:36 AM +10:00 b", aest_d)
|
558
|
+
y("a {:ta} b", "a 11-21-2011 10:21:36 AM +1000 b", aest_d)
|
559
|
+
y("a {:ta} b", "a 11/21/2011 10:21:36 +1000 b", aest_d)
|
560
|
+
y("a {:ta} b", "a 11/21/2011 10:21:36 b", dt)
|
561
|
+
y("a {:ta} b", "a 11/21/2011 10:21 b", dt00)
|
562
|
+
y("a {:ta} b", "a 11-21-2011 b", d)
|
563
|
+
y("a {:ta} b", "a Nov-21-2011 10:21:36 AM +1000 b", aest_d)
|
564
|
+
y("a {:ta} b", "a November-21-2011 10:21:36 AM +1000 b", aest_d)
|
565
|
+
y("a {:ta} b", "a November-21-2011 b", d)
|
566
|
+
|
567
|
+
# ts Linux System log format datetime
|
568
|
+
y(
|
569
|
+
"a {:ts} b",
|
570
|
+
"a Nov 21 10:21:36 b",
|
571
|
+
datetime(datetime.today().year, 11, 21, 10, 21, 36),
|
572
|
+
)
|
573
|
+
y(
|
574
|
+
"a {:ts} b",
|
575
|
+
"a Nov 1 10:21:36 b",
|
576
|
+
datetime(datetime.today().year, 11, 1, 10, 21, 36),
|
577
|
+
)
|
578
|
+
y(
|
579
|
+
"a {:ts} b",
|
580
|
+
"a Nov 1 03:21:36 b",
|
581
|
+
datetime(datetime.today().year, 11, 1, 3, 21, 36),
|
582
|
+
)
|
583
|
+
|
584
|
+
# th HTTP log format date/time datetime
|
585
|
+
y("a {:th} b", "a 21/Nov/2011:10:21:36 +1000 b", aest_d)
|
586
|
+
y("a {:th} b", "a 21/Nov/2011:10:21:36 +10:00 b", aest_d)
|
587
|
+
|
588
|
+
d = datetime(2011, 11, 21, 10, 21, 36)
|
589
|
+
|
590
|
+
# tc ctime() format datetime
|
591
|
+
y("a {:tc} b", "a Mon Nov 21 10:21:36 2011 b", d)
|
592
|
+
|
593
|
+
t530 = parse.FixedTzOffset(-5 * 60 - 30, "-5:30")
|
594
|
+
t830 = parse.FixedTzOffset(-8 * 60 - 30, "-8:30")
|
595
|
+
|
596
|
+
# tt Time time
|
597
|
+
y("a {:tt} b", "a 10:21:36 AM +1000 b", time(10, 21, 36, tzinfo=aest))
|
598
|
+
y("a {:tt} b", "a 10:21:36 AM +10:00 b", time(10, 21, 36, tzinfo=aest))
|
599
|
+
y("a {:tt} b", "a 10:21:36 AM b", time(10, 21, 36))
|
600
|
+
y("a {:tt} b", "a 10:21:36 PM b", time(22, 21, 36))
|
601
|
+
y("a {:tt} b", "a 10:21:36 b", time(10, 21, 36))
|
602
|
+
y("a {:tt} b", "a 10:21 b", time(10, 21))
|
603
|
+
y("a {:tt} b", "a 10:21:36 PM -5:30 b", time(22, 21, 36, tzinfo=t530))
|
604
|
+
y("a {:tt} b", "a 10:21:36 PM -530 b", time(22, 21, 36, tzinfo=t530))
|
605
|
+
y("a {:tt} b", "a 10:21:36 PM -05:30 b", time(22, 21, 36, tzinfo=t530))
|
606
|
+
y("a {:tt} b", "a 10:21:36 PM -0530 b", time(22, 21, 36, tzinfo=t530))
|
607
|
+
y("a {:tt} b", "a 10:21:36 PM -08:30 b", time(22, 21, 36, tzinfo=t830))
|
608
|
+
y("a {:tt} b", "a 10:21:36 PM -0830 b", time(22, 21, 36, tzinfo=t830))
|
609
|
+
|
610
|
+
|
611
|
+
def test_datetime_group_count():
|
612
|
+
# test we increment the group count correctly for datetimes
|
613
|
+
r = parse.parse("{:ti} {}", "1972-01-01 spam")
|
614
|
+
assert r.fixed[1] == "spam"
|
615
|
+
r = parse.parse("{:tg} {}", "1-1-1972 spam")
|
616
|
+
assert r.fixed[1] == "spam"
|
617
|
+
r = parse.parse("{:ta} {}", "1-1-1972 spam")
|
618
|
+
assert r.fixed[1] == "spam"
|
619
|
+
r = parse.parse("{:th} {}", "21/Nov/2011:10:21:36 +1000 spam")
|
620
|
+
assert r.fixed[1] == "spam"
|
621
|
+
r = parse.parse("{:te} {}", "21 Nov 2011 10:21:36 +1000 spam")
|
622
|
+
assert r.fixed[1] == "spam"
|
623
|
+
r = parse.parse("{:tc} {}", "Mon Nov 21 10:21:36 2011 spam")
|
624
|
+
assert r.fixed[1] == "spam"
|
625
|
+
r = parse.parse("{:tt} {}", "10:21 spam")
|
626
|
+
assert r.fixed[1] == "spam"
|
627
|
+
|
628
|
+
|
629
|
+
def test_mixed_types():
|
630
|
+
# stress-test: pull one of everything out of a string
|
631
|
+
r = parse.parse(
|
632
|
+
"""
|
633
|
+
letters: {:w}
|
634
|
+
non-letters: {:W}
|
635
|
+
whitespace: "{:s}"
|
636
|
+
non-whitespace: \t{:S}\n
|
637
|
+
digits: {:d} {:d}
|
638
|
+
non-digits: {:D}
|
639
|
+
numbers with thousands: {:n}
|
640
|
+
fixed-point: {:f}
|
641
|
+
floating-point: {:e}
|
642
|
+
general numbers: {:g} {:g}
|
643
|
+
binary: {:b}
|
644
|
+
octal: {:o}
|
645
|
+
hex: {:x}
|
646
|
+
ISO 8601 e.g. {:ti}
|
647
|
+
RFC2822 e.g. {:te}
|
648
|
+
Global e.g. {:tg}
|
649
|
+
US e.g. {:ta}
|
650
|
+
ctime() e.g. {:tc}
|
651
|
+
HTTP e.g. {:th}
|
652
|
+
time: {:tt}
|
653
|
+
final value: {}
|
654
|
+
""",
|
655
|
+
"""
|
656
|
+
letters: abcdef_GHIJLK
|
657
|
+
non-letters: !@#%$ *^%
|
658
|
+
whitespace: " \t\n"
|
659
|
+
non-whitespace: \tabc\n
|
660
|
+
digits: 12345 0b1011011
|
661
|
+
non-digits: abcdef
|
662
|
+
numbers with thousands: 1,000
|
663
|
+
fixed-point: 100.2345
|
664
|
+
floating-point: 1.1e-10
|
665
|
+
general numbers: 1 1.1
|
666
|
+
binary: 0b1000
|
667
|
+
octal: 0o1000
|
668
|
+
hex: 0x1000
|
669
|
+
ISO 8601 e.g. 1972-01-20T10:21:36Z
|
670
|
+
RFC2822 e.g. Mon, 20 Jan 1972 10:21:36 +1000
|
671
|
+
Global e.g. 20/1/1972 10:21:36 AM +1:00
|
672
|
+
US e.g. 1/20/1972 10:21:36 PM +10:30
|
673
|
+
ctime() e.g. Sun Sep 16 01:03:52 1973
|
674
|
+
HTTP e.g. 21/Nov/2011:00:07:11 +0000
|
675
|
+
time: 10:21:36 PM -5:30
|
676
|
+
final value: spam
|
677
|
+
""",
|
678
|
+
)
|
679
|
+
assert r is not None
|
680
|
+
assert r.fixed[22] == "spam"
|
681
|
+
|
682
|
+
|
683
|
+
def test_mixed_type_variant():
|
684
|
+
r = parse.parse(
|
685
|
+
"""
|
686
|
+
letters: {:w}
|
687
|
+
non-letters: {:W}
|
688
|
+
whitespace: "{:s}"
|
689
|
+
non-whitespace: \t{:S}\n
|
690
|
+
digits: {:d}
|
691
|
+
non-digits: {:D}
|
692
|
+
numbers with thousands: {:n}
|
693
|
+
fixed-point: {:f}
|
694
|
+
floating-point: {:e}
|
695
|
+
general numbers: {:g} {:g}
|
696
|
+
binary: {:b}
|
697
|
+
octal: {:o}
|
698
|
+
hex: {:x}
|
699
|
+
ISO 8601 e.g. {:ti}
|
700
|
+
RFC2822 e.g. {:te}
|
701
|
+
Global e.g. {:tg}
|
702
|
+
US e.g. {:ta}
|
703
|
+
ctime() e.g. {:tc}
|
704
|
+
HTTP e.g. {:th}
|
705
|
+
time: {:tt}
|
706
|
+
final value: {}
|
707
|
+
""",
|
708
|
+
"""
|
709
|
+
letters: abcdef_GHIJLK
|
710
|
+
non-letters: !@#%$ *^%
|
711
|
+
whitespace: " \t\n"
|
712
|
+
non-whitespace: \tabc\n
|
713
|
+
digits: 0xabcdef
|
714
|
+
non-digits: abcdef
|
715
|
+
numbers with thousands: 1.000.000
|
716
|
+
fixed-point: 0.00001
|
717
|
+
floating-point: NAN
|
718
|
+
general numbers: 1.1e10 nan
|
719
|
+
binary: 0B1000
|
720
|
+
octal: 0O1000
|
721
|
+
hex: 0X1000
|
722
|
+
ISO 8601 e.g. 1972-01-20T10:21:36Z
|
723
|
+
RFC2822 e.g. Mon, 20 Jan 1972 10:21:36 +1000
|
724
|
+
Global e.g. 20/1/1972 10:21:36 AM +1:00
|
725
|
+
US e.g. 1/20/1972 10:21:36 PM +10:30
|
726
|
+
ctime() e.g. Sun Sep 16 01:03:52 1973
|
727
|
+
HTTP e.g. 21/Nov/2011:00:07:11 +0000
|
728
|
+
time: 10:21:36 PM -5:30
|
729
|
+
final value: spam
|
730
|
+
""",
|
731
|
+
)
|
732
|
+
assert r is not None
|
733
|
+
assert r.fixed[21] == "spam"
|
734
|
+
|
735
|
+
|
736
|
+
@pytest.mark.skipif(
|
737
|
+
sys.version_info >= (3, 5),
|
738
|
+
reason="Python 3.5 removed the limit of 100 named groups in a regular expression",
|
739
|
+
)
|
740
|
+
def test_too_many_fields():
|
741
|
+
# Python 3.5 removed the limit of 100 named groups in a regular expression,
|
742
|
+
# so only test for the exception if the limit exists.
|
743
|
+
p = parse.compile("{:ti}" * 15)
|
744
|
+
with pytest.raises(parse.TooManyFields):
|
745
|
+
p.parse("")
|
746
|
+
|
747
|
+
|
748
|
+
def test_letters():
|
749
|
+
res = parse.parse("{:l}", "")
|
750
|
+
assert res is None
|
751
|
+
res = parse.parse("{:l}", "sPaM")
|
752
|
+
assert res.fixed == ("sPaM",)
|
753
|
+
res = parse.parse("{:l}", "sP4M")
|
754
|
+
assert res is None
|
755
|
+
res = parse.parse("{:l}", "sP_M")
|
756
|
+
assert res is None
|
757
|
+
|
758
|
+
|
759
|
+
def test_strftime_strptime_roundtrip():
|
760
|
+
dt = datetime.now()
|
761
|
+
fmt = "_".join([k for k in parse.dt_format_to_regex if k != "%z"])
|
762
|
+
s = dt.strftime(fmt)
|
763
|
+
[res] = parse.parse("{:" + fmt + "}", s)
|
764
|
+
assert res == dt
|
765
|
+
|
766
|
+
|
767
|
+
def test_parser_format():
|
768
|
+
parser = parse.compile("hello {}")
|
769
|
+
assert parser.format.format("world") == "hello world"
|
770
|
+
with pytest.raises(AttributeError):
|
771
|
+
parser.format = "hi {}"
|
772
|
+
|
773
|
+
|
774
|
+
def test_hyphen_inside_field_name():
|
775
|
+
# https://github.com/r1chardj0n3s/parse/issues/86
|
776
|
+
# https://github.com/python-openapi/openapi-core/issues/672
|
777
|
+
template = "/local/sub/{user-id}/duration"
|
778
|
+
assert parse.Parser(template).named_fields == ["user_id"]
|
779
|
+
string = "https://dummy_server.com/local/sub/1647222638/duration"
|
780
|
+
result = parse.search(template, string)
|
781
|
+
assert result["user-id"] == "1647222638"
|
782
|
+
|
783
|
+
|
784
|
+
def test_hyphen_inside_field_name_collision_handling():
|
785
|
+
template = "/foo/{user-id}/{user_id}/{user.id}/bar/"
|
786
|
+
assert parse.Parser(template).named_fields == ["user_id", "user__id", "user___id"]
|
787
|
+
string = "/foo/1/2/3/bar/"
|
788
|
+
result = parse.search(template, string)
|
789
|
+
assert result["user-id"] == "1"
|
790
|
+
assert result["user_id"] == "2"
|
791
|
+
assert result["user.id"] == "3"
|