diff --git a/CHANGES b/CHANGES index bff09c76..0e187687 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,9 @@ Version 3.2.0 will also discontinue support for Python versions 3.6 and 3.7. Version 3.1.3 - in development ------------------------------ +- Fixed issue where PEP8 compatibility names for `ParserElement` staticmethods were + not themselves defined as staticmethods. When called using a `ParserElement` instance, + this resulted in a `TypeError` exception. Reported by eylenburg (#548). Version 3.1.2 - March, 2024 diff --git a/pyparsing/__init__.py b/pyparsing/__init__.py index be806116..87360fad 100644 --- a/pyparsing/__init__.py +++ b/pyparsing/__init__.py @@ -121,7 +121,7 @@ def __repr__(self): __version_info__ = version_info(3, 1, 3, "final", 1) -__version_time__ = "07 Mar 2024 13:11 UTC" +__version_time__ = "09 Mar 2024 15:40 UTC" __version__ = __version_info__.__version__ __versionTime__ = __version_time__ __author__ = "Paul McGuire " @@ -143,7 +143,7 @@ def __repr__(self): _builtin_exprs as common_builtin_exprs, ) -# define backward compat synonyms +# Compatibility synonyms if "pyparsing_unicode" not in globals(): pyparsing_unicode = unicode # type: ignore[misc] if "pyparsing_common" not in globals(): diff --git a/pyparsing/actions.py b/pyparsing/actions.py index ce51b395..1d2dce99 100644 --- a/pyparsing/actions.py +++ b/pyparsing/actions.py @@ -196,7 +196,7 @@ def with_class(classname, namespace=""): return with_attribute(**{classattr: classname}) -# pre-PEP8 compatibility symbols +# Compatibility synonyms # fmt: off replaceWith = replaced_by_pep8("replaceWith", replace_with) removeQuotes = replaced_by_pep8("removeQuotes", remove_quotes) diff --git a/pyparsing/common.py b/pyparsing/common.py index 74faa460..bb43d5d6 100644 --- a/pyparsing/common.py +++ b/pyparsing/common.py @@ -151,12 +151,12 @@ class pyparsing_common: [UUID('12345678-1234-5678-1234-567812345678')] """ - convert_to_integer = token_map(int) + convert_to_integer = staticmethod(token_map(int)) """ Parse action for converting parsed integers to Python int """ - convert_to_float = token_map(float) + convert_to_float = staticmethod(token_map(float)) """ Parse action for converting parsed numbers to Python float """ @@ -418,20 +418,15 @@ def strip_html_tags(s: str, l: int, tokens: ParseResults): # fmt: on # pre-PEP8 compatibility names - convertToInteger = convert_to_integer - """Deprecated - use :class:`convert_to_integer`""" - convertToFloat = convert_to_float - """Deprecated - use :class:`convert_to_float`""" - convertToDate = convert_to_date - """Deprecated - use :class:`convert_to_date`""" - convertToDatetime = convert_to_datetime - """Deprecated - use :class:`convert_to_datetime`""" - stripHTMLTags = strip_html_tags - """Deprecated - use :class:`strip_html_tags`""" - upcaseTokens = upcase_tokens - """Deprecated - use :class:`upcase_tokens`""" - downcaseTokens = downcase_tokens - """Deprecated - use :class:`downcase_tokens`""" + # fmt: off + convertToInteger = staticmethod(replaced_by_pep8("convertToInteger", convert_to_integer)) + convertToFloat = staticmethod(replaced_by_pep8("convertToFloat", convert_to_float)) + convertToDate = staticmethod(replaced_by_pep8("convertToDate", convert_to_date)) + convertToDatetime = staticmethod(replaced_by_pep8("convertToDatetime", convert_to_datetime)) + stripHTMLTags = staticmethod(replaced_by_pep8("stripHTMLTags", strip_html_tags)) + upcaseTokens = staticmethod(replaced_by_pep8("upcaseTokens", upcase_tokens)) + downcaseTokens = staticmethod(replaced_by_pep8("downcaseTokens", downcase_tokens)) + # fmt: on _builtin_exprs = [ diff --git a/pyparsing/core.py b/pyparsing/core.py index b19d1221..09d0c09e 100644 --- a/pyparsing/core.py +++ b/pyparsing/core.py @@ -2264,10 +2264,15 @@ def create_diagram( # Compatibility synonyms # fmt: off - inlineLiteralsUsing = replaced_by_pep8("inlineLiteralsUsing", inline_literals_using) - setDefaultWhitespaceChars = replaced_by_pep8( + inlineLiteralsUsing = staticmethod(replaced_by_pep8("inlineLiteralsUsing", inline_literals_using)) + setDefaultWhitespaceChars = staticmethod(replaced_by_pep8( "setDefaultWhitespaceChars", set_default_whitespace_chars - ) + )) + disableMemoization = staticmethod(replaced_by_pep8("disableMemoization", disable_memoization)) + enableLeftRecursion = staticmethod(replaced_by_pep8("enableLeftRecursion", enable_left_recursion)) + enablePackrat = staticmethod(replaced_by_pep8("enablePackrat", enable_packrat)) + resetCache = staticmethod(replaced_by_pep8("resetCache", reset_cache)) + setResultsName = replaced_by_pep8("setResultsName", set_results_name) setBreak = replaced_by_pep8("setBreak", set_break) setParseAction = replaced_by_pep8("setParseAction", set_parse_action) @@ -2275,8 +2280,6 @@ def create_diagram( addCondition = replaced_by_pep8("addCondition", add_condition) setFailAction = replaced_by_pep8("setFailAction", set_fail_action) tryParse = replaced_by_pep8("tryParse", try_parse) - enableLeftRecursion = replaced_by_pep8("enableLeftRecursion", enable_left_recursion) - enablePackrat = replaced_by_pep8("enablePackrat", enable_packrat) parseString = replaced_by_pep8("parseString", parse_string) scanString = replaced_by_pep8("scanString", scan_string) transformString = replaced_by_pep8("transformString", transform_string) @@ -2290,8 +2293,7 @@ def create_diagram( setName = replaced_by_pep8("setName", set_name) parseFile = replaced_by_pep8("parseFile", parse_file) runTests = replaced_by_pep8("runTests", run_tests) - canParseNext = can_parse_next - resetCache = reset_cache + canParseNext = replaced_by_pep8("canParseNext", can_parse_next) defaultName = default_name # fmt: on @@ -2556,7 +2558,10 @@ def set_default_keyword_chars(chars) -> None: """ Keyword.DEFAULT_KEYWORD_CHARS = chars - setDefaultKeywordChars = set_default_keyword_chars + # Compatibility synonyms + setDefaultKeywordChars = staticmethod( + replaced_by_pep8("setDefaultKeywordChars", set_default_keyword_chars) + ) class CaselessLiteral(Literal): @@ -6070,7 +6075,7 @@ def autoname_elements() -> None: v for v in vars().values() if isinstance(v, ParserElement) ] -# backward compatibility names +# Compatibility synonyms # fmt: off sglQuotedString = sgl_quoted_string dblQuotedString = dbl_quoted_string diff --git a/pyparsing/exceptions.py b/pyparsing/exceptions.py index e259c908..8db34f19 100644 --- a/pyparsing/exceptions.py +++ b/pyparsing/exceptions.py @@ -245,6 +245,7 @@ def explain(self, depth=16) -> str: """ return self.explain_exception(self, depth) + # Compatibility synonyms # fmt: off markInputline = replaced_by_pep8("markInputline", mark_input_line) # fmt: on diff --git a/pyparsing/helpers.py b/pyparsing/helpers.py index dcfdb8fe..6a3cc73b 100644 --- a/pyparsing/helpers.py +++ b/pyparsing/helpers.py @@ -1049,7 +1049,7 @@ def delimited_list( ) -# pre-PEP8 compatible names +# Compatibility synonyms # fmt: off opAssoc = OpAssoc anyOpenTag = any_open_tag diff --git a/pyparsing/results.py b/pyparsing/results.py index 3e5fe208..f9864ae0 100644 --- a/pyparsing/results.py +++ b/pyparsing/results.py @@ -10,6 +10,9 @@ import pprint from typing import Tuple, Any, Dict, Set, List +from .util import replaced_by_pep8 + + str_type: Tuple[type, ...] = (str, bytes) _generator_type = type((_ for _ in ())) @@ -785,12 +788,10 @@ def is_iterable(obj): ret = cls([ret], name=name) return ret - asList = as_list - """Deprecated - use :class:`as_list`""" - asDict = as_dict - """Deprecated - use :class:`as_dict`""" - getName = get_name - """Deprecated - use :class:`get_name`""" + # Compatibility synonyms + asList = replaced_by_pep8("asList", as_list) + asDict = replaced_by_pep8("asDict", as_dict) + getName = replaced_by_pep8("getName", get_name) MutableMapping.register(ParseResults) diff --git a/pyparsing/util.py b/pyparsing/util.py index 4ae018a9..1da0e8c7 100644 --- a/pyparsing/util.py +++ b/pyparsing/util.py @@ -246,7 +246,7 @@ def replaced_by_pep8(compat_name: str, fn: C) -> C: # (Presence of 'self' arg in signature is used by explain_exception() methods, so we take # some extra steps to add it if present in decorated function.) - if "self" == list(inspect.signature(fn).parameters)[0]: + if "self" == next(iter(inspect.signature(fn).parameters), ""): @wraps(fn) def _inner(self, *args, **kwargs): diff --git a/tests/test_unit.py b/tests/test_unit.py index 71e7e45d..8e1b42c4 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -10206,6 +10206,36 @@ def test_exception_messages(self, tests=test_exception_messages_tests): with self.assertRaisesParseException(expected_msg=expected_msg): expr.parse_string(input_str) + def test_pep8_synonyms(self): + """ + Test that staticmethods wrapped by replaced_by_pep8 wrapper are properly + callable as staticmethods. + """ + + def run_subtest(fn_name, expr=None, args=""): + bool_expr = pp.one_of("true false", as_keyword=True) + if expr is None: + expr = "bool_expr" + + # try calling a ParserElement staticmethod via a ParserElement instance + with self.subTest(fn_name=fn_name): + exec(f"{expr}.{fn_name}({args})", globals(), locals()) + + # access staticmethod synonyms using a ParserElement + parser_element_staticmethod_names = """ + enablePackrat disableMemoization enableLeftRecursion resetCache + """.split() + + if not (pp.ParserElement._packratEnabled or pp.ParserElement._left_recursion_enabled): + for name in parser_element_staticmethod_names: + run_subtest(name) + pp.ParserElement.disable_memoization() + + run_subtest("setDefaultWhitespaceChars", args="' '") + run_subtest("inlineLiteralsUsing", args="pp.Suppress") + + run_subtest("setDefaultKeywordChars", expr="pp.Keyword('START')", args="'abcde'") + class Test03_EnablePackratParsing(TestCase): def runTest(self):