From 8e98706892fc86fd687c691d7e8c6f20d63508a9 Mon Sep 17 00:00:00 2001 From: Isaac Muse Date: Fri, 22 Mar 2019 22:20:37 -0600 Subject: [PATCH] Rework wrapper parameters to be more flexible (support regex timeout) (#113) This changes the wrapper function inputs to accept *args and **kwargs instead of explicitly specifying parameters. This allows the API of the underlying library to change without affecting our wrapper. Fixes #112 --- backrefs/bre.py | 95 ++++++++++++++----------- backrefs/bregex.py | 126 ++++++++++++++------------------- docs/src/markdown/changelog.md | 2 + tests/test_bregex.py | 23 ++++++ 4 files changed, 132 insertions(+), 114 deletions(-) diff --git a/backrefs/bre.py b/backrefs/bre.py index e5bcd52..82264bd 100644 --- a/backrefs/bre.py +++ b/backrefs/bre.py @@ -26,7 +26,6 @@ Copyright (c) 2011 - 2019 Isaac Muse """ from __future__ import unicode_literals -import sys as _sys import re as _re import copyreg as _copyreg from functools import lru_cache as _lru_cache @@ -256,56 +255,56 @@ def compile(self, repl, flags=0): # noqa A001 return compile_replace(self._pattern, repl, flags) - def search(self, string, pos=0, endpos=_sys.maxsize): + def search(self, string, *args, **kwargs): """Apply `search`.""" - return self._pattern.search(string, pos, endpos) + return self._pattern.search(string, *args, **kwargs) - def match(self, string, pos=0, endpos=_sys.maxsize): + def match(self, string, *args, **kwargs): """Apply `match`.""" - return self._pattern.match(string, pos, endpos) + return self._pattern.match(string, *args, **kwargs) if _util.PY34: - def fullmatch(self, string, pos=0, endpos=_sys.maxsize): + def fullmatch(self, string, *args, **kwargs): """Apply `fullmatch`.""" - return self._pattern.fullmatch(string, pos, endpos) + return self._pattern.fullmatch(string, *args, **kwargs) - def split(self, string, maxsplit=0): + def split(self, string, *args, **kwargs): """Apply `split`.""" - return self._pattern.split(string, maxsplit) + return self._pattern.split(string, *args, **kwargs) - def findall(self, string, pos=0, endpos=_sys.maxsize): + def findall(self, string, *args, **kwargs): """Apply `findall`.""" - return self._pattern.findall(string, pos, endpos) + return self._pattern.findall(string, *args, **kwargs) - def finditer(self, string, pos=0, endpos=_sys.maxsize): + def finditer(self, string, *args, **kwargs): """Apply `finditer`.""" - return self._pattern.finditer(string, pos, endpos) + return self._pattern.finditer(string, *args, **kwargs) - def sub(self, repl, string, count=0): + def sub(self, repl, string, *args, **kwargs): """Apply `sub`.""" - return self._pattern.sub(self._auto_compile(repl), string, count) + return self._pattern.sub(self._auto_compile(repl), string, *args, **kwargs) - def subf(self, repl, string, count=0): # noqa A002 + def subf(self, repl, string, *args, **kwargs): # noqa A002 """Apply `sub` with format style replace.""" - return self._pattern.sub(self._auto_compile(repl, True), string, count) + return self._pattern.sub(self._auto_compile(repl, True), string, *args, **kwargs) - def subn(self, repl, string, count=0): + def subn(self, repl, string, *args, **kwargs): """Apply `subn` with format style replace.""" - return self._pattern.subn(self._auto_compile(repl), string, count) + return self._pattern.subn(self._auto_compile(repl), string, *args, **kwargs) - def subfn(self, repl, string, count=0): # noqa A002 + def subfn(self, repl, string, *args, **kwargs): # noqa A002 """Apply `subn` after applying backrefs.""" - return self._pattern.subn(self._auto_compile(repl, True), string, count) + return self._pattern.subn(self._auto_compile(repl, True), string, *args, **kwargs) def compile(pattern, flags=0, auto_compile=None): # noqa A001 @@ -374,46 +373,53 @@ def expandf(m, format): # noqa A002 return _apply_replace_backrefs(m, format, flags=FORMAT) -def search(pattern, string, flags=0): +def search(pattern, string, *args, **kwargs): """Apply `search` after applying backrefs.""" - return _re.search(_apply_search_backrefs(pattern, flags), string, flags) + flags = args[2] if len(args) > 2 else kwargs.get('flags', 0) + return _re.search(_apply_search_backrefs(pattern, flags), string, *args, **kwargs) -def match(pattern, string, flags=0): +def match(pattern, string, *args, **kwargs): """Apply `match` after applying backrefs.""" - return _re.match(_apply_search_backrefs(pattern, flags), string, flags) + flags = args[2] if len(args) > 2 else kwargs.get('flags', 0) + return _re.match(_apply_search_backrefs(pattern, flags), string, *args, **kwargs) if _util.PY34: - def fullmatch(pattern, string, flags=0): + def fullmatch(pattern, string, *args, **kwargs): """Apply `fullmatch` after applying backrefs.""" - return _re.fullmatch(_apply_search_backrefs(pattern, flags), string, flags) + flags = args[2] if len(args) > 2 else kwargs.get('flags', 0) + return _re.fullmatch(_apply_search_backrefs(pattern, flags), string, *args, **kwargs) -def split(pattern, string, maxsplit=0, flags=0): +def split(pattern, string, *args, **kwargs): """Apply `split` after applying backrefs.""" - return _re.split(_apply_search_backrefs(pattern, flags), string, maxsplit, flags) + flags = args[3] if len(args) > 3 else kwargs.get('flags', 0) + return _re.split(_apply_search_backrefs(pattern, flags), string, *args, **kwargs) -def findall(pattern, string, flags=0): +def findall(pattern, string, *args, **kwargs): """Apply `findall` after applying backrefs.""" - return _re.findall(_apply_search_backrefs(pattern, flags), string, flags) + flags = args[2] if len(args) > 2 else kwargs.get('flags', 0) + return _re.findall(_apply_search_backrefs(pattern, flags), string, *args, **kwargs) -def finditer(pattern, string, flags=0): +def finditer(pattern, string, *args, **kwargs): """Apply `finditer` after applying backrefs.""" - return _re.finditer(_apply_search_backrefs(pattern, flags), string, flags) + flags = args[2] if len(args) > 2 else kwargs.get('flags', 0) + return _re.finditer(_apply_search_backrefs(pattern, flags), string, *args, **kwargs) -def sub(pattern, repl, string, count=0, flags=0): +def sub(pattern, repl, string, *args, **kwargs): """Apply `sub` after applying backrefs.""" + flags = args[4] if len(args) > 4 else kwargs.get('flags', 0) is_replace = _is_replace(repl) is_string = isinstance(repl, (str, bytes)) if is_replace and repl.use_format: @@ -421,13 +427,14 @@ def sub(pattern, repl, string, count=0, flags=0): pattern = compile_search(pattern, flags) return _re.sub( - pattern, (compile_replace(pattern, repl) if is_replace or is_string else repl), string, count, flags + pattern, (compile_replace(pattern, repl) if is_replace or is_string else repl), string, *args, **kwargs ) -def subf(pattern, format, string, count=0, flags=0): # noqa A002 +def subf(pattern, format, string, *args, **kwargs): # noqa A002 """Apply `sub` with format style replace.""" + flags = args[4] if len(args) > 4 else kwargs.get('flags', 0) is_replace = _is_replace(format) is_string = isinstance(format, (str, bytes)) if is_replace and not format.use_format: @@ -436,14 +443,15 @@ def subf(pattern, format, string, count=0, flags=0): # noqa A002 pattern = compile_search(pattern, flags) rflags = FORMAT if is_string else 0 return _re.sub( - pattern, (compile_replace(pattern, format, flags=rflags) if is_replace or is_string else format), - string, count, flags + pattern, (compile_replace(pattern, format, flags=rflags) if is_replace or is_string else format), string, + *args, **kwargs ) -def subn(pattern, repl, string, count=0, flags=0): +def subn(pattern, repl, string, *args, **kwargs): """Apply `subn` with format style replace.""" + flags = args[4] if len(args) > 4 else kwargs.get('flags', 0) is_replace = _is_replace(repl) is_string = isinstance(repl, (str, bytes)) if is_replace and repl.use_format: @@ -451,13 +459,14 @@ def subn(pattern, repl, string, count=0, flags=0): pattern = compile_search(pattern, flags) return _re.subn( - pattern, (compile_replace(pattern, repl) if is_replace or is_string else repl), string, count, flags + pattern, (compile_replace(pattern, repl) if is_replace or is_string else repl), string, *args, **kwargs ) -def subfn(pattern, format, string, count=0, flags=0): # noqa A002 +def subfn(pattern, format, string, *args, **kwargs): # noqa A002 """Apply `subn` after applying backrefs.""" + flags = args[4] if len(args) > 4 else kwargs.get('flags', 0) is_replace = _is_replace(format) is_string = isinstance(format, (str, bytes)) if is_replace and not format.use_format: @@ -466,8 +475,8 @@ def subfn(pattern, format, string, count=0, flags=0): # noqa A002 pattern = compile_search(pattern, flags) rflags = FORMAT if is_string else 0 return _re.subn( - pattern, (compile_replace(pattern, format, flags=rflags) if is_replace or is_string else format), - string, count, flags + pattern, (compile_replace(pattern, format, flags=rflags) if is_replace or is_string else format), string, + *args, **kwargs ) diff --git a/backrefs/bregex.py b/backrefs/bregex.py index c26b9cb..ba1a5f6 100644 --- a/backrefs/bregex.py +++ b/backrefs/bregex.py @@ -314,60 +314,60 @@ def compile(self, repl, flags=0): # noqa A001 return compile_replace(self._pattern, repl, flags) - def search(self, string, pos=None, endpos=None, concurrent=None, partial=False): + def search(self, string, *args, **kwargs): """Apply `search`.""" - return self._pattern.search(string, pos, endpos, concurrent, partial) + return self._pattern.search(string, *args, **kwargs) - def match(self, string, pos=None, endpos=None, concurrent=None, partial=False): + def match(self, string, *args, **kwargs): """Apply `match`.""" - return self._pattern.match(string, pos, endpos, concurrent, partial) + return self._pattern.match(string, *args, **kwargs) - def fullmatch(self, string, pos=None, endpos=None, concurrent=None, partial=False): + def fullmatch(self, string, *args, **kwargs): """Apply `fullmatch`.""" - return self._pattern.fullmatch(string, pos, endpos, concurrent, partial) + return self._pattern.fullmatch(string, *args, **kwargs) - def split(self, string, maxsplit=0, concurrent=None): + def split(self, string, *args, **kwargs): """Apply `split`.""" - return self._pattern.split(string, maxsplit, concurrent) + return self._pattern.split(string, *args, **kwargs) - def splititer(self, string, maxsplit=0, concurrent=None): + def splititer(self, string, *args, **kwargs): """Apply `splititer`.""" - return self._pattern.splititer(string, maxsplit, concurrent) + return self._pattern.splititer(string, *args, **kwargs) - def findall(self, string, pos=None, endpos=None, overlapped=False, concurrent=None): + def findall(self, string, *args, **kwargs): """Apply `findall`.""" - return self._pattern.findall(string, pos, endpos, overlapped, concurrent) + return self._pattern.findall(string, *args, **kwargs) - def finditer(self, string, pos=None, endpos=None, overlapped=False, concurrent=None, partial=False): + def finditer(self, string, *args, **kwargs): """Apply `finditer`.""" - return self._pattern.finditer(string, pos, endpos, overlapped, concurrent, partial) + return self._pattern.finditer(string, *args, **kwargs) - def sub(self, repl, string, count=0, pos=None, endpos=None, concurrent=None): + def sub(self, repl, string, *args, **kwargs): """Apply `sub`.""" - return self._pattern.sub(self._auto_compile(repl), string, count, pos, endpos, concurrent) + return self._pattern.sub(self._auto_compile(repl), string, *args, **kwargs) - def subf(self, repl, string, count=0, pos=None, endpos=None, concurrent=None): # noqa A002 + def subf(self, repl, string, *args, **kwargs): # noqa A002 """Apply `sub` with format style replace.""" - return self._pattern.subf(self._auto_compile(repl, True), string, count, pos, endpos, concurrent) + return self._pattern.subf(self._auto_compile(repl, True), string, *args, **kwargs) - def subn(self, repl, string, count=0, pos=None, endpos=None, concurrent=None): + def subn(self, repl, string, *args, **kwargs): """Apply `subn` with format style replace.""" - return self._pattern.subn(self._auto_compile(repl), string, count, pos, endpos, concurrent) + return self._pattern.subn(self._auto_compile(repl), string, *args, **kwargs) - def subfn(self, repl, string, count=0, pos=None, endpos=None, concurrent=None): # noqa A002 + def subfn(self, repl, string, *args, **kwargs): # noqa A002 """Apply `subn` after applying backrefs.""" - return self._pattern.subfn(self._auto_compile(repl, True), string, count, pos, endpos, concurrent) + return self._pattern.subfn(self._auto_compile(repl, True), string, *args, **kwargs) def purge(): @@ -391,36 +391,31 @@ def expandf(m, format): # noqa A002 return _apply_replace_backrefs(m, format, flags=FORMAT) -def match(pattern, string, flags=0, pos=None, endpos=None, partial=False, concurrent=None, **kwargs): +def match(pattern, string, *args, **kwargs): """Wrapper for `match`.""" - return _regex.match( - _apply_search_backrefs(pattern, flags), string, - flags, pos, endpos, partial, concurrent, **kwargs - ) + flags = args[2] if len(args) > 2 else kwargs.get('flags', 0) + return _regex.match(_apply_search_backrefs(pattern, flags), string, *args, **kwargs) -def fullmatch(pattern, string, flags=0, pos=None, endpos=None, partial=False, concurrent=None, **kwargs): +def fullmatch(pattern, string, *args, **kwargs): """Wrapper for `fullmatch`.""" - return _regex.fullmatch( - _apply_search_backrefs(pattern, flags), string, - flags, pos, endpos, partial, concurrent, **kwargs - ) + flags = args[2] if len(args) > 2 else kwargs.get('flags', 0) + return _regex.fullmatch(_apply_search_backrefs(pattern, flags), string, *args, **kwargs) -def search(pattern, string, flags=0, pos=None, endpos=None, partial=False, concurrent=None, **kwargs): +def search(pattern, string, *args, **kwargs): """Wrapper for `search`.""" - return _regex.search( - _apply_search_backrefs(pattern, flags), string, - flags, pos, endpos, partial, concurrent, **kwargs - ) + flags = args[2] if len(args) > 2 else kwargs.get('flags', 0) + return _regex.search(_apply_search_backrefs(pattern, flags), string, *args, **kwargs) -def sub(pattern, repl, string, count=0, flags=0, pos=None, endpos=None, concurrent=None, **kwargs): +def sub(pattern, repl, string, *args, **kwargs): """Wrapper for `sub`.""" + flags = args[4] if len(args) > 4 else kwargs.get('flags', 0) is_replace = _is_replace(repl) is_string = isinstance(repl, (str, bytes)) if is_replace and repl.use_format: @@ -429,13 +424,14 @@ def sub(pattern, repl, string, count=0, flags=0, pos=None, endpos=None, concurre pattern = compile_search(pattern, flags) return _regex.sub( pattern, (compile_replace(pattern, repl) if is_replace or is_string else repl), string, - count, flags, pos, endpos, concurrent, **kwargs + *args, **kwargs ) -def subf(pattern, format, string, count=0, flags=0, pos=None, endpos=None, concurrent=None, **kwargs): # noqa A002 +def subf(pattern, format, string, *args, **kwargs): # noqa A002 """Wrapper for `subf`.""" + flags = args[4] if len(args) > 4 else kwargs.get('flags', 0) is_replace = _is_replace(format) is_string = isinstance(format, (str, bytes)) if is_replace and not format.use_format: @@ -445,13 +441,14 @@ def subf(pattern, format, string, count=0, flags=0, pos=None, endpos=None, concu rflags = FORMAT if is_string else 0 return _regex.sub( pattern, (compile_replace(pattern, format, flags=rflags) if is_replace or is_string else format), string, - count, flags, pos, endpos, concurrent, **kwargs + *args, **kwargs ) -def subn(pattern, repl, string, count=0, flags=0, pos=None, endpos=None, concurrent=None, **kwargs): +def subn(pattern, repl, string, *args, **kwargs): """Wrapper for `subn`.""" + flags = args[4] if len(args) > 4 else kwargs.get('flags', 0) is_replace = _is_replace(repl) is_string = isinstance(repl, (str, bytes)) if is_replace and repl.use_format: @@ -460,13 +457,14 @@ def subn(pattern, repl, string, count=0, flags=0, pos=None, endpos=None, concurr pattern = compile_search(pattern, flags) return _regex.subn( pattern, (compile_replace(pattern, repl) if is_replace or is_string else repl), string, - count, flags, pos, endpos, concurrent, **kwargs + *args, **kwargs ) -def subfn(pattern, format, string, count=0, flags=0, pos=None, endpos=None, concurrent=None, **kwargs): # noqa A002 +def subfn(pattern, format, string, *args, **kwargs): # noqa A002 """Wrapper for `subfn`.""" + flags = args[4] if len(args) > 4 else kwargs.get('flags', 0) is_replace = _is_replace(format) is_string = isinstance(format, (str, bytes)) if is_replace and not format.use_format: @@ -476,50 +474,36 @@ def subfn(pattern, format, string, count=0, flags=0, pos=None, endpos=None, conc rflags = FORMAT if is_string else 0 return _regex.subn( pattern, (compile_replace(pattern, format, flags=rflags) if is_replace or is_string else format), string, - count, flags, pos, endpos, concurrent, **kwargs + *args, **kwargs ) -def split(pattern, string, maxsplit=0, flags=0, concurrent=None, **kwargs): +def split(pattern, string, *args, **kwargs): """Wrapper for `split`.""" - return _regex.split( - _apply_search_backrefs(pattern, flags), string, - maxsplit, flags, concurrent, **kwargs - ) + flags = args[3] if len(args) > 3 else kwargs.get('flags', 0) + return _regex.split(_apply_search_backrefs(pattern, flags), string, *args, **kwargs) -def splititer(pattern, string, maxsplit=0, flags=0, concurrent=None, **kwargs): +def splititer(pattern, string, *args, **kwargs): """Wrapper for `splititer`.""" - return _regex.splititer( - _apply_search_backrefs(pattern, flags), string, - maxsplit, flags, concurrent, **kwargs - ) + flags = args[3] if len(args) > 3 else kwargs.get('flags', 0) + return _regex.splititer(_apply_search_backrefs(pattern, flags), string, *args, **kwargs) -def findall( - pattern, string, flags=0, pos=None, endpos=None, overlapped=False, - concurrent=None, **kwargs -): +def findall(pattern, string, *args, **kwargs): """Wrapper for `findall`.""" - return _regex.findall( - _apply_search_backrefs(pattern, flags), string, - flags, pos, endpos, overlapped, concurrent, **kwargs - ) + flags = args[2] if len(args) > 2 else kwargs.get('flags', 0) + return _regex.findall(_apply_search_backrefs(pattern, flags), string, *args, **kwargs) -def finditer( - pattern, string, flags=0, pos=None, endpos=None, overlapped=False, - partial=False, concurrent=None, **kwargs -): +def finditer(pattern, string, *args, **kwargs): """Wrapper for `finditer`.""" - return _regex.finditer( - _apply_search_backrefs(pattern, flags), string, - flags, pos, endpos, overlapped, partial, concurrent, **kwargs - ) + flags = args[2] if len(args) > 2 else kwargs.get('flags', 0) + return _regex.finditer(_apply_search_backrefs(pattern, flags), string, *args, **kwargs) def _pickle(p): diff --git a/docs/src/markdown/changelog.md b/docs/src/markdown/changelog.md index 9e7bc43..33547f8 100644 --- a/docs/src/markdown/changelog.md +++ b/docs/src/markdown/changelog.md @@ -4,6 +4,8 @@ - **NEW**: Add official support for Python 3.8. - **NEW**: Vendor the `Pep562` library instead of requiring as a dependency. +- **NEW**: Input parameters accept `*args` and `**kwargs` instead of specify every parameter in order to allow Backrefs +to work even when the Re or Regex API changes. Change was made to support new Regex `timeout` parameter. ## 4.0.2 diff --git a/tests/test_bregex.py b/tests/test_bregex.py index a66f77b..7c18efd 100644 --- a/tests/test_bregex.py +++ b/tests/test_bregex.py @@ -8,6 +8,7 @@ import pytest import random import copy +import time try: import _regex_core except ImportError: @@ -1914,6 +1915,28 @@ def test_octal_fail(self): assert "octal escape value outside of range 0-0o377!" in str(excinfo.value) + def test_timeout(self): + """Test timeout.""" + + def fast_replace(m): + """Fast replace.""" + return 'X' + + def slow_replace(m): + start = time.time() + while True: + elapsed = time.time() - start + if elapsed >= 0.5: + break + return 'X' + + self.assertEqual(regex.sub(r'[a-z]', fast_replace, 'abcdef', timeout=2), 'XXXXXX') + + with pytest.raises(TimeoutError) as excinfo: + regex.sub(r'[a-z]', slow_replace, 'abcdef', timeout=2) + + assert "regex timed out" in str(excinfo.value) + class TestConvenienceFunctions(unittest.TestCase): """Test convenience functions."""