Skip to content

Commit 2939d2a

Browse files
authored
Add support for Py 3.13 (#221)
* Add support for Py 3.13 - Support Py 3.13 - Remove deprecated `raw_escape` - Remove casting which incurs a performance cost for little gain. Once that cannot easily be resolved, just ignore the mypy error. - Update formatted strings to f-string (where possible) * Remove some dead code
1 parent dd0b3c2 commit 2939d2a

File tree

13 files changed

+88
-235
lines changed

13 files changed

+88
-235
lines changed

docs/src/markdown/about/changelog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## 9.0
4+
5+
- **NEW**: Remove deprecated function `glob.raw_escape`.
6+
- **NEW**: Officially support Python 3.13.
7+
38
## 8.5.2
49

510
- **FIX**: Fix `pathlib` issue with inheritance on Python versions greater than 3.12.

docs/src/markdown/fnmatch.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ Translate patterns now provide capturing groups for [`EXTMATCH`](#extmatch) grou
203203
def escape(pattern):
204204
```
205205

206-
The `escape` function will conservatively escape `-`, `!`, `*`, `?`, `(`, `)`, `[`, `]`, `|`, `{`, `}`. and `\` with
206+
The `escape` function will conservatively escape `-`, `!`, `*`, `?`, `(`, `)`, `[`, `]`, `|`, `{`, `}`, and `\` with
207207
backslashes, regardless of what feature is or is not enabled. It is meant to escape filenames.
208208

209209
```pycon3

docs/src/markdown/glob.md

Lines changed: 1 addition & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,7 @@ Translate patterns now provide capturing groups for [`EXTGLOB`](#extglob) groups
600600
def escape(pattern, unix=None):
601601
```
602602

603-
The `escape` function will conservatively escape `-`, `!`, `*`, `?`, `(`, `)`, `[`, `]`, `|`, `{`, `}`. and `\` with
603+
The `escape` function will conservatively escape `-`, `!`, `*`, `?`, `(`, `)`, `[`, `]`, `|`, `{`, `}`, and `\` with
604604
backslashes, regardless of what feature is or is not enabled. It is meant to escape path parts (filenames, Windows
605605
drives, UNC sharepoints) or full paths.
606606

@@ -660,90 +660,6 @@ force Windows style escaping.
660660
drives manually in their match patterns as well.
661661
///
662662

663-
#### `glob.raw_escape` {: #raw_escape}
664-
665-
/// warning | Deprecated 8.1
666-
In 8.1, `raw_escape` has been deprecated. The same can be accomplished simply by using `codecs` and then using the
667-
normal [`escape`](#escape):
668-
669-
```pycon3
670-
>>> string = r"translate\\raw strings\\\u00c3\xc3\303\N{LATIN CAPITAL LETTER A WITH TILDE}"
671-
>>> translated = codecs.decode(string, 'unicode_escape')
672-
>>> glob.escape(translated)
673-
'translate\\\\raw strings\\\\ÃÃÃÃ'
674-
>>> glob.raw_escape(string)
675-
'translate\\\\raw strings\\\\ÃÃÃÃ'
676-
```
677-
///
678-
679-
```py3
680-
def raw_escape(pattern, unix=None, raw_chars=True):
681-
```
682-
683-
`raw_escape` is kind of a niche function and 99% of the time, it is recommended to use [`escape`](#escape).
684-
685-
The big difference between `raw_escape` and [`escape`](#escape) is how `\` are handled. `raw_escape` is mainly for paths
686-
provided to Python via an interface that doesn't process Python strings like they normally are, for instance an input
687-
in a GUI.
688-
689-
To illustrate, you may have an interface to input path names, but may want to take advantage of Python Unicode
690-
references. Normally, on a python command line, you can do this:
691-
692-
```pycon3
693-
>>> 'folder\\El Ni\u00f1o'
694-
'folder\\El Niño'
695-
```
696-
697-
But when in a GUI interface, if a user inputs the same, it's like getting a raw string.
698-
699-
```pycon3
700-
>>> r'folder\\El Ni\u00f1o'
701-
'folder\\\\El Ni\\u00f1o'
702-
```
703-
704-
`raw_escape` will take a raw string in the above format and resolve character escapes and escape the path as if it was
705-
a normal string. Notice to do this, we must treat literal Windows' path backslashes as an escaped backslash.
706-
707-
```pycon3
708-
>>> glob.escape('folder\\El Ni\u00f1o', unix=False)
709-
'folder\\\\El Niño'
710-
>>> glob.raw_escape(r'folder\\El Ni\u00f1o')
711-
'folder\\\\El Niño'
712-
```
713-
714-
Handling of raw character references can be turned off if desired:
715-
716-
```pycon3
717-
>>> glob.raw_escape(r'my\\file-\x31.txt', unix=False)
718-
'my\\\\file\\-1.txt'
719-
>>> glob.raw_escape(r'my\\file-\x31.txt', unix=False, raw_chars=False)
720-
'my\\\\file\\-\\\\x31.txt'
721-
```
722-
723-
Outside of the treatment of `\`, `raw_escape` will function just like [`escape`](#escape):
724-
725-
`raw_escape` will detect the system it is running on and pick Windows escape logic or Linux/Unix logic. Since
726-
[`globmatch`](#globmatch) allows you to match Unix style paths on a Windows system, and vice versa, you can force
727-
Unix style escaping or Windows style escaping via the `unix` parameter. When `unix` is `None`, the escape style will be
728-
detected, when `unix` is `True` Linux/Unix style escaping will be used, and when `unix` is `False` Windows style
729-
escaping will be used.
730-
731-
```pycon3
732-
>>> glob.raw_escape(r'some/path?/\x2a\x2afile\x2a\x2a{}.txt', unix=True)
733-
```
734-
735-
/// new | New 5.0
736-
The `unix` parameter is now `None` by default. Set to `True` to force Linux/Unix style escaping or set to `False` to
737-
force Windows style escaping.
738-
///
739-
740-
/// new | New 7.0
741-
`{`, `}`, and `|` will be escaped in Windows drives. Additionally, users can escape these characters in Windows
742-
drives manually in their match patterns as well.
743-
744-
`raw_chars` option was added.
745-
///
746-
747663
### `glob.is_magic` {: #is_magic}
748664

749665
```py3

hatch_build.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def update(self, metadata):
3333
'Programming Language :: Python :: 3.10',
3434
'Programming Language :: Python :: 3.11',
3535
'Programming Language :: Python :: 3.12',
36+
'Programming Language :: Python :: 3.13',
3637
'Topic :: Software Development :: Libraries :: Python Modules',
3738
'Typing :: Typed'
3839
]

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ legacy_tox_ini = """
111111
isolated_build = true
112112
skipsdist=true
113113
envlist=
114-
py38,py39,py310,py311,py312,
114+
py38,py39,py310,py311,py312,py313,
115115
lint
116116
117117
[testenv]

tests/test_glob.py

Lines changed: 9 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,7 +1373,7 @@ class TestGlobCornerCaseMarked(Testglob):
13731373
class TestGlobEscapes(unittest.TestCase):
13741374
"""Test escaping."""
13751375

1376-
def check_escape(self, arg, expected, raw=False, unix=None, raw_chars=True):
1376+
def check_escape(self, arg, expected, unix=None):
13771377
"""Verify escapes."""
13781378

13791379
flags = 0
@@ -1382,38 +1382,15 @@ def check_escape(self, arg, expected, raw=False, unix=None, raw_chars=True):
13821382
elif unix is True:
13831383
flags = glob.FORCEUNIX
13841384

1385-
if raw:
1386-
self.assertEqual(glob.raw_escape(arg, unix=unix, raw_chars=raw_chars), expected)
1387-
self.assertEqual(glob.raw_escape(os.fsencode(arg), unix=unix, raw_chars=raw_chars), os.fsencode(expected))
1388-
file = (util.norm_pattern(arg, False, True) if raw_chars else arg).replace('\\\\', '\\')
1389-
self.assertTrue(
1390-
glob.globmatch(
1391-
file,
1392-
glob.raw_escape(arg, unix=unix, raw_chars=raw_chars),
1393-
flags=flags
1394-
)
1395-
)
1396-
else:
1397-
self.assertEqual(glob.escape(arg, unix=unix), expected)
1398-
self.assertEqual(glob.escape(os.fsencode(arg), unix=unix), os.fsencode(expected))
1399-
self.assertTrue(
1400-
glob.globmatch(
1401-
arg,
1402-
glob.escape(arg, unix=unix),
1403-
flags=flags
1404-
)
1385+
self.assertEqual(glob.escape(arg, unix=unix), expected)
1386+
self.assertEqual(glob.escape(os.fsencode(arg), unix=unix), os.fsencode(expected))
1387+
self.assertTrue(
1388+
glob.globmatch(
1389+
arg,
1390+
glob.escape(arg, unix=unix),
1391+
flags=flags
14051392
)
1406-
1407-
def test_raw_escape_deprecation(self):
1408-
"""Test raw escape deprecation."""
1409-
1410-
with warnings.catch_warnings(record=True) as w:
1411-
warnings.simplefilter("always")
1412-
1413-
glob.raw_escape(r'test\\test')
1414-
1415-
self.assertTrue(len(w) == 1)
1416-
self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
1393+
)
14171394

14181395
def test_escape(self):
14191396
"""Test path escapes."""
@@ -1426,34 +1403,6 @@ def test_escape(self):
14261403
check('[[_/*?*/_]]', r'\[\[_/\*\?\*/_\]\]')
14271404
check('/[[_/*?*/_]]/', r'/\[\[_/\*\?\*/_\]\]/')
14281405

1429-
def test_raw_escape(self):
1430-
"""Test path escapes."""
1431-
1432-
check = self.check_escape
1433-
check(r'abc', 'abc', raw=True)
1434-
check(r'[', r'\[', raw=True)
1435-
check(r'?', r'\?', raw=True)
1436-
check(r'*', r'\*', raw=True)
1437-
check(r'[[_/*?*/_]]', r'\[\[_/\*\?\*/_\]\]', raw=True)
1438-
check(r'/[[_/*?*/_]]/', r'/\[\[_/\*\?\*/_\]\]/', raw=True)
1439-
check(r'\x3f', r'\?', raw=True)
1440-
check(r'\\\\[^what]\\name\\temp', r'\\\\[^what]\\name\\temp', raw=True, unix=False)
1441-
check('//[^what]/name/temp', r'//[^what]/name/temp', raw=True, unix=False)
1442-
1443-
def test_raw_escape_no_raw_chars(self):
1444-
"""Test path escapes with no raw character translations."""
1445-
1446-
check = self.check_escape
1447-
check(r'abc', 'abc', raw=True, raw_chars=False)
1448-
check(r'[', r'\[', raw=True, raw_chars=False)
1449-
check(r'?', r'\?', raw=True, raw_chars=False)
1450-
check(r'*', r'\*', raw=True, raw_chars=False)
1451-
check(r'[[_/*?*/_]]', r'\[\[_/\*\?\*/_\]\]', raw=True, raw_chars=False)
1452-
check(r'/[[_/*?*/_]]/', r'/\[\[_/\*\?\*/_\]\]/', raw=True, raw_chars=False)
1453-
check(r'\x3f', r'\\x3f', raw=True, raw_chars=False)
1454-
check(r'\\\\[^what]\\name\\temp', r'\\\\[^what]\\name\\temp', raw=True, raw_chars=False, unix=False)
1455-
check('//[^what]/name/temp', r'//[^what]/name/temp', raw=True, raw_chars=False, unix=False)
1456-
14571406
@unittest.skipUnless(sys.platform.startswith('win'), "Windows specific test")
14581407
def test_escape_windows(self):
14591408
"""Test windows escapes."""

wcmatch/__meta__.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Meta related things."""
22
from __future__ import annotations
3-
from __future__ import unicode_literals
43
from collections import namedtuple
54
import re
65

@@ -94,7 +93,7 @@ def __new__(
9493
raise ValueError("All version parts except 'release' should be integers.")
9594

9695
if release not in REL_MAP:
97-
raise ValueError("'{}' is not a valid release type.".format(release))
96+
raise ValueError(f"'{release}' is not a valid release type.")
9897

9998
# Ensure valid pre-release (we do not allow implicit pre-releases).
10099
if ".dev-candidate" < release < "final":
@@ -119,7 +118,7 @@ def __new__(
119118
elif dev:
120119
raise ValueError("Version is not a development release.")
121120

122-
return super(Version, cls).__new__(cls, major, minor, micro, release, pre, post, dev)
121+
return super().__new__(cls, major, minor, micro, release, pre, post, dev)
123122

124123
def _is_pre(self) -> bool:
125124
"""Is prerelease."""
@@ -146,15 +145,15 @@ def _get_canonical(self) -> str:
146145

147146
# Assemble major, minor, micro version and append `pre`, `post`, or `dev` if needed..
148147
if self.micro == 0:
149-
ver = "{}.{}".format(self.major, self.minor)
148+
ver = f"{self.major}.{self.minor}"
150149
else:
151-
ver = "{}.{}.{}".format(self.major, self.minor, self.micro)
150+
ver = f"{self.major}.{self.minor}.{self.micro}"
152151
if self._is_pre():
153-
ver += '{}{}'.format(REL_MAP[self.release], self.pre)
152+
ver += f'{REL_MAP[self.release]}{self.pre}'
154153
if self._is_post():
155-
ver += ".post{}".format(self.post)
154+
ver += f".post{self.post}"
156155
if self._is_dev():
157-
ver += ".dev{}".format(self.dev)
156+
ver += f".dev{self.dev}"
158157

159158
return ver
160159

@@ -165,7 +164,7 @@ def parse_version(ver: str) -> Version:
165164
m = RE_VER.match(ver)
166165

167166
if m is None:
168-
raise ValueError("'{}' is not a valid version".format(ver))
167+
raise ValueError(f"'{ver}' is not a valid version")
169168

170169
# Handle major, minor, micro
171170
major = int(m.group('major'))
@@ -194,5 +193,5 @@ def parse_version(ver: str) -> Version:
194193
return Version(major, minor, micro, release, pre, post, dev)
195194

196195

197-
__version_info__ = Version(8, 5, 2, "final")
196+
__version_info__ = Version(9, 0, 0, "final")
198197
__version__ = __version_info__._get_canonical()

wcmatch/_wcmatch.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import stat
66
import copyreg
77
from . import util
8-
from typing import Pattern, AnyStr, Generic, Any, cast
8+
from typing import Pattern, AnyStr, Generic, Any
99

1010
# `O_DIRECTORY` may not always be defined
1111
DIR_FLAGS = os.O_RDONLY | getattr(os, 'O_DIRECTORY', 0)
@@ -192,7 +192,7 @@ def match(self, root_dir: AnyStr | None = None, dir_fd: int | None = None) -> bo
192192
)
193193
)
194194

195-
re_mount = cast(Pattern[AnyStr], (RE_WIN_MOUNT if util.platform() == "windows" else RE_MOUNT)[self.ptype])
195+
re_mount = (RE_WIN_MOUNT if util.platform() == "windows" else RE_MOUNT)[self.ptype] # type: Pattern[AnyStr] # type: ignore[assignment]
196196
is_abs = re_mount.match(self.filename) is not None
197197

198198
if is_abs:

0 commit comments

Comments
 (0)