Skip to content

Commit

Permalink
Merge pull request #170 from r1chardj0n3s/pytests
Browse files Browse the repository at this point in the history
split massive test_parse.py into submodules
  • Loading branch information
wimglenn authored Nov 25, 2023
2 parents cd0c3c2 + e8818d8 commit 5687cf0
Show file tree
Hide file tree
Showing 9 changed files with 1,290 additions and 1,223 deletions.
10 changes: 5 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ with the same identifier:
>>> def shouty(string):
... return string.upper()
...
>>> parse('{:shouty} world', 'hello world', dict(shouty=shouty))
>>> parse('{:shouty} world', 'hello world', {"shouty": shouty})
<Result ('HELLO',) {}>
If the type converter has the optional ``pattern`` attribute, it is used as
Expand All @@ -350,9 +350,9 @@ regular expression for better pattern matching (instead of the default one):
>>> def parse_number(text):
... return int(text)
>>> parse_number.pattern = r'\d+'
>>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number))
>>> parse('Answer: {number:Number}', 'Answer: 42', {"Number": parse_number})
<Result () {'number': 42}>
>>> _ = parse('Answer: {:Number}', 'Answer: Alice', dict(Number=parse_number))
>>> _ = parse('Answer: {:Number}', 'Answer: Alice', {"Number": parse_number})
>>> assert _ is None, "MISMATCH"
You can also use the ``with_pattern(pattern)`` decorator to add this
Expand All @@ -364,7 +364,7 @@ information to a type converter function:
>>> @with_pattern(r'\d+')
... def parse_number(text):
... return int(text)
>>> parse('Answer: {number:Number}', 'Answer: 42', dict(Number=parse_number))
>>> parse('Answer: {number:Number}', 'Answer: 42', {"Number": parse_number})
<Result () {'number': 42}>
A more complete example of a custom type might be:
Expand All @@ -390,7 +390,7 @@ in the ``with_pattern()`` decorator:
>>> @with_pattern(r'((\d+))', regex_group_count=2)
... def parse_number2(text):
... return int(text)
>>> parse('Answer: {:Number2} {:Number2}', 'Answer: 42 43', dict(Number2=parse_number2))
>>> parse('Answer: {:Number2} {:Number2}', 'Answer: 42 43', {"Number2": parse_number2})
<Result (42, 43) {}>
Otherwise, this may cause parsing problems with unnamed/fixed parameters.
Expand Down
76 changes: 38 additions & 38 deletions parse.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
from __future__ import absolute_import

__version__ = "1.20.0"

# yes, I now have two problems
import logging
import re
import sys
from datetime import datetime, time, tzinfo, timedelta
from datetime import datetime
from datetime import time
from datetime import timedelta
from datetime import tzinfo
from decimal import Decimal
from functools import partial
import logging

__all__ = "parse search findall with_pattern".split()

__version__ = "1.20.0"
__all__ = ["parse", "search", "findall", "with_pattern"]

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -139,31 +141,31 @@ def __eq__(self, other):
return self._name == other._name and self._offset == other._offset


