coverage 7.6.7__cp311-cp311-win_amd64.whl → 7.11.1__cp311-cp311-win_amd64.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.
Files changed (54) hide show
  1. coverage/__init__.py +2 -0
  2. coverage/__main__.py +2 -0
  3. coverage/annotate.py +1 -2
  4. coverage/bytecode.py +177 -3
  5. coverage/cmdline.py +329 -154
  6. coverage/collector.py +31 -42
  7. coverage/config.py +166 -62
  8. coverage/context.py +4 -5
  9. coverage/control.py +164 -85
  10. coverage/core.py +70 -33
  11. coverage/data.py +3 -4
  12. coverage/debug.py +112 -56
  13. coverage/disposition.py +1 -0
  14. coverage/env.py +65 -55
  15. coverage/exceptions.py +35 -7
  16. coverage/execfile.py +18 -13
  17. coverage/files.py +23 -18
  18. coverage/html.py +134 -88
  19. coverage/htmlfiles/style.css +42 -2
  20. coverage/htmlfiles/style.scss +65 -1
  21. coverage/inorout.py +61 -44
  22. coverage/jsonreport.py +17 -8
  23. coverage/lcovreport.py +16 -20
  24. coverage/misc.py +50 -46
  25. coverage/multiproc.py +12 -7
  26. coverage/numbits.py +3 -4
  27. coverage/parser.py +193 -269
  28. coverage/patch.py +166 -0
  29. coverage/phystokens.py +24 -25
  30. coverage/plugin.py +13 -13
  31. coverage/plugin_support.py +36 -35
  32. coverage/python.py +9 -13
  33. coverage/pytracer.py +40 -33
  34. coverage/regions.py +2 -1
  35. coverage/report.py +59 -43
  36. coverage/report_core.py +6 -9
  37. coverage/results.py +118 -66
  38. coverage/sqldata.py +260 -210
  39. coverage/sqlitedb.py +33 -25
  40. coverage/sysmon.py +195 -157
  41. coverage/templite.py +6 -6
  42. coverage/tomlconfig.py +12 -12
  43. coverage/tracer.cp311-win_amd64.pyd +0 -0
  44. coverage/tracer.pyi +2 -0
  45. coverage/types.py +25 -22
  46. coverage/version.py +3 -18
  47. coverage/xmlreport.py +16 -13
  48. {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info}/METADATA +40 -18
  49. coverage-7.11.1.dist-info/RECORD +59 -0
  50. {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info}/WHEEL +1 -1
  51. coverage-7.6.7.dist-info/RECORD +0 -58
  52. {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info}/entry_points.txt +0 -0
  53. {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info/licenses}/LICENSE.txt +0 -0
  54. {coverage-7.6.7.dist-info → coverage-7.11.1.dist-info}/top_level.txt +0 -0
coverage/env.py CHANGED
@@ -8,9 +8,8 @@ from __future__ import annotations
8
8
  import os
9
9
  import platform
10
10
  import sys
11
-
12
- from typing import Any
13
11
  from collections.abc import Iterable
12
+ from typing import Any, Final
14
13
 
15
14
  # debug_info() at the bottom wants to show all the globals, but not imports.
16
15
  # Grab the global names here to know which names to not show. Nothing defined
@@ -22,11 +21,11 @@ _UNINTERESTING_GLOBALS += ["PYBEHAVIOR", "debug_info"]
22
21
  # Operating systems.
23
22
  WINDOWS = sys.platform == "win32"
24
23
  LINUX = sys.platform.startswith("linux")
25
- OSX = sys.platform == "darwin"
24
+ MACOS = sys.platform == "darwin"
26
25
 
27
26
  # Python implementations.
28
- CPYTHON = (platform.python_implementation() == "CPython")
29
- PYPY = (platform.python_implementation() == "PyPy")
27
+ CPYTHON = (platform.python_implementation() == "CPython") # fmt: skip
28
+ PYPY = (platform.python_implementation() == "PyPy") # fmt: skip
30
29
 
31
30
  # Python versions. We amend version_info with one more value, a zero if an
