diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9a06305..83dc4da 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,10 +17,8 @@ jobs: max-parallel: 4 matrix: platform: [ubuntu-latest, windows-latest] - python-version: [3.7, 3.8, 3.9, '3.10', '3.11', '3.12-dev'] + python-version: [3.8, 3.9, '3.10', '3.11', '3.12'] include: - - python-version: 3.7 - tox-env: py37 - python-version: 3.8 tox-env: py38 - python-version: 3.9 @@ -29,11 +27,11 @@ jobs: tox-env: py310 - python-version: '3.11' tox-env: py311 - - python-version: '3.12-dev' + - python-version: '3.12' tox-env: py312 - exclude: - - platform: windows-latest - python-version: '3.12-dev' + # exclude: + # - platform: windows-latest + # python-version: '3.12-dev' env: TOXENV: ${{ matrix.tox-env }} @@ -41,12 +39,13 @@ jobs: runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} if: "!endsWith(matrix.python-version, '-dev')" - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + allow-prereleases: true - name: Set up development Python ${{ matrix.python-version }} if: endsWith(matrix.python-version, '-dev') uses: deadsnakes/action@v2.1.1 @@ -79,9 +78,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -103,9 +102,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2e1142d..2ad3e4a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -16,11 +16,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -40,7 +40,7 @@ jobs: strategy: matrix: distribution: [bdist_wheel] - python-3-version: [7, 8, 9, '10', '11'] + python-3-version: [8, 9, '10', '11', '12'] include: - distribution: sdist python-3-version: '11' @@ -49,8 +49,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 with: python-version: 3.${{ matrix.python-3-version }} - name: Package sdist @@ -65,7 +65,7 @@ jobs: python -m build -w - name: Upload artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: path: dist/* diff --git a/backrefs/__meta__.py b/backrefs/__meta__.py index e521ccb..d371200 100644 --- a/backrefs/__meta__.py +++ b/backrefs/__meta__.py @@ -93,7 +93,7 @@ def __new__( raise ValueError("All version parts except 'release' should be integers.") if release not in REL_MAP: - raise ValueError("'{}' is not a valid release type.".format(release)) + raise ValueError(f"'{release}' is not a valid release type.") # Ensure valid pre-release (we do not allow implicit pre-releases). if ".dev-candidate" < release < "final": @@ -118,7 +118,7 @@ def __new__( elif dev: raise ValueError("Version is not a development release.") - return super(Version, cls).__new__(cls, major, minor, micro, release, pre, post, dev) + return super().__new__(cls, major, minor, micro, release, pre, post, dev) def _is_pre(self) -> bool: """Is prerelease.""" @@ -145,15 +145,15 @@ def _get_canonical(self) -> str: # Assemble major, minor, micro version and append `pre`, `post`, or `dev` if needed.. if self.micro == 0: - ver = "{}.{}".format(self.major, self.minor) + ver = f"{self.major}.{self.minor}" else: - ver = "{}.{}.{}".format(self.major, self.minor, self.micro) + ver = f"{self.major}.{self.minor}.{self.micro}" if self._is_pre(): - ver += '{}{}'.format(REL_MAP[self.release], self.pre) + ver += f'{REL_MAP[self.release]}{self.pre}' if self._is_post(): - ver += ".post{}".format(self.post) + ver += f".post{self.post}" if self._is_dev(): - ver += ".dev{}".format(self.dev) + ver += f".dev{self.dev}" return ver @@ -164,7 +164,7 @@ def parse_version(ver: str) -> Version: m = RE_VER.match(ver) if m is None: - raise ValueError("'{}' is not a valid version".format(ver)) + raise ValueError(f"'{ver}' is not a valid version") # Handle major, minor, micro major = int(m.group('major')) @@ -193,5 +193,5 @@ def parse_version(ver: str) -> Version: return Version(major, minor, micro, release, pre, post, dev) -__version_info__ = Version(5, 5, 1, "final") +__version_info__ = Version(5, 6, 0, "final") __version__ = __version_info__._get_canonical() diff --git a/backrefs/_bre_parse.py b/backrefs/_bre_parse.py index 9731d62..ceecfc6 100644 --- a/backrefs/_bre_parse.py +++ b/backrefs/_bre_parse.py @@ -205,7 +205,7 @@ def get_unicode_property(self, i: _util.StringIter, brackets: bool = False) -> t if c.upper() in _ASCII_LETTERS: prop.append(c) elif not brackets and c != '{' or brackets and c != ':': - raise SyntaxError("Unicode property missing '{{' at {}!".format(i.index - 1)) + raise SyntaxError(f"Unicode property missing '{{' at {i.index - 1}!") else: c = next(i) if c == '^': @@ -214,7 +214,7 @@ def get_unicode_property(self, i: _util.StringIter, brackets: bool = False) -> t while c not in (':', '=', '}'): if c not in _PROPERTY: - raise SyntaxError('Invalid Unicode property character at {}!'.format(i.index - 1)) + raise SyntaxError(f'Invalid Unicode property character at {i.index - 1}!') if c not in _PROPERTY_STRIP: prop.append(c) c = next(i) @@ -236,22 +236,22 @@ def get_unicode_property(self, i: _util.StringIter, brackets: bool = False) -> t if not skip: while c != end: if c not in _PROPERTY: - raise SyntaxError('Invalid Unicode property character at {}!'.format(i.index - 1)) + raise SyntaxError(f'Invalid Unicode property character at {i.index - 1}!') if c not in _PROPERTY_STRIP: value.append(c) c = next(i) if brackets and c == ':': c = next(i) if c != ']': - raise SyntaxError('Invalid Unicode property character at {}!'.format(i.index - 1)) + raise SyntaxError(f'Invalid Unicode property character at {i.index - 1}!') if not value: raise SyntaxError('Invalid Unicode property!') - except StopIteration: + except StopIteration as e: if brackets: - raise SyntaxError("Missing or unmatched ':]' at {}!".format(index)) + raise SyntaxError(f"Missing or unmatched ':]' at {index}!") from e else: - raise SyntaxError("Missing or unmatched '{{' at {}!".format(index)) + raise SyntaxError(f"Missing or unmatched '{{' at {index}!") from e return ''.join(prop).lower(), ''.join(value).lower() @@ -262,13 +262,13 @@ def get_named_unicode(self, i: _util.StringIter) -> str: value = [] try: if next(i) != '{': - raise ValueError("Named Unicode missing '{{' {}!".format(i.index - 1)) + raise ValueError(f"Named Unicode missing '{{' {i.index - 1}!") c = next(i) while c != '}': value.append(c) c = next(i) - except Exception: - raise SyntaxError("Unmatched '{{' at {}!".format(index)) + except Exception as e: + raise SyntaxError(f"Unmatched '{{' at {index}!") from e return ''.join(value) @@ -340,8 +340,8 @@ def get_comments(self, i: _util.StringIter) -> str | None: value.append(c) c = next(i) value.append(c) - except StopIteration: - raise SyntaxError("Unmatched '(' at {}!".format(index - 1)) + except StopIteration as e: + raise SyntaxError(f"Unmatched '(' at {index - 1}!") from e return ''.join(value) @@ -505,9 +505,9 @@ def char_groups(self, t: str, i: _util.StringIter) -> list[str]: if temp == '[]': # We specified some properties, but they are all # out of reach. Therefore we can match nothing. - current = ['[^{}]'.format(_uniprops.ASCII_RANGE if self.is_bytes else _uniprops.UNICODE_RANGE)] + current = [f'[^{_uniprops.ASCII_RANGE if self.is_bytes else _uniprops.UNICODE_RANGE}]'] elif temp == '[^]': - current = ['[{}]'.format(_uniprops.ASCII_RANGE if self.is_bytes else _uniprops.UNICODE_RANGE)] + current = [f'[{_uniprops.ASCII_RANGE if self.is_bytes else _uniprops.UNICODE_RANGE}]'] else: current = [temp] @@ -540,10 +540,10 @@ def unicode_name(self, name: str, in_group: bool = False) -> list[str]: value = ord(_unicodedata.lookup(name)) if self.is_bytes and value > 0xFF: if not in_group: - return ['[^{}]'.format(_uniprops.ASCII_RANGE if self.is_bytes else _uniprops.UNICODE_RANGE)] + return [f'[^{_uniprops.ASCII_RANGE if self.is_bytes else _uniprops.UNICODE_RANGE}]'] else: return [''] - return ['\\{:03o}'.format(value) if value <= 0xFF else chr(value)] + return [f'\\{value:03o}' if value <= 0xFF else chr(value)] def unicode_props( self, @@ -577,8 +577,8 @@ def unicode_props( v = _uniprops.get_unicode_property(props, prop_value, mode) if not in_group: if not v: - v = '^{}'.format(_uniprops.ASCII_RANGE if self.is_bytes else _uniprops.UNICODE_RANGE) - v = "[{}]".format(v) + v = f'^{_uniprops.ASCII_RANGE if self.is_bytes else _uniprops.UNICODE_RANGE}' + v = f"[{v}]" properties = [v] return properties @@ -587,12 +587,12 @@ def main_group(self, i: _util.StringIter) -> list[str]: """The main group: group 0.""" current = [] - while True: - try: + try: + while True: t = next(i) current.extend(self.normal(t, i)) - except StopIteration: - break + except StopIteration: + pass return current def _parse(self, search: str) -> str: @@ -620,17 +620,17 @@ def _parse(self, search: str) -> str: retry = False try: new_pattern = self.main_group(i) - except GlobalRetryException: + except GlobalRetryException as e: # Prevent a loop of retry over and over for a pattern like ((?u)(?a)) # or (?-x:(?x)) if self.temp_global_flag_swap['unicode']: if self.global_flag_swap['unicode']: - raise LoopException('Global unicode flag recursion.') + raise LoopException('Global unicode flag recursion.') from e else: self.global_flag_swap["unicode"] = True if self.temp_global_flag_swap['verbose']: if self.global_flag_swap['verbose']: - raise LoopException('Global verbose flag recursion.') + raise LoopException('Global verbose flag recursion.') from e else: self.global_flag_swap['verbose'] = True self.temp_global_flag_swap = { @@ -743,8 +743,8 @@ def get_format(self, c: str, i: _util.StringIter) -> tuple[str, list[tuple[int, while c != ']': findex.append(c) c = self.format_next(i) - except StopIteration: - raise SyntaxError("Unmatched '[' at {}".format(sindex - 1)) + except StopIteration as e: + raise SyntaxError(f"Unmatched '[' at {sindex - 1}") from e idx = self.parse_format_index(''.join(findex)) value.append((_util.FMT_INDEX, idx)) c = self.format_next(i) @@ -760,7 +760,7 @@ def get_format(self, c: str, i: _util.StringIter) -> tuple[str, list[tuple[int, if c == '!': c = self.format_next(i) if c not in _FMT_CONV_TYPE: - raise SyntaxError("Invalid conversion type at {}!".format(i.index - 1)) + raise SyntaxError(f"Invalid conversion type at {i.index - 1}!") value.append((_util.FMT_CONV, c)) c = self.format_next(i) @@ -798,7 +798,7 @@ def get_format(self, c: str, i: _util.StringIter) -> tuple[str, list[tuple[int, fill = None if fill is not None: if c not in ('<', '>', '^'): - raise SyntaxError('Invalid format spec char at {}!'.format(i.index - 1)) + raise SyntaxError(f'Invalid format spec char at {i.index - 1}!') align = c c = self.format_next(i) @@ -831,9 +831,9 @@ def get_format(self, c: str, i: _util.StringIter) -> tuple[str, list[tuple[int, ) if c != '}': - raise SyntaxError("Unmatched '{{' at {}".format(index - 1)) - except StopIteration: - raise SyntaxError("Unmatched '{{' at {}!".format(index - 1)) + raise SyntaxError(f"Unmatched '{{' at {index - 1}") + except StopIteration as e: + raise SyntaxError(f"Unmatched '{{' at {index - 1}!") from e return field, value @@ -854,7 +854,7 @@ def handle_format(self, t: str, i: _util.StringIter) -> None: self.get_single_stack() self.result.append(t) else: - raise SyntaxError("Unmatched '}}' at {}!".format(i.index - 2)) + raise SyntaxError(f"Unmatched '}}' at {i.index - 2}!") def get_octal(self, c: str, i: _util.StringIter) -> str | None: """Get octal.""" @@ -864,14 +864,14 @@ def get_octal(self, c: str, i: _util.StringIter) -> str | None: zero_count = 0 try: if c == '0': - for x in range(3): + for _ in range(3): if c != '0': break value.append(c) c = next(i) zero_count = len(value) if zero_count < 3: - for x in range(3 - zero_count): + for _ in range(3 - zero_count): if c not in _OCTAL: break value.append(c) @@ -904,7 +904,7 @@ def parse_octal(self, text: str, i: _util.StringIter) -> None: if self.use_format and value in _CURLY_BRACKETS_ORD: self.handle_format(chr(value), i) elif value <= 0xFF: - self.result.append('\\{:03o}'.format(value)) + self.result.append(f'\\{value:03o}') else: self.result.append(chr(value)) @@ -915,13 +915,13 @@ def get_named_unicode(self, i: _util.StringIter) -> str: value = [] try: if next(i) != '{': - raise SyntaxError("Named Unicode missing '{{' at {}!".format(i.index - 1)) + raise SyntaxError(f"Named Unicode missing '{{' at {i.index - 1}!") c = next(i) while c != '}': value.append(c) c = next(i) - except StopIteration: - raise SyntaxError("Unmatched '}}' at {}!".format(index)) + except StopIteration as e: + raise SyntaxError(f"Unmatched '}}' at {index}!") from e return ''.join(value) @@ -938,7 +938,7 @@ def parse_named_unicode(self, i: _util.StringIter) -> None: if self.use_format and value in _CURLY_BRACKETS_ORD: self.handle_format(chr(value), i) elif value <= 0xFF: - self.result.append('\\{:03o}'.format(value)) + self.result.append(f'\\{value:03o}') else: self.result.append(chr(value)) @@ -946,37 +946,37 @@ def get_wide_unicode(self, i: _util.StringIter) -> str: """Get narrow Unicode.""" value = [] - for x in range(3): + for _ in range(3): c = next(i) if c == '0': value.append(c) else: # pragma: no cover - raise SyntaxError('Invalid wide Unicode character at {}!'.format(i.index - 1)) + raise SyntaxError(f'Invalid wide Unicode character at {i.index - 1}!') c = next(i) if c in ('0', '1'): value.append(c) else: # pragma: no cover - raise SyntaxError('Invalid wide Unicode character at {}!'.format(i.index - 1)) + raise SyntaxError(f'Invalid wide Unicode character at {i.index - 1}!') - for x in range(4): + for _ in range(4): c = next(i) if c.lower() in _HEX: value.append(c) else: # pragma: no cover - raise SyntaxError('Invalid wide Unicode character at {}!'.format(i.index - 1)) + raise SyntaxError(f'Invalid wide Unicode character at {i.index - 1}!') return ''.join(value) def get_narrow_unicode(self, i: _util.StringIter) -> str: """Get narrow Unicode.""" value = [] - for x in range(4): + for _ in range(4): c = next(i) if c.lower() in _HEX: value.append(c) else: # pragma: no cover - raise SyntaxError('Invalid Unicode character at {}!'.format(i.index - 1)) + raise SyntaxError(f'Invalid Unicode character at {i.index - 1}!') return ''.join(value) def parse_unicode(self, i: _util.StringIter, wide: bool = False) -> None: @@ -993,7 +993,7 @@ def parse_unicode(self, i: _util.StringIter, wide: bool = False) -> None: if self.use_format and value in _CURLY_BRACKETS_ORD: self.handle_format(chr(value), i) elif value <= 0xFF: - self.result.append('\\{:03o}'.format(value)) + self.result.append(f'\\{value:03o}') else: self.result.append(chr(value)) @@ -1001,12 +1001,12 @@ def get_byte(self, i: _util.StringIter) -> str: """Get byte.""" value = [] - for x in range(2): + for _x in range(2): c = next(i) if c.lower() in _HEX: value.append(c) else: # pragma: no cover - raise SyntaxError('Invalid byte character at {}!'.format(i.index - 1)) + raise SyntaxError(f'Invalid byte character at {i.index - 1}!') return ''.join(value) def parse_bytes(self, i: _util.StringIter) -> None: @@ -1022,7 +1022,7 @@ def parse_bytes(self, i: _util.StringIter) -> None: if self.use_format and value in _CURLY_BRACKETS_ORD: self.handle_format(chr(value), i) else: - self.result.append('\\{:03o}'.format(value)) + self.result.append(f'\\{value:03o}') def get_named_group(self, t: str, i: _util.StringIter) -> str: """Get group number.""" @@ -1032,7 +1032,7 @@ def get_named_group(self, t: str, i: _util.StringIter) -> str: try: c = next(i) if c != "<": - raise SyntaxError("Group missing '<' at {}!".format(i.index - 1)) + raise SyntaxError(f"Group missing '<' at {i.index - 1}!") value.append(c) c = next(i) if c in _DIGIT: @@ -1052,9 +1052,9 @@ def get_named_group(self, t: str, i: _util.StringIter) -> str: c = next(i) value.append(c) else: - raise SyntaxError("Invalid group character at {}!".format(i.index - 1)) - except StopIteration: - raise SyntaxError("Unmatched '<' at {}!".format(index)) + raise SyntaxError(f"Invalid group character at {i.index - 1}!") + except StopIteration as e: + raise SyntaxError(f"Unmatched '<' at {index}!") from e return ''.join(value) @@ -1156,8 +1156,8 @@ def _parse_template(self, template: str) -> str: i = _util.StringIter(template) - while True: - try: + try: + while True: t = next(i) if self.use_format and t in _CURLY_BRACKETS: self.handle_format(t, i) @@ -1170,9 +1170,8 @@ def _parse_template(self, template: str) -> str: raise else: self.result.append(t) - - except StopIteration: - break + except StopIteration: + pass if len(self.result) > 1: self.literal_slots.append("".join(self.result)) @@ -1335,7 +1334,7 @@ def handle_group( ( (self.span_stack[-1] if self.span_stack else None), self.get_single_stack(), - (tuple() if self.is_bytes else '') if capture is None else capture + (() if self.is_bytes else '') if capture is None else capture ) ) ) @@ -1346,7 +1345,7 @@ def get_base_template(self) -> AnyStr: return self._original - def parse(self) -> 'ReplaceTemplate[AnyStr]': + def parse(self) -> ReplaceTemplate[AnyStr]: """Parse template.""" if not isinstance(self.pattern.pattern, type(self._original)): @@ -1492,14 +1491,14 @@ def expand(self, m: Match[AnyStr] | None) -> AnyStr: l = m.group(g_index) if l is None: l = sep - except IndexError: # pragma: no cover - raise IndexError("'{}' is out of range!".format(g_index)) + except IndexError as e: # pragma: no cover + raise IndexError(f"'{g_index}' is out of range!") from e else: # String format replace try: obj = m.group(g_index) - except IndexError: # pragma: no cover - raise IndexError("'{}' is out of range!".format(g_index)) + except IndexError as e: # pragma: no cover + raise IndexError(f"'{g_index}' is out of range!") from e l = _util.format_captures( [] if obj is None else [obj], capture, diff --git a/backrefs/_bregex_parse.py b/backrefs/_bregex_parse.py index 1438ba7..d618264 100644 --- a/backrefs/_bregex_parse.py +++ b/backrefs/_bregex_parse.py @@ -255,8 +255,8 @@ def get_comments(self, i: _util.StringIter) -> str | None: value.append(c) c = next(i) value.append(c) - except StopIteration: - raise SyntaxError("Unmatched '(' at {}!".format(index - 1)) + except StopIteration as e: + raise SyntaxError(f"Unmatched '(' at {index - 1}!") from e return ''.join(value) if value else None @@ -429,12 +429,12 @@ def main_group(self, i: _util.StringIter) -> list[str]: """The main group: group 0.""" current = [] - while True: - try: + try: + while True: t = next(i) current.extend(self.normal(t, i)) - except StopIteration: - break + except StopIteration: + pass return current def _parse(self, search: str) -> str: @@ -459,17 +459,17 @@ def _parse(self, search: str) -> str: retry = False try: new_pattern = self.main_group(i) - except GlobalRetryException: + except GlobalRetryException as e: # Prevent a loop of retry over and over for a pattern like ((?V0)(?V1)) # or on V0 (?-x:(?x)) if self.temp_global_flag_swap['version']: if self.global_flag_swap['version']: - raise LoopException('Global version flag recursion.') + raise LoopException('Global version flag recursion.') from e else: self.global_flag_swap["version"] = True if self.temp_global_flag_swap['verbose']: if self.global_flag_swap['verbose']: - raise LoopException('Global verbose flag recursion.') + raise LoopException('Global verbose flag recursion.') from e else: self.global_flag_swap['verbose'] = True self.temp_global_flag_swap = { @@ -582,8 +582,8 @@ def get_format(self, c: str, i: _util.StringIter) -> tuple[str, list[tuple[int, while c != ']': findex.append(c) c = self.format_next(i) - except StopIteration: - raise SyntaxError("Unmatched '[' at {}".format(sindex - 1)) + except StopIteration as e: + raise SyntaxError(f"Unmatched '[' at {sindex - 1}") from e idx = self.parse_format_index(''.join(findex)) value.append((_util.FMT_INDEX, idx)) c = self.format_next(i) @@ -599,7 +599,7 @@ def get_format(self, c: str, i: _util.StringIter) -> tuple[str, list[tuple[int, if c == '!': c = self.format_next(i) if c not in _FMT_CONV_TYPE: - raise SyntaxError("Invalid conversion type at {}!".format(i.index - 1)) + raise SyntaxError(f"Invalid conversion type at {i.index - 1}!") value.append((_util.FMT_CONV, c)) c = self.format_next(i) @@ -637,7 +637,7 @@ def get_format(self, c: str, i: _util.StringIter) -> tuple[str, list[tuple[int, fill = None if fill is not None: if c not in ('<', '>', '^'): - raise SyntaxError('Invalid format spec char at {}!'.format(i.index - 1)) + raise SyntaxError(f'Invalid format spec char at {i.index - 1}!') align = c c = self.format_next(i) @@ -670,9 +670,9 @@ def get_format(self, c: str, i: _util.StringIter) -> tuple[str, list[tuple[int, ) if c != '}': - raise SyntaxError("Unmatched '{{' at {}".format(index - 1)) - except StopIteration: - raise SyntaxError("Unmatched '{{' at {}!".format(index - 1)) + raise SyntaxError(f"Unmatched '{{' at {index - 1}") + except StopIteration as e: + raise SyntaxError(f"Unmatched '{{' at {index - 1}!") from e return field, value @@ -693,7 +693,7 @@ def handle_format(self, t: str, i: _util.StringIter) -> None: self.get_single_stack() self.result.append(t) else: - raise SyntaxError("Unmatched '}}' at {}!".format(i.index - 2)) + raise SyntaxError(f"Unmatched '}}' at {i.index - 2}!") def get_octal(self, c: str, i: _util.StringIter) -> str | None: """Get octal.""" @@ -703,14 +703,14 @@ def get_octal(self, c: str, i: _util.StringIter) -> str | None: zero_count = 0 try: if c == '0': - for x in range(3): + for _ in range(3): if c != '0': break value.append(c) c = next(i) zero_count = len(value) if zero_count < 3: - for x in range(3 - zero_count): + for _ in range(3 - zero_count): if c not in _OCTAL: break value.append(c) @@ -743,7 +743,7 @@ def parse_octal(self, text: str, i: _util.StringIter) -> None: if self.use_format and value in _CURLY_BRACKETS_ORD: self.handle_format(chr(value), i) elif value <= 0xFF: - self.result.append('\\{:03o}'.format(value)) + self.result.append(f'\\{value:03o}') else: self.result.append(chr(value)) @@ -754,13 +754,13 @@ def get_named_unicode(self, i: _util.StringIter) -> str: value = [] try: if next(i) != '{': - raise SyntaxError("Named Unicode missing '{{' at {}!".format(i.index - 1)) + raise SyntaxError(f"Named Unicode missing '{{' at {i.index - 1}!") c = next(i) while c != '}': value.append(c) c = next(i) - except StopIteration: - raise SyntaxError("Unmatched '{{' at {}!".format(index)) + except StopIteration as e: + raise SyntaxError(f"Unmatched '{{' at {index}!") from e return ''.join(value) @@ -777,7 +777,7 @@ def parse_named_unicode(self, i: _util.StringIter) -> None: if self.use_format and value in _CURLY_BRACKETS_ORD: self.handle_format(chr(value), i) elif value <= 0xFF: - self.result.append('\\{:03o}'.format(value)) + self.result.append(f'\\{value:03o}') else: self.result.append(chr(value)) @@ -785,37 +785,37 @@ def get_wide_unicode(self, i: _util.StringIter) -> str: """Get narrow Unicode.""" value = [] - for x in range(3): + for _ in range(3): c = next(i) if c == '0': value.append(c) else: # pragma: no cover - raise SyntaxError('Invalid wide Unicode character at {}!'.format(i.index - 1)) + raise SyntaxError(f'Invalid wide Unicode character at {i.index - 1}!') c = next(i) if c in ('0', '1'): value.append(c) else: # pragma: no cover - raise SyntaxError('Invalid wide Unicode character at {}!'.format(i.index - 1)) + raise SyntaxError(f'Invalid wide Unicode character at {i.index - 1}!') - for x in range(4): + for _ in range(4): c = next(i) if c.lower() in _HEX: value.append(c) else: # pragma: no cover - raise SyntaxError('Invalid wide Unicode character at {}!'.format(i.index - 1)) + raise SyntaxError(f'Invalid wide Unicode character at {i.index - 1}!') return ''.join(value) def get_narrow_unicode(self, i: _util.StringIter) -> str: """Get narrow Unicode.""" value = [] - for x in range(4): + for _ in range(4): c = next(i) if c.lower() in _HEX: value.append(c) else: # pragma: no cover - raise SyntaxError('Invalid Unicode character at {}!'.format(i.index - 1)) + raise SyntaxError(f'Invalid Unicode character at {i.index - 1}!') return ''.join(value) def parse_unicode(self, i: _util.StringIter, wide: bool = False) -> None: @@ -832,7 +832,7 @@ def parse_unicode(self, i: _util.StringIter, wide: bool = False) -> None: if self.use_format and value in _CURLY_BRACKETS_ORD: self.handle_format(chr(value), i) elif value <= 0xFF: - self.result.append('\\{:03o}'.format(value)) + self.result.append(f'\\{value:03o}') else: self.result.append(chr(value)) @@ -840,12 +840,12 @@ def get_byte(self, i: _util.StringIter) -> str: """Get byte.""" value = [] - for x in range(2): + for _ in range(2): c = next(i) if c.lower() in _HEX: value.append(c) else: # pragma: no cover - raise SyntaxError('Invalid byte character at {}!'.format(i.index - 1)) + raise SyntaxError(f'Invalid byte character at {i.index - 1}!') return ''.join(value) def parse_bytes(self, i: _util.StringIter) -> None: @@ -861,7 +861,7 @@ def parse_bytes(self, i: _util.StringIter) -> None: if self.use_format and value in _CURLY_BRACKETS_ORD: self.handle_format(chr(value), i) else: - self.result.append('\\{:03o}'.format(value)) + self.result.append(f'\\{value:03o}') def get_named_group(self, t: str, i: _util.StringIter) -> str: """Get group number.""" @@ -871,7 +871,7 @@ def get_named_group(self, t: str, i: _util.StringIter) -> str: try: c = next(i) if c != "<": - raise SyntaxError("Group missing '<' at {}!".format(i.index - 1)) + raise SyntaxError(f"Group missing '<' at {i.index - 1}!") value.append(c) c = next(i) if c in _DIGIT: @@ -891,9 +891,9 @@ def get_named_group(self, t: str, i: _util.StringIter) -> str: c = next(i) value.append(c) else: - raise SyntaxError("Invalid group character at {}!".format(i.index - 1)) - except StopIteration: - raise SyntaxError("Unmatched '<' at {}!".format(index)) + raise SyntaxError(f"Invalid group character at {i.index - 1}!") + except StopIteration as e: + raise SyntaxError(f"Unmatched '<' at {index}!") from e return ''.join(value) @@ -995,8 +995,8 @@ def _parse_template(self, template: str) -> str: i = _util.StringIter(template) - while True: - try: + try: + while True: t = next(i) if self.use_format and t in _CURLY_BRACKETS: self.handle_format(t, i) @@ -1009,9 +1009,8 @@ def _parse_template(self, template: str) -> str: raise else: self.result.append(t) - - except StopIteration: - break + except StopIteration: + pass if len(self.result) > 1: self.literal_slots.append("".join(self.result)) @@ -1169,7 +1168,7 @@ def handle_group( ( (self.span_stack[-1] if self.span_stack else None), self.get_single_stack(), - (tuple() if self.is_bytes else '') if capture is None else capture + (() if self.is_bytes else '') if capture is None else capture ) ) ) @@ -1180,7 +1179,7 @@ def get_base_template(self) -> AnyStr: return self._original - def parse(self) -> 'ReplaceTemplate[AnyStr]': + def parse(self) -> ReplaceTemplate[AnyStr]: """Parse template.""" if not isinstance(self.pattern.pattern, type(self._original)): @@ -1325,14 +1324,14 @@ def expand(self, m: Match[AnyStr] | None) -> AnyStr: l = cast('AnyStr | None', m.group(g_index)) if l is None: l = sep - except IndexError: # pragma: no cover - raise IndexError("'{}' is out of range!".format(g_index)) + except IndexError as e: # pragma: no cover + raise IndexError(f"'{g_index}' is out of range!") from e else: # String format replace try: obj = cast('list[AnyStr]', m.captures(g_index)) - except IndexError: # pragma: no cover - raise IndexError("'{}' is out of range!".format(g_index)) + except IndexError as e: # pragma: no cover + raise IndexError(f"'{g_index}' is out of range!") from e l = _util.format_captures( obj, capture, diff --git a/backrefs/bre.py b/backrefs/bre.py index 3417ea3..1ba60a5 100644 --- a/backrefs/bre.py +++ b/backrefs/bre.py @@ -393,7 +393,7 @@ def compile( # noqa A001 pattern: AnyStr | Pattern[AnyStr] | Bre[AnyStr], flags: int = 0, auto_compile: bool | None = None -) -> 'Bre[AnyStr]': +) -> Bre[AnyStr]: """Compile both the search or search and replace into one object.""" if isinstance(pattern, Bre): diff --git a/backrefs/bregex.py b/backrefs/bregex.py index a6bc6d2..241a87c 100644 --- a/backrefs/bregex.py +++ b/backrefs/bregex.py @@ -425,7 +425,7 @@ def compile( # noqa A001 flags: int = 0, auto_compile: bool | None = None, **kwargs: Any -) -> 'Bregex[AnyStr]': +) -> Bregex[AnyStr]: """Compile both the search or search and replace into one object.""" if isinstance(pattern, Bregex): diff --git a/backrefs/uniprops/__init__.py b/backrefs/uniprops/__init__.py index aad8710..931d0b3 100644 --- a/backrefs/uniprops/__init__.py +++ b/backrefs/uniprops/__init__.py @@ -490,7 +490,7 @@ def get_unicode_property(prop: str, value: str | None = None, mode: int = MODE_U if value in ('n', 'no', 'f', 'false'): negate = not negate elif value not in ('y', 'yes', 't', 'true'): - raise ValueError("'{}' is not a valid value for the binary property '{}'".format(value, prop)) + raise ValueError(f"'{value}' is not a valid value for the binary property '{prop}'") return get_binary_property('^' + name if negate else name, mode) else: @@ -555,9 +555,9 @@ def get_unicode_property(prop: str, value: str | None = None, mode: int = MODE_U elif name == 'verticalorientation': return get_vertical_orientation_property(value, mode) else: - raise ValueError("'{}={}' does not have a valid property name".format(prop, value)) - except Exception: - raise ValueError("'{}={}' does not appear to be a valid property".format(prop, value)) + raise ValueError(f"'{prop}={value}' does not have a valid property name") + except Exception as e: + raise ValueError(f"'{prop}={value}' does not appear to be a valid property") from e try: return get_gc_property(prop, mode) @@ -589,4 +589,4 @@ def get_unicode_property(prop: str, value: str | None = None, mode: int = MODE_U except Exception: pass - raise ValueError("'{}' does not appear to be a valid property".format(prop)) + raise ValueError(f"'{prop}' does not appear to be a valid property") diff --git a/backrefs/util.py b/backrefs/util.py index f2f7c4a..04c4fa8 100644 --- a/backrefs/util.py +++ b/backrefs/util.py @@ -28,7 +28,7 @@ def __init__(self, text: str) -> None: self._string = text self._index = 0 - def __iter__(self) -> "StringIter": + def __iter__(self) -> StringIter: """Iterate.""" return self @@ -58,8 +58,8 @@ def iternext(self) -> str: try: char = self._string[self._index] self._index += 1 - except IndexError: - raise StopIteration + except IndexError as e: + raise StopIteration from e return char @@ -136,16 +136,16 @@ def format_captures( return converter(capture) -class Immutable(object): +class Immutable: """Immutable.""" - __slots__: tuple[Any, ...] = tuple() + __slots__: tuple[Any, ...] = () def __init__(self, **kwargs: Any) -> None: """Initialize.""" for k, v in kwargs.items(): - super(Immutable, self).__setattr__(k, v) + super().__setattr__(k, v) def __setattr__(self, name: str, value: Any) -> None: """Prevent mutability.""" diff --git a/docs/src/markdown/about/changelog.md b/docs/src/markdown/about/changelog.md index 23259b9..1e6c65c 100644 --- a/docs/src/markdown/about/changelog.md +++ b/docs/src/markdown/about/changelog.md @@ -1,5 +1,9 @@ # Changelog +## 5.6 + +- **NEW**: Officially support Python 3.12. + ## 5.5.1 - **FIX**: Fix some flag issues in `bregex`. diff --git a/hatch_build.py b/hatch_build.py index c0b3702..46fa61a 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -16,17 +16,6 @@ def get_version_dev_status(root): return module.__version_info__._get_dev_status() -def get_requirements(root, requirements): - """Load list of dependencies.""" - - install_requires = [] - with open(os.path.join(root, requirements)) as f: - for line in f: - if not line.startswith("#"): - install_requires.append(line.strip()) - return install_requires - - def get_unicodedata(): """Download the `unicodedata` version for the given Python version.""" @@ -78,8 +67,6 @@ class CustomMetadataHook(MetadataHookInterface): def update(self, metadata): """See https://ofek.dev/hatch/latest/plugins/metadata-hook/ for more information.""" - metadata["dependencies"] = get_requirements(self.root, 'requirements/project.txt') - metadata['optional-dependencies'] = {'extras': get_requirements(self.root, "requirements/extras.txt")} metadata["classifiers"] = [ f"Development Status :: {get_version_dev_status(self.root)}", 'Environment :: Console', diff --git a/pyproject.toml b/pyproject.toml index 568f49f..8c4e873 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ name = "backrefs" description = "A wrapper around re and regex that adds additional back references." readme = "README.md" license = "MIT" -requires-python = ">=3.7" +requires-python = ">=3.8" authors = [ { name = "Isaac Muse", email = "Isaac.Muse@gmail.com" }, ] @@ -19,9 +19,12 @@ keywords = [ ] dynamic = [ "classifiers", - "dependencies", - "version", - "optional-dependencies" + "version" +] + +[project.optional-dependencies] +extras = [ + "regex" ] [project.urls] @@ -49,8 +52,7 @@ include = [ "/tools/unicodedata/LICENSE", "/.pyspelling.yml", "/.coveragerc", - "/mkdocs.yml", - "/tox.ini", + "/mkdocs.yml" ] exclude = [ @@ -84,3 +86,83 @@ warn_unused_ignores = false [[tool.mypy.overrides]] module = 'backrefs.uniprops.*' implicit_reexport = true + +[tool.ruff] +line-length = 120 + +extend-exclude = [ + "backrefs/uniprops/unidata/*" +] + +select = [ + "A", # flake8-builtins + "B", # flake8-bugbear + "D", # pydocstyle + "C4", # flake8-comprehensions + "N", # pep8-naming + "E", # pycodestyle + "F", # pyflakes + "PGH", # pygrep-hooks + "RUF", # ruff + # "UP", # pyupgrade + "W", # pycodestyle + "YTT", # flake8-2020, + "PERF" # Perflint +] + +ignore = [ + "B034", + "E741", + "D202", + "D401", + "D212", + "D203", + "N802", + "N801", + "N803", + "N806", + "N818", + "RUF012", + "RUF005", + "PGH004", + "RUF100" +] + +[tool.tox] +legacy_tox_ini = """ +[tox] +isolated_build = true +envlist = + py38,py39,py310,py311,py312, + lint + +[testenv] +passenv = * +deps= + .[extras] + -r requirements/test.txt +commands= + {envpython} tools/unidatadownload.py + {envpython} tools/unipropgen.py backrefs/uniprops/unidata + {envpython} -m mypy + {envpython} -m pytest --cov backrefs --cov-append tests + {envpython} -m coverage html -d {envtmpdir}/coverage + {envpython} -m coverage xml + {envpython} -m coverage report --show-missing + +[testenv:documents] +passenv = * +deps= + -r requirements/docs.txt +commands= + {envpython} -m mkdocs build --clean --verbose --strict + {envbindir}/pyspelling + +[testenv:lint] +passenv = * +deps= + . + -r requirements/lint.txt +commands= + "{envbindir}"/ruff check . +""" diff --git a/requirements/extras.txt b/requirements/extras.txt deleted file mode 100644 index 4f9256d..0000000 --- a/requirements/extras.txt +++ /dev/null @@ -1 +0,0 @@ -regex diff --git a/requirements/flake8.txt b/requirements/flake8.txt deleted file mode 100644 index ccd7518..0000000 --- a/requirements/flake8.txt +++ /dev/null @@ -1,6 +0,0 @@ -flake8 -pydocstyle -flake8-docstrings -pep8-naming -flake8-mutable -flake8-builtins diff --git a/requirements/lint.txt b/requirements/lint.txt new file mode 100644 index 0000000..af3ee57 --- /dev/null +++ b/requirements/lint.txt @@ -0,0 +1 @@ +ruff diff --git a/requirements/project.txt b/requirements/project.txt deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_bre.py b/tests/test_bre.py index 81b0864..bba47dd 100644 --- a/tests/test_bre.py +++ b/tests/test_bre.py @@ -207,7 +207,7 @@ def test_cache(self): bre.purge() self.assertEqual(bre._get_cache_size(), 0) self.assertEqual(bre._get_cache_size(True), 0) - for x in range(1000): + for _ in range(1000): value = str(random.randint(1, 10000)) p = bre.compile(value) p.sub('', value) @@ -2439,14 +2439,14 @@ def test_finditer(self): """Test that `finditer` works.""" count = 0 - for m in bre.finditer(r'\w+', 'This is a test for finditer!'): + for _ in bre.finditer(r'\w+', 'This is a test for finditer!'): count += 1 self.assertEqual(count, 6) count = 0 p = bre.compile(r'\w+') - for m in p.finditer('This is a test for finditer!'): + for _ in p.finditer('This is a test for finditer!'): count += 1 self.assertEqual(count, 6) diff --git a/tests/test_bregex.py b/tests/test_bregex.py index 3a46321..2960c2c 100644 --- a/tests/test_bregex.py +++ b/tests/test_bregex.py @@ -122,7 +122,7 @@ def test_cache(self): bregex.purge() self.assertEqual(bregex._get_cache_size(), 0) self.assertEqual(bregex._get_cache_size(True), 0) - for x in range(1000): + for _ in range(1000): value = str(random.randint(1, 10000)) p = bregex.compile(value) p.sub('', value) @@ -1996,16 +1996,12 @@ def test_split(self): def test_splititer(self): """Test that `split` works.""" - array = [] - for x in bregex.splititer(r'\W+', "This is a test for split!"): - array.append(x) - + array = list(bregex.splititer(r'\W+', "This is a test for split!")) self.assertEqual(array, ["This", "is", "a", "test", "for", "split", ""]) array = [] p = bregex.compile(r'\W+') - for x in p.splititer("This is a test for split!"): - array.append(x) + array = list(p.splititer("This is a test for split!")) self.assertEqual(array, ["This", "is", "a", "test", "for", "split", ""]) @@ -2101,14 +2097,14 @@ def test_finditer(self): """Test that `finditer` works.""" count = 0 - for m in bregex.finditer(r'\w+', 'This is a test for finditer!'): + for _ in bregex.finditer(r'\w+', 'This is a test for finditer!'): count += 1 self.assertEqual(count, 6) count = 0 p = bregex.compile(r'\w+') - for m in p.finditer('This is a test for finditer!'): + for _ in p.finditer('This is a test for finditer!'): count += 1 self.assertEqual(count, 6) diff --git a/tools/unidatadownload.py b/tools/unidatadownload.py index 80e807b..cdf9a71 100644 --- a/tools/unidatadownload.py +++ b/tools/unidatadownload.py @@ -17,7 +17,7 @@ def zip_unicode(output, version): print('Zipping %s.zip...' % version) - for root, dirs, files in os.walk(target): + for root, _, files in os.walk(target): for file in files: if file.endswith('.txt'): zipper.write(os.path.join(root, file), arcname=file) diff --git a/tools/unipropgen.py b/tools/unipropgen.py index 003a3c2..40ae458 100644 --- a/tools/unipropgen.py +++ b/tools/unipropgen.py @@ -19,9 +19,9 @@ ASCII_RANGE = (0x00, 0xFF) ASCII_LIMIT = (0x00, 0x7F) -ALL_CHARS = frozenset([x for x in range(UNICODE_RANGE[0], UNICODE_RANGE[1] + 1)]) -ASCII_UNUSED = frozenset([x for x in range(0x80, UNICODE_RANGE[1] + 1)]) -ALL_ASCII = frozenset([x for x in range(ASCII_RANGE[0], ASCII_RANGE[1] + 1)]) +ALL_CHARS = frozenset(list(range(UNICODE_RANGE[0], UNICODE_RANGE[1] + 1))) +ASCII_UNUSED = frozenset(list(range(0x80, UNICODE_RANGE[1] + 1))) +ALL_ASCII = frozenset(list(range(ASCII_RANGE[0], ASCII_RANGE[1] + 1))) HEADER = '''\ """Unicode Properties from Unicode version {} (autogen).""" {}''' @@ -58,7 +58,7 @@ def create_span(unirange, is_bytes=False): return [] if unirange[1] > MAXVALIDASCII: unirange[1] = MAXVALIDASCII - return [x for x in range(unirange[0], unirange[1] + 1)] + return list(range(unirange[0], unirange[1] + 1)) def not_explicitly_defined(table, name, is_bytes=False): @@ -67,7 +67,7 @@ def not_explicitly_defined(table, name, is_bytes=False): name = name.lower() all_chars = ALL_CHARS s = set() - for k, v in table.items(): + for v in table.values(): s.update(v) if name in table: table[name] = list(set(table[name]) | (all_chars - s)) @@ -251,7 +251,7 @@ def gen_blocks(output, ascii_props=False, append=False, prefix="", aliases=None) f.write('%s_blocks: dict[str, str] = {' % prefix) no_block = [] last = -1 - found = set(['noblock']) + found = {'noblock'} max_limit = MAXVALIDASCII if ascii_props else MAXUNICODE max_range = MAXUNICODE @@ -612,7 +612,7 @@ def gen_nf_quick_check(output, ascii_props=False, append=False, prefix="", alias nf[name][subvalue].extend(span) - for k1, v1 in nf.items(): + for v1 in nf.values(): temp = set() for k2 in list(v1.keys()): temp |= set(v1[k2]) @@ -838,7 +838,7 @@ def gen_uposix(table, posix_table, ascii_props): posix_table["posixpunct"] = list(s) # `Digit: [0-9]` - s = set([x for x in range(0x30, 0x39 + 1)]) + s = set(range(0x30, 0x39 + 1)) posix_table["posixdigit"] = list(s) # `XDigit: [\p{Nd}\p{HexDigit}]` @@ -846,9 +846,9 @@ def gen_uposix(table, posix_table, ascii_props): posix_table["xdigit"] = list(s) # `XDigit: [A-Fa-f0-9]` - s = set([x for x in range(0x30, 0x39 + 1)]) - s |= set([x for x in range(0x41, 0x46 + 1)]) - s |= set([x for x in range(0x61, 0x66 + 1)]) + s = set(range(0x30, 0x39 + 1)) + s |= set(range(0x41, 0x46 + 1)) + s |= set(range(0x61, 0x66 + 1)) posix_table["posixxdigit"] = list(s) # `Alnum: [\p{PosixAlpha}\p{PosixDigit}]` @@ -883,7 +883,7 @@ def gen_uposix(table, posix_table, ascii_props): posix_table["posixprint"] = list(s) # `ASCII: [\x00-\x7F]` - s = set([x for x in range(0, 0x7F + 1)]) + s = set(range(0, 0x7F + 1)) posix_table["posixascii"] = list(s) @@ -1056,15 +1056,15 @@ def gen_properties(output, files, aliases, ascii_props=False, append=False): table['l']['c'].append(i) s = set() - for k, v in table.items(): - for k2, v2 in v.items(): + for v in table.values(): + for v2 in v.values(): s.update(v2) table['c']['n'] = list(all_chars - s) # Create inverse of each category for k1, v1 in table.items(): inverse_category = set() - for k2, v2 in v1.items(): + for v2 in v1.values(): s = set(v2) inverse_category |= s itable[k1]['^'] = list(all_chars - inverse_category) diff --git a/tox.ini b/tox.ini deleted file mode 100644 index da36d26..0000000 --- a/tox.ini +++ /dev/null @@ -1,41 +0,0 @@ -[tox] -isolated_build = true -envlist = - py36,py37,py38,py39,py310,py311,py312, - lint - -[testenv] -passenv = * -deps= - -rrequirements/project.txt - -rrequirements/extras.txt - -rrequirements/test.txt -commands= - {envpython} tools/unidatadownload.py - {envpython} tools/unipropgen.py backrefs/uniprops/unidata - {envpython} -m mypy - {envpython} -m pytest --cov backrefs --cov-append tests - {envpython} -m coverage html -d {envtmpdir}/coverage - {envpython} -m coverage xml - {envpython} -m coverage report --show-missing - -[testenv:documents] -passenv = * -deps= - -rrequirements/docs.txt -commands= - {envpython} -m mkdocs build --clean --verbose --strict - {envbindir}/pyspelling - -[testenv:lint] -passenv = * -deps= - -rrequirements/project.txt - -rrequirements/flake8.txt -commands= - {envbindir}/flake8 {toxinidir} - -[flake8] -exclude=site/*,backrefs/uniprops/unidata/*,.tox/*,backrefs/pep562.py,build/*,dist/* -max-line-length=120 -ignore=D202,D203,D401,E741,W504,N818