MONTHS_MAP = dict(
Jan=1,
January=1,
Feb=2,
February=2,
Mar=3,
March=3,
Apr=4,
April=4,
May=5,
Jun=6,
June=6,
Jul=7,
July=7,
Aug=8,
August=8,
Sep=9,
September=9,
Oct=10,
October=10,
Nov=11,
November=11,
Dec=12,
December=12,
)
MONTHS_MAP = {
"Jan": 1,
"January": 1,
"Feb": 2,
"February": 2,
"Mar": 3,
"March": 3,
"Apr": 4,
"April": 4,
"May": 5,
"Jun": 6,
"June": 6,
"Jul": 7,
"July": 7,
"Aug": 8,
"August": 8,
"Sep": 9,
"September": 9,
"Oct": 10,
"October": 10,
"Nov": 11,
"November": 11,
"Dec": 12,
"December": 12,
}
DAYS_PAT = r"(Mon|Tue|Wed|Thu|Fri|Sat|Sun)"
MONTHS_PAT = r"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)"
ALL_MONTHS_PAT = r"(%s)" % "|".join(MONTHS_MAP)
Expand Down Expand Up @@ -272,7 +274,6 @@ def date_convert(


def strf_date_convert(x, _, type):

is_date = any("%" + x in type for x in "aAwdbBmyYjUW")
is_time = any("%" + x in type for x in "HIpMSfz")

Expand Down Expand Up @@ -341,8 +342,7 @@ class RepeatedNameError(ValueError):


# note: {} are handled separately
# note: I don't use r'' here because Sublime Text 2 syntax highlight has a fit
REGEX_SAFETY = re.compile(r"([?\\\\.[\]()*+\^$!\|])")
REGEX_SAFETY = re.compile(r"([?\\.[\]()*+^$!|])")

# allowed field types
ALLOWED_TYPES = set(list("nbox%fFegwWdDsSl") + ["t" + c for c in "ieahgcts"])
Expand Down Expand Up @@ -398,15 +398,15 @@ def extract_format(format, extra_types):
return locals()


PARSE_RE = re.compile(r"""({{|}}|{\w*(?:(?:\.\w+)|(?:\[[^\]]+\]))*(?::[^}]+)?})""")
PARSE_RE = re.compile(r"({{|}}|{\w*(?:\.\w+|\[[^]]+])*(?::[^}]+)?})")


class Parser(object):
"""Encapsulate a format string that may be used to parse other strings."""

def __init__(self, format, extra_types=None, case_sensitive=False):
# a mapping of a name as in {hello.world} to a regex-group compatible
# name, like hello__world Its used to prevent the transformation of
# name, like hello__world. It's used to prevent the transformation of
# name-to-group and group to name to fail subtly, such as in:
# hello_.world-> hello___world->hello._world
self._group_to_name_map = {}
Expand Down Expand Up @@ -552,7 +552,7 @@ def _expand_named_fields(self, named_fields):
k = basename

if subkeys:
for subkey in re.findall(r"\[[^\]]+\]", subkeys):
for subkey in re.findall(r"\[[^]]+]", subkeys):
d = d.setdefault(k, {})
k = subkey[1:-1]

Expand Down Expand Up @@ -585,7 +585,7 @@ def evaluate_result(self, m):
named_fields[korig] = value

# now figure the match spans
spans = dict((n, m.span(name_map[n])) for n in named_fields)
spans = {n: m.span(name_map[n]) for n in named_fields}
spans.update((i, m.span(n + 1)) for i, n in enumerate(self._fixed_fields))

# and that's our result
Expand Down
103 changes: 103 additions & 0 deletions tests/test_bugs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import pickle
from datetime import datetime

import parse


def test_tz_compare_to_None():
utc = parse.FixedTzOffset(0, "UTC")
assert utc is not None
assert utc != "spam"


def test_named_date_issue7():
r = parse.parse("on {date:ti}", "on 2012-09-17")
assert r["date"] == datetime(2012, 9, 17, 0, 0, 0)

# fix introduced regressions
r = parse.parse("a {:ti} b", "a 1997-07-16T19:20 b")
assert r[0] == datetime(1997, 7, 16, 19, 20, 0)
r = parse.parse("a {:ti} b", "a 1997-07-16T19:20Z b")
utc = parse.FixedTzOffset(0, "UTC")
assert r[0] == datetime(1997, 7, 16, 19, 20, tzinfo=utc)
r = parse.parse("a {date:ti} b", "a 1997-07-16T19:20Z b")
assert r["date"] == datetime(1997, 7, 16, 19, 20, tzinfo=utc)


def test_dotted_type_conversion_pull_8():
# test pull request 8 which fixes type conversion related to dotted
# names being applied correctly
r = parse.parse("{a.b:d}", "1")
assert r["a.b"] == 1
r = parse.parse("{a_b:w} {a.b:d}", "1 2")
assert r["a_b"] == "1"
assert r["a.b"] == 2


def test_pm_overflow_issue16():
r = parse.parse("Meet at {:tg}", "Meet at 1/2/2011 12:45 PM")
assert r[0] == datetime(2011, 2, 1, 12, 45)


def test_pm_handling_issue57():
r = parse.parse("Meet at {:tg}", "Meet at 1/2/2011 12:15 PM")
assert r[0] == datetime(2011, 2, 1, 12, 15)
r = parse.parse("Meet at {:tg}", "Meet at 1/2/2011 12:15 AM")
assert r[0] == datetime(2011, 2, 1, 0, 15)


def test_user_type_with_group_count_issue60():
@parse.with_pattern(r"((\w+))", regex_group_count=2)
def parse_word_and_covert_to_uppercase(text):
return text.strip().upper()

@parse.with_pattern(r"\d+")
def parse_number(text):
return int(text)

# -- CASE: Use named (OK)
type_map = {"Name": parse_word_and_covert_to_uppercase, "Number": parse_number}
r = parse.parse(
"Hello {name:Name} {number:Number}", "Hello Alice 42", extra_types=type_map
)
assert r.named == {"name": "ALICE", "number": 42}

# -- CASE: Use unnamed/fixed (problematic)
r = parse.parse("Hello {:Name} {:Number}", "Hello Alice 42", extra_types=type_map)
assert r[0] == "ALICE"
assert r[1] == 42


def test_unmatched_brace_doesnt_match():
r = parse.parse("{who.txt", "hello")
assert r is None


def test_pickling_bug_110():
p = parse.compile("{a:d}")
# prior to the fix, this would raise an AttributeError
pickle.dumps(p)


def test_unused_centered_alignment_bug():
r = parse.parse("{:^2S}", "foo")
assert r[0] == "foo"
r = parse.search("{:^2S}", "foo")
assert r[0] == "foo"

# specifically test for the case in issue #118 as well
r = parse.parse("Column {:d}:{:^}", "Column 1: Timestep")
assert r[0] == 1
assert r[1] == "Timestep"


def test_unused_left_alignment_bug():
r = parse.parse("{:<2S}", "foo")
assert r[0] == "foo"
r = parse.search("{:<2S}", "foo")
assert r[0] == "foo"


def test_match_trailing_newline():
r = parse.parse("{}", "test\n")
assert r[0] == "test\n"
26 changes: 26 additions & 0 deletions tests/test_findall.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import parse


def test_findall():
s = "".join(
r.fixed[0] for r in parse.findall(">{}<", "<p>some <b>bold</b> text</p>")
)
assert s == "some bold text"


def test_no_evaluate_result():
s = "".join(
m.evaluate_result().fixed[0]
for m in parse.findall(
">{}<", "<p>some <b>bold</b> text</p>", evaluate_result=False
)
)
assert s == "some bold text"


def test_case_sensitivity():
l = [r.fixed[0] for r in parse.findall("x({})x", "X(hi)X")]
assert l == ["hi"]

l = [r.fixed[0] for r in parse.findall("x({})x", "X(hi)X", case_sensitive=True)]
assert l == []
Loading

0 comments on commit 5687cf0

Please sign in to comment.