32
31
  # official version, or 1 if built from source beyond an official version.
@@ -36,69 +35,80 @@ PYVERSION = sys.version_info + (int(platform.python_version()[-1] == "+"),)
36
35
 
37
36
  if PYPY:
38
37
  # Minimum now is 7.3.16
39
- PYPYVERSION = sys.pypy_version_info # type: ignore[attr-defined]
38
+ PYPYVERSION = tuple(sys.pypy_version_info) # type: ignore[attr-defined]
40
39
  else:
41
40
  PYPYVERSION = (0,)
42
41
 
43
- # Python behavior.
44
- class PYBEHAVIOR:
45
- """Flags indicating this Python's behavior."""
46
-
47
- # Does Python conform to PEP626, Precise line numbers for debugging and other tools.
48
- # https://www.python.org/dev/peps/pep-0626
49
- pep626 = (PYVERSION > (3, 10, 0, "alpha", 4))
50
-
51
- # Is "if __debug__" optimized away?
52
- optimize_if_debug = not pep626
53
-
54
- # Is "if not __debug__" optimized away? The exact details have changed
55
- # across versions.
56
- if pep626:
57
- optimize_if_not_debug = 1
58
- else:
59
- optimize_if_not_debug = 2
60
-
61
- # 3.7 changed how functions with only docstrings are numbered.
62
- docstring_only_function = (not PYPY) and (PYVERSION <= (3, 10))
63
-
64
- # Lines after break/continue/return/raise are no longer compiled into the
65
- # bytecode. They used to be marked as missing, now they aren't executable.
66
- omit_after_jump = pep626 or PYPY
42
+ # Do we have a GIL?
43
+ GIL = getattr(sys, "_is_gil_enabled", lambda: True)()
67
44
 
68
- # PyPy has always omitted statements after return.
69
- omit_after_return = omit_after_jump or PYPY
45
+ # Do we ship compiled coveragepy wheels for this version?
46
+ SHIPPING_WHEELS = CPYTHON and PYVERSION[:2] <= (3, 14)
70
47
 
71
- # Optimize away unreachable try-else clauses.
72
- optimize_unreachable_try_else = pep626
48
+ # Should we default to sys.monitoring?
49
+ SYSMON_DEFAULT = CPYTHON and PYVERSION >= (3, 14)
73
50
 
74
- # Modules used to have firstlineno equal to the line number of the first
75
- # real line of code. Now they always start at 1.
76
- module_firstline_1 = pep626
77
51
 
78
- # Are "if 0:" lines (and similar) kept in the compiled code?
79
- keep_constant_test = pep626
80
-
81
- # When leaving a with-block, do we visit the with-line again for the exit?
82
- exit_through_with = (PYVERSION >= (3, 10, 0, "beta"))
52
+ # Python behavior.
53
+ class PYBEHAVIOR:
54
+ """Flags indicating this Python's behavior."""
83
55
 
84
56
  # When leaving a with-block, do we visit the with-line exactly,
85
- # or the inner-most context manager?
86
- exit_with_through_ctxmgr = (PYVERSION >= (3, 12, 6))
87
-
88
- # Match-case construct.
89
- match_case = (PYVERSION >= (3, 10))
90
-
91
- # Some words are keywords in some places, identifiers in other places.
92
- soft_keywords = (PYVERSION >= (3, 10))
57
+ # or the context managers in inner-out order?
58
+ #
59
+ # mwith.py:
60
+ # with (
61
+ # open("/tmp/one", "w") as f2,
62
+ # open("/tmp/two", "w") as f3,
63
+ # open("/tmp/three", "w") as f4,
64
+ # ):
65
+ # print("hello 6")
66
+ #
67
+ # % python3.11 -m trace -t mwith.py | grep mwith
68
+ # --- modulename: mwith, funcname: <module>
69
+ # mwith.py(2): open("/tmp/one", "w") as f2,
70
+ # mwith.py(1): with (
71
+ # mwith.py(2): open("/tmp/one", "w") as f2,
72
+ # mwith.py(3): open("/tmp/two", "w") as f3,
73
+ # mwith.py(1): with (
74
+ # mwith.py(3): open("/tmp/two", "w") as f3,
75
+ # mwith.py(4): open("/tmp/three", "w") as f4,
76
+ # mwith.py(1): with (
77
+ # mwith.py(4): open("/tmp/three", "w") as f4,
78
+ # mwith.py(6): print("hello 6")
79
+ # mwith.py(1): with (
80
+ #
81
+ # % python3.12 -m trace -t mwith.py | grep mwith
82
+ # --- modulename: mwith, funcname: <module>
83
+ # mwith.py(2): open("/tmp/one", "w") as f2,
84
+ # mwith.py(3): open("/tmp/two", "w") as f3,
85
+ # mwith.py(4): open("/tmp/three", "w") as f4,
86
+ # mwith.py(6): print("hello 6")
87
+ # mwith.py(4): open("/tmp/three", "w") as f4,
88
+ # mwith.py(3): open("/tmp/two", "w") as f3,
89
+ # mwith.py(2): open("/tmp/one", "w") as f2,
90
+
91
+ exit_with_through_ctxmgr = (PYVERSION >= (3, 12, 6)) # fmt: skip
92
+
93
+ # f-strings are parsed as code, pep 701
94
+ fstring_syntax = (PYVERSION >= (3, 12)) # fmt: skip
93
95
 
94
96
  # PEP669 Low Impact Monitoring: https://peps.python.org/pep-0669/
95
- pep669 = bool(getattr(sys, "monitoring", None))
97
+ pep669: Final[bool] = bool(getattr(sys, "monitoring", None))
96
98
 
97
99
  # Where does frame.f_lasti point when yielding from a generator?
98
100
  # It used to point at the YIELD, in 3.13 it points at the RESUME,
99
101
  # then it went back to the YIELD.
100
102
  # https://github.com/python/cpython/issues/113728
101
- lasti_is_yield = (PYVERSION[:2] != (3, 13))
103
+ lasti_is_yield = (PYVERSION[:2] != (3, 13)) # fmt: skip
104
+
105
+ # PEP649 and PEP749: Deferred annotations
106
+ deferred_annotations = (PYVERSION >= (3, 14)) # fmt: skip
107
+
108
+ # Does sys.monitoring support BRANCH_RIGHT and BRANCH_LEFT? The names
109
+ # were added in early 3.14 alphas, but didn't work entirely correctly until
110
+ # after 3.14.0a5.
111
+ branch_right_left = pep669 and (PYVERSION > (3, 14, 0, "alpha", 5, 0))
102
112
 
103
113
 
104
114
  # Coverage.py specifics, about testing scenarios. See tests/testenv.py also.
@@ -115,11 +125,11 @@ TESTING = os.getenv("COVERAGE_TESTING") == "True"
115
125
  def debug_info() -> Iterable[tuple[str, Any]]:
116
126
  """Return a list of (name, value) pairs for printing debug information."""
117
127
  info = [
118
- (name, value) for name, value in globals().items()
128
+ (name, value)
129
+ for name, value in globals().items()
119
130
  if not name.startswith("_") and name not in _UNINTERESTING_GLOBALS
120
131
  ]
121
132
  info += [
122
- (name, value) for name, value in PYBEHAVIOR.__dict__.items()
123
- if not name.startswith("_")
133
+ (name, value) for name, value in PYBEHAVIOR.__dict__.items() if not name.startswith("_")
124
134
  ]
125
135
  return sorted(info)
coverage/exceptions.py CHANGED
@@ -5,47 +5,73 @@
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
- class _BaseCoverageException(Exception):
9
- """The base-base of all Coverage exceptions."""
10
- pass
8
+ from typing import Any
11
9
 
12
10
 
13
- class CoverageException(_BaseCoverageException):
11
+ class CoverageException(Exception):
14
12
  """The base class of all exceptions raised by Coverage.py."""
15
- pass
16
13
 
17
-
18
- class ConfigError(_BaseCoverageException):
14
+ def __init__(
15
+ self,
16
+ *args: Any,
17
+ slug: str | None = None,
18
+ skip_tests: bool = False,
19
+ ) -> None:
20
+ """Create an exception.
21
+
22
+ Args:
23
+ slug: A short string identifying the exception, will be used for
24
+ linking to documentation.
25
+ skip_tests: If True, raising this exception will skip the test it
26
+ is raised in. This is used for shutting off large numbers of
27
+ tests that we know will not succeed because of a configuration
28
+ mismatch.
29
+ """
30
+
31
+ super().__init__(*args)
32
+ self.slug = slug
33
+ self.skip_tests = skip_tests
34
+
35
+
36
+ class ConfigError(CoverageException):
19
37
  """A problem with a config file, or a value in one."""
38
+
20
39
  pass
21
40
 
22
41
 
23
42
  class DataError(CoverageException):
24
43
  """An error in using a data file."""
44
+
25
45
  pass
26
46
 
47
+
27
48
  class NoDataError(CoverageException):
28
49
  """We didn't have data to work with."""
50
+
29
51
  pass
30
52
 
31
53
 
32
54
  class NoSource(CoverageException):
33
55
  """We couldn't find the source for a module."""
56
+
34
57
  pass
35
58
 
36
59
 
37
60
  class NoCode(NoSource):
38
61
  """We couldn't find any code at all."""
62
+
39
63
  pass
40
64
 
41
65
 
42
66
  class NotPython(CoverageException):
43
67
  """A source file turned out not to be parsable Python."""
68
+
44
69
  pass
45
70
 
46
71
 
47
72
  class PluginError(CoverageException):
48
73
  """A plugin misbehaved."""
74
+
49
75
  pass
50
76
 
51
77
 
@@ -55,9 +81,11 @@ class _ExceptionDuringRun(CoverageException):
55
81
  Construct it with three arguments, the values from `sys.exc_info`.
56
82
 
57
83
  """
84
+
58
85
  pass
59
86
 
60
87
 
61
88
  class CoverageWarning(Warning):
62
89
  """A warning from Coverage.py."""
90
+
63
91
  pass
coverage/execfile.py CHANGED
@@ -12,12 +12,11 @@ import marshal
12
12
  import os
13
13
  import struct
14
14
  import sys
15
-
16
15
  from importlib.machinery import ModuleSpec
17
16
  from types import CodeType, ModuleType
18
17
  from typing import Any
19
18
 
20
- from coverage.exceptions import CoverageException, _ExceptionDuringRun, NoCode, NoSource
19
+ from coverage.exceptions import CoverageException, NoCode, NoSource, _ExceptionDuringRun
21
20
  from coverage.files import canonical_filename, python_reported_file
22
21
  from coverage.misc import isolate_module
23
22
  from coverage.python import get_python_source
@@ -27,11 +26,13 @@ os = isolate_module(os)
27
26
 
28
27
  PYC_MAGIC_NUMBER = importlib.util.MAGIC_NUMBER
29
28
 
29
+
30
30
  class DummyLoader:
31
31
  """A shim for the pep302 __loader__, emulating pkgutil.ImpLoader.
32
32
 
33
33
  Currently only implements the .fullname attribute
34
34
  """
35
+
35
36
  def __init__(self, fullname: str, *_args: Any) -> None:
36
37
  self.fullname = fullname
37
38
 
@@ -57,8 +58,8 @@ def find_module(
57
58
  spec = importlib.util.find_spec(mod_main)
58
59
  if not spec:
59
60
  raise NoSource(
60
- f"No module named {mod_main}; " +
61
- f"{modulename!r} is a package and cannot be directly executed",
61
+ f"No module named {mod_main}; "
62
+ + f"{modulename!r} is a package and cannot be directly executed",
62
63
  )
63
64
  pathname = spec.origin
64
65
  packagename = spec.name
@@ -72,6 +73,7 @@ class PyRunner:
72
73
  This is meant to emulate real Python execution as closely as possible.
73
74
 
74
75
  """
76
+
75
77
  def __init__(self, args: list[str], as_module: bool = False) -> None:
76
78
  self.args = args
77
79
  self.as_module = as_module
@@ -89,7 +91,10 @@ class PyRunner:
89
91
  This needs to happen before any importing, and without importing anything.
90
92
  """
91
93
  path0: str | None
92
- if self.as_module:
94
+ if getattr(sys.flags, "safe_path", False):
95
+ # See https://docs.python.org/3/using/cmdline.html#cmdoption-P
96
+ path0 = None
97
+ elif self.as_module:
93
98
  path0 = os.getcwd()
94
99
  elif os.path.isdir(self.arg0):
95
100
  # Running a directory means running the __main__.py file in that
@@ -142,7 +147,7 @@ class PyRunner:
142
147
  # Running a directory means running the __main__.py file in that
143
148
  # directory.
144
149
  for ext in [".py", ".pyc", ".pyo"]:
145
- try_filename = os.path.join(self.arg0, "__main__" + ext)
150
+ try_filename = os.path.join(self.arg0, f"__main__{ext}")
146
151
  # 3.8.10 changed how files are reported when running a
147
152
  # directory.
148
153
  try_filename = os.path.abspath(try_filename)
@@ -177,11 +182,11 @@ class PyRunner:
177
182
  main_mod.__file__ = main_mod.__file__[:-1]
178
183
  if self.package is not None:
179
184
  main_mod.__package__ = self.package
180
- main_mod.__loader__ = self.loader # type: ignore[assignment]
185
+ main_mod.__loader__ = self.loader # type: ignore[assignment]
181
186
  if self.spec is not None:
182
187
  main_mod.__spec__ = self.spec
183
188
 
184
- main_mod.__builtins__ = sys.modules["builtins"] # type: ignore[attr-defined]
189
+ main_mod.__builtins__ = sys.modules["builtins"] # type: ignore[attr-defined]
185
190
 
186
191
  sys.modules["__main__"] = main_mod
187
192
 
@@ -206,7 +211,7 @@ class PyRunner:
206
211
  cwd = os.getcwd()
207
212
  try:
208
213
  exec(code, main_mod.__dict__)
209
- except SystemExit: # pylint: disable=try-except-raise
214
+ except SystemExit: # pylint: disable=try-except-raise
210
215
  # The user called sys.exit(). Just pass it along to the upper
211
216
  # layers, where it will be handled.
212
217
  raise
@@ -232,7 +237,7 @@ class PyRunner:
232
237
  assert err.__traceback__ is not None
233
238
  err.__traceback__ = err.__traceback__.tb_next
234
239
  sys.excepthook(typ, err, tb.tb_next)
235
- except SystemExit: # pylint: disable=try-except-raise
240
+ except SystemExit: # pylint: disable=try-except-raise
236
241
  raise
237
242
  except Exception as exc:
238
243
  # Getting the output right in the case of excepthook
@@ -311,11 +316,11 @@ def make_code_from_pyc(filename: str) -> CodeType:
311
316
  flags = struct.unpack("<L", fpyc.read(4))[0]
312
317
  hash_based = flags & 0x01
313
318
  if hash_based:
314
- fpyc.read(8) # Skip the hash.
319
+ fpyc.read(8) # Skip the hash.
315
320
  else:
316
321
  # Skip the junk in the header that we don't need.
317
- fpyc.read(4) # Skip the moddate.
318
- fpyc.read(4) # Skip the size.
322
+ fpyc.read(4) # Skip the moddate.
323
+ fpyc.read(4) # Skip the size.
319
324
 
320
325
  # The rest of the file is the code object we want.
321
326
  code = marshal.load(fpyc)
coverage/files.py CHANGED
@@ -12,21 +12,20 @@ import os.path
12
12
  import posixpath
13
13
  import re
14
14
  import sys
15
-
16
- from typing import Callable
17
15
  from collections.abc import Iterable
16
+ from typing import Callable
18
17
 
19
18
  from coverage import env
20
19
  from coverage.exceptions import ConfigError
21
20
  from coverage.misc import human_sorted, isolate_module, join_regex
22
21
 
23
-
24
22
  os = isolate_module(os)
25
23
 
26
24
 
27
25
  RELATIVE_DIR: str = ""
28
26
  CANONICAL_FILENAME_CACHE: dict[str, str] = {}
29
27
 
28
+
30
29
  def set_relative_directory() -> None:
31
30
  """Set the directory that `relative_filename` will be relative to."""
32
31
  global RELATIVE_DIR, CANONICAL_FILENAME_CACHE
@@ -59,7 +58,7 @@ def relative_filename(filename: str) -> str:
59
58
  """
60
59
  fnorm = os.path.normcase(filename)
61
60
  if fnorm.startswith(RELATIVE_DIR):
62
- filename = filename[len(RELATIVE_DIR):]
61
+ filename = filename[len(RELATIVE_DIR) :]
63
62
  return filename
64
63
 
65
64
 
@@ -74,7 +73,7 @@ def canonical_filename(filename: str) -> str:
74
73
  if not os.path.isabs(filename):
75
74
  for path in [os.curdir] + sys.path:
76
75
  if path is None:
77
- continue # type: ignore[unreachable]
76
+ continue # type: ignore[unreachable]
78
77
  f = os.path.join(path, filename)
79
78
  try:
80
79
  exists = os.path.exists(f)
@@ -112,7 +111,6 @@ def flat_rootname(filename: str) -> str:
112
111
 
113
112
 
114
113
  if env.WINDOWS:
115
-
116
114
  _ACTUAL_PATH_CACHE: dict[str, str] = {}
117
115
  _ACTUAL_PATH_LIST_CACHE: dict[str, list[str]] = {}
118
116
 
@@ -149,6 +147,7 @@ if env.WINDOWS:
149
147
  return actpath
150
148
 
151
149
  else:
150
+
152
151
  def actual_path(path: str) -> str:
153
152
  """The actual path for non-Windows platforms."""
154
153
  return path
@@ -166,7 +165,7 @@ def zip_location(filename: str) -> tuple[str, str] | None:
166
165
  name is in the zipfile.
167
166
 
168
167
  """
169
- for ext in [".zip", ".whl", ".egg", ".pex"]:
168
+ for ext in [".zip", ".whl", ".egg", ".pex", ".par"]:
170
169
  zipbase, extension, inner = filename.partition(ext + sep(filename))
171
170
  if extension:
172
171
  zipfile = zipbase + ext
@@ -224,9 +223,9 @@ class TreeMatcher:
224
223
  somewhere in a subtree rooted at one of the directories.
225
224
 
226
225
  """
226
+
227
227
  def __init__(self, paths: Iterable[str], name: str = "unknown") -> None:
228
228
  self.original_paths: list[str] = human_sorted(paths)
229
- #self.paths = list(map(os.path.normcase, paths))
230
229
  self.paths = [os.path.normcase(p) for p in paths]
231
230
  self.name = name
232
231
 
@@ -253,7 +252,8 @@ class TreeMatcher:
253
252
 
254
253
  class ModuleMatcher:
255
254
  """A matcher for modules in a tree."""
256
- def __init__(self, module_names: Iterable[str], name:str = "unknown") -> None:
255
+
256
+ def __init__(self, module_names: Iterable[str], name: str = "unknown") -> None:
257
257
  self.modules = list(module_names)
258
258
  self.name = name
259
259
 
@@ -282,6 +282,7 @@ class ModuleMatcher:
282
282
 
283
283
  class GlobMatcher:
284
284
  """A matcher for files by file name pattern."""
285
+
285
286
  def __init__(self, pats: Iterable[str], name: str = "unknown") -> None:
286
287
  self.pats = list(pats)
287
288
  self.re = globs_to_regex(self.pats, case_insensitive=env.WINDOWS)
@@ -310,6 +311,7 @@ def sep(s: str) -> str:
310
311
 
311
312
  # Tokenizer for _glob_to_regex.
312
313
  # None as a sub means disallowed.
314
+ # fmt: off
313
315
  G2RX_TOKENS = [(re.compile(rx), sub) for rx, sub in [
314
316
  (r"\*\*\*+", None), # Can't have ***
315
317
  (r"[^/]+\*\*+", None), # Can't have x**
@@ -326,17 +328,19 @@ G2RX_TOKENS = [(re.compile(rx), sub) for rx, sub in [
326
328
  (r"[\[\]]", None), # Can't have single square brackets
327
329
  (r".", r"\\\g<0>"), # Anything else is escaped to be safe
328
330
  ]]
331
+ # fmt: on
332
+
329
333
 
330
334
  def _glob_to_regex(pattern: str) -> str:
331
335
  """Convert a file-path glob pattern into a regex."""
332
336
  # Turn all backslashes into slashes to simplify the tokenizer.
333
337
  pattern = pattern.replace("\\", "/")
334
338
  if "/" not in pattern:
335
- pattern = "**/" + pattern
339
+ pattern = f"**/{pattern}"
336
340
  path_rx = []
337
341
  pos = 0
338
342
  while pos < len(pattern):
339
- for rx, sub in G2RX_TOKENS: # pragma: always breaks
343
+ for rx, sub in G2RX_TOKENS: # pragma: always breaks
340
344
  if m := rx.match(pattern, pos=pos):
341
345
  if sub is None:
342
346
  raise ConfigError(f"File pattern can't include {m[0]!r}")
@@ -372,7 +376,7 @@ def globs_to_regex(
372
376
  flags |= re.IGNORECASE
373
377
  rx = join_regex(map(_glob_to_regex, patterns))
374
378
  if not partial:
375
- rx = fr"(?:{rx})\Z"
379
+ rx = rf"(?:{rx})\Z"
376
380
  compiled = re.compile(rx, flags=flags)
377
381
  return compiled
378
382
 
@@ -388,6 +392,7 @@ class PathAliases:
388
392
  map a path through those aliases to produce a unified path.
389
393
 
390
394
  """
395
+
391
396
  def __init__(
392
397
  self,
393
398
  debugfn: Callable[[str], None] | None = None,
@@ -444,7 +449,7 @@ class PathAliases:
444
449
  result = result.rstrip(r"\/") + result_sep
445
450
  self.aliases.append((original_pattern, regex, result))
446
451
 
447
- def map(self, path: str, exists:Callable[[str], bool] = source_exists) -> str:
452
+ def map(self, path: str, exists: Callable[[str], bool] = source_exists) -> str:
448
453
  """Map `path` through the aliases.
449
454
 
450
455
  `path` is checked against all of the patterns. The first pattern to
@@ -478,13 +483,13 @@ class PathAliases:
478
483
  new = new[2:]
479
484
  if not exists(new):
480
485
  self.debugfn(
481
- f"Rule {original_pattern!r} changed {path!r} to {new!r} " +
482
- "which doesn't exist, continuing",
486
+ f"Rule {original_pattern!r} changed {path!r} to {new!r} "
487
+ + "which doesn't exist, continuing",
483
488
  )
484
489
  continue
485
490
  self.debugfn(
486
- f"Matched path {path!r} to rule {original_pattern!r} -> {result!r}, " +
487
- f"producing {new!r}",
491
+ f"Matched path {path!r} to rule {original_pattern!r} -> {result!r}, "
492
+ + f"producing {new!r}",
488
493
  )
489
494
  return new
490
495
 
@@ -499,7 +504,7 @@ class PathAliases:
499
504
  if len(parts) > 1:
500
505
  dir1 = parts[0]
501
506
  pattern = f"*/{dir1}"
502
- regex_pat = fr"^(.*[\\/])?{re.escape(dir1)}[\\/]"
507
+ regex_pat = rf"^(.*[\\/])?{re.escape(dir1)}[\\/]"
503
508
  result = f"{dir1}{os.sep}"
504
509
  # Only add a new pattern if we don't already have this pattern.
505
510
  if not any(p == pattern for p, _, _ in self.aliases):