mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-08-22 06:13:25 -07:00
Update importlib-metadata==8.5.0
This commit is contained in:
parent
ae0fdd0ca6
commit
39f7de4a0e
10 changed files with 187 additions and 122 deletions
|
@ -1,24 +1,34 @@
|
||||||
|
"""
|
||||||
|
APIs exposing metadata from third-party Python packages.
|
||||||
|
|
||||||
|
This codebase is shared between importlib.metadata in the stdlib
|
||||||
|
and importlib_metadata in PyPI. See
|
||||||
|
https://github.com/python/importlib_metadata/wiki/Development-Methodology
|
||||||
|
for more detail.
|
||||||
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import abc
|
import abc
|
||||||
import sys
|
import collections
|
||||||
import json
|
|
||||||
import zipp
|
|
||||||
import email
|
import email
|
||||||
import types
|
|
||||||
import inspect
|
|
||||||
import pathlib
|
|
||||||
import operator
|
|
||||||
import textwrap
|
|
||||||
import functools
|
import functools
|
||||||
import itertools
|
import itertools
|
||||||
|
import operator
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
import posixpath
|
import posixpath
|
||||||
import collections
|
import re
|
||||||
|
import sys
|
||||||
|
import textwrap
|
||||||
|
import types
|
||||||
|
from contextlib import suppress
|
||||||
|
from importlib import import_module
|
||||||
|
from importlib.abc import MetaPathFinder
|
||||||
|
from itertools import starmap
|
||||||
|
from typing import Any, Iterable, List, Mapping, Match, Optional, Set, cast
|
||||||
|
|
||||||
from . import _meta
|
from . import _meta
|
||||||
from .compat import py39, py311
|
|
||||||
from ._collections import FreezableDefaultDict, Pair
|
from ._collections import FreezableDefaultDict, Pair
|
||||||
from ._compat import (
|
from ._compat import (
|
||||||
NullFinder,
|
NullFinder,
|
||||||
|
@ -27,12 +37,7 @@ from ._compat import (
|
||||||
from ._functools import method_cache, pass_none
|
from ._functools import method_cache, pass_none
|
||||||
from ._itertools import always_iterable, bucket, unique_everseen
|
from ._itertools import always_iterable, bucket, unique_everseen
|
||||||
from ._meta import PackageMetadata, SimplePath
|
from ._meta import PackageMetadata, SimplePath
|
||||||
|
from .compat import py39, py311
|
||||||
from contextlib import suppress
|
|
||||||
from importlib import import_module
|
|
||||||
from importlib.abc import MetaPathFinder
|
|
||||||
from itertools import starmap
|
|
||||||
from typing import Any, Iterable, List, Mapping, Match, Optional, Set, cast
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'Distribution',
|
'Distribution',
|
||||||
|
@ -58,7 +63,7 @@ class PackageNotFoundError(ModuleNotFoundError):
|
||||||
return f"No package metadata was found for {self.name}"
|
return f"No package metadata was found for {self.name}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str: # type: ignore[override]
|
def name(self) -> str: # type: ignore[override] # make readonly
|
||||||
(name,) = self.args
|
(name,) = self.args
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
@ -227,9 +232,26 @@ class EntryPoint:
|
||||||
>>> ep.matches(attr='bong')
|
>>> ep.matches(attr='bong')
|
||||||
True
|
True
|
||||||
"""
|
"""
|
||||||
|
self._disallow_dist(params)
|
||||||
attrs = (getattr(self, param) for param in params)
|
attrs = (getattr(self, param) for param in params)
|
||||||
return all(map(operator.eq, params.values(), attrs))
|
return all(map(operator.eq, params.values(), attrs))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _disallow_dist(params):
|
||||||
|
"""
|
||||||
|
Querying by dist is not allowed (dist objects are not comparable).
|
||||||
|
>>> EntryPoint(name='fan', value='fav', group='fag').matches(dist='foo')
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValueError: "dist" is not suitable for matching...
|
||||||
|
"""
|
||||||
|
if "dist" in params:
|
||||||
|
raise ValueError(
|
||||||
|
'"dist" is not suitable for matching. '
|
||||||
|
"Instead, use Distribution.entry_points.select() on a "
|
||||||
|
"located distribution."
|
||||||
|
)
|
||||||
|
|
||||||
def _key(self):
|
def _key(self):
|
||||||
return self.name, self.value, self.group
|
return self.name, self.value, self.group
|
||||||
|
|
||||||
|
@ -259,7 +281,7 @@ class EntryPoints(tuple):
|
||||||
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
def __getitem__(self, name: str) -> EntryPoint: # type: ignore[override]
|
def __getitem__(self, name: str) -> EntryPoint: # type: ignore[override] # Work with str instead of int
|
||||||
"""
|
"""
|
||||||
Get the EntryPoint in self matching name.
|
Get the EntryPoint in self matching name.
|
||||||
"""
|
"""
|
||||||
|
@ -315,7 +337,7 @@ class PackagePath(pathlib.PurePosixPath):
|
||||||
size: int
|
size: int
|
||||||
dist: Distribution
|
dist: Distribution
|
||||||
|
|
||||||
def read_text(self, encoding: str = 'utf-8') -> str: # type: ignore[override]
|
def read_text(self, encoding: str = 'utf-8') -> str:
|
||||||
return self.locate().read_text(encoding=encoding)
|
return self.locate().read_text(encoding=encoding)
|
||||||
|
|
||||||
def read_binary(self) -> bytes:
|
def read_binary(self) -> bytes:
|
||||||
|
@ -373,6 +395,17 @@ class Distribution(metaclass=abc.ABCMeta):
|
||||||
"""
|
"""
|
||||||
Given a path to a file in this distribution, return a SimplePath
|
Given a path to a file in this distribution, return a SimplePath
|
||||||
to it.
|
to it.
|
||||||
|
|
||||||
|
This method is used by callers of ``Distribution.files()`` to
|
||||||
|
locate files within the distribution. If it's possible for a
|
||||||
|
Distribution to represent files in the distribution as
|
||||||
|
``SimplePath`` objects, it should implement this method
|
||||||
|
to resolve such objects.
|
||||||
|
|
||||||
|
Some Distribution providers may elect not to resolve SimplePath
|
||||||
|
objects within the distribution by raising a
|
||||||
|
NotImplementedError, but consumers of such a Distribution would
|
||||||
|
be unable to invoke ``Distribution.files()``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -639,6 +672,9 @@ class Distribution(metaclass=abc.ABCMeta):
|
||||||
return self._load_json('direct_url.json')
|
return self._load_json('direct_url.json')
|
||||||
|
|
||||||
def _load_json(self, filename):
|
def _load_json(self, filename):
|
||||||
|
# Deferred for performance (python/importlib_metadata#503)
|
||||||
|
import json
|
||||||
|
|
||||||
return pass_none(json.loads)(
|
return pass_none(json.loads)(
|
||||||
self.read_text(filename),
|
self.read_text(filename),
|
||||||
object_hook=lambda data: types.SimpleNamespace(**data),
|
object_hook=lambda data: types.SimpleNamespace(**data),
|
||||||
|
@ -723,7 +759,7 @@ class FastPath:
|
||||||
True
|
True
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@functools.lru_cache() # type: ignore
|
@functools.lru_cache() # type: ignore[misc]
|
||||||
def __new__(cls, root):
|
def __new__(cls, root):
|
||||||
return super().__new__(cls)
|
return super().__new__(cls)
|
||||||
|
|
||||||
|
@ -741,7 +777,10 @@ class FastPath:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def zip_children(self):
|
def zip_children(self):
|
||||||
zip_path = zipp.Path(self.root)
|
# deferred for performance (python/importlib_metadata#502)
|
||||||
|
from zipp.compat.overlay import zipfile
|
||||||
|
|
||||||
|
zip_path = zipfile.Path(self.root)
|
||||||
names = zip_path.root.namelist()
|
names = zip_path.root.namelist()
|
||||||
self.joinpath = zip_path.joinpath
|
self.joinpath = zip_path.joinpath
|
||||||
|
|
||||||
|
@ -1078,11 +1117,10 @@ def _get_toplevel_name(name: PackagePath) -> str:
|
||||||
>>> _get_toplevel_name(PackagePath('foo.dist-info'))
|
>>> _get_toplevel_name(PackagePath('foo.dist-info'))
|
||||||
'foo.dist-info'
|
'foo.dist-info'
|
||||||
"""
|
"""
|
||||||
return _topmost(name) or (
|
# Defer import of inspect for performance (python/cpython#118761)
|
||||||
# python/typeshed#10328
|
import inspect
|
||||||
inspect.getmodulename(name) # type: ignore
|
|
||||||
or str(name)
|
return _topmost(name) or inspect.getmodulename(name) or str(name)
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _top_level_inferred(dist):
|
def _top_level_inferred(dist):
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
import email.message
|
||||||
import re
|
import re
|
||||||
import textwrap
|
import textwrap
|
||||||
import email.message
|
|
||||||
|
|
||||||
from ._text import FoldedCase
|
from ._text import FoldedCase
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import sys
|
|
||||||
import platform
|
import platform
|
||||||
|
import sys
|
||||||
|
|
||||||
__all__ = ['install', 'NullFinder']
|
__all__ = ['install', 'NullFinder']
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import types
|
|
||||||
import functools
|
import functools
|
||||||
|
import types
|
||||||
|
|
||||||
|
|
||||||
# from jaraco.functools 3.3
|
# from jaraco.functools 3.3
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import Protocol
|
from typing import (
|
||||||
from typing import Any, Dict, Iterator, List, Optional, TypeVar, Union, overload
|
Any,
|
||||||
|
Dict,
|
||||||
|
Iterator,
|
||||||
|
List,
|
||||||
|
Optional,
|
||||||
|
Protocol,
|
||||||
|
TypeVar,
|
||||||
|
Union,
|
||||||
|
overload,
|
||||||
|
)
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
_T = TypeVar("_T")
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,27 @@
|
||||||
|
"""
|
||||||
|
A Path-like interface for zipfiles.
|
||||||
|
|
||||||
|
This codebase is shared between zipfile.Path in the stdlib
|
||||||
|
and zipp in PyPI. See
|
||||||
|
https://github.com/python/importlib_metadata/wiki/Development-Methodology
|
||||||
|
for more detail.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import functools
|
||||||
import io
|
import io
|
||||||
import posixpath
|
|
||||||
import zipfile
|
|
||||||
import itertools
|
import itertools
|
||||||
import contextlib
|
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import posixpath
|
||||||
import re
|
import re
|
||||||
import stat
|
import stat
|
||||||
import sys
|
import sys
|
||||||
|
import zipfile
|
||||||
|
|
||||||
from .compat.py310 import text_encoding
|
from .compat.py310 import text_encoding
|
||||||
from .glob import Translator
|
from .glob import Translator
|
||||||
|
|
||||||
|
from ._functools import save_method_args
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['Path']
|
__all__ = ['Path']
|
||||||
|
|
||||||
|
@ -37,7 +48,7 @@ def _parents(path):
|
||||||
def _ancestry(path):
|
def _ancestry(path):
|
||||||
"""
|
"""
|
||||||
Given a path with elements separated by
|
Given a path with elements separated by
|
||||||
posixpath.sep, generate all elements of that path
|
posixpath.sep, generate all elements of that path.
|
||||||
|
|
||||||
>>> list(_ancestry('b/d'))
|
>>> list(_ancestry('b/d'))
|
||||||
['b/d', 'b']
|
['b/d', 'b']
|
||||||
|
@ -49,9 +60,14 @@ def _ancestry(path):
|
||||||
['b']
|
['b']
|
||||||
>>> list(_ancestry(''))
|
>>> list(_ancestry(''))
|
||||||
[]
|
[]
|
||||||
|
|
||||||
|
Multiple separators are treated like a single.
|
||||||
|
|
||||||
|
>>> list(_ancestry('//b//d///f//'))
|
||||||
|
['//b//d///f', '//b//d', '//b']
|
||||||
"""
|
"""
|
||||||
path = path.rstrip(posixpath.sep)
|
path = path.rstrip(posixpath.sep)
|
||||||
while path and path != posixpath.sep:
|
while path.rstrip(posixpath.sep):
|
||||||
yield path
|
yield path
|
||||||
path, tail = posixpath.split(path)
|
path, tail = posixpath.split(path)
|
||||||
|
|
||||||
|
@ -73,82 +89,19 @@ class InitializedState:
|
||||||
Mix-in to save the initialization state for pickling.
|
Mix-in to save the initialization state for pickling.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@save_method_args
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.__args = args
|
|
||||||
self.__kwargs = kwargs
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
return self.__args, self.__kwargs
|
return self._saved___init__.args, self._saved___init__.kwargs
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state):
|
||||||
args, kwargs = state
|
args, kwargs = state
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class SanitizedNames:
|
class CompleteDirs(InitializedState, zipfile.ZipFile):
|
||||||
"""
|
|
||||||
ZipFile mix-in to ensure names are sanitized.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def namelist(self):
|
|
||||||
return list(map(self._sanitize, super().namelist()))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _sanitize(name):
|
|
||||||
r"""
|
|
||||||
Ensure a relative path with posix separators and no dot names.
|
|
||||||
|
|
||||||
Modeled after
|
|
||||||
https://github.com/python/cpython/blob/bcc1be39cb1d04ad9fc0bd1b9193d3972835a57c/Lib/zipfile/__init__.py#L1799-L1813
|
|
||||||
but provides consistent cross-platform behavior.
|
|
||||||
|
|
||||||
>>> san = SanitizedNames._sanitize
|
|
||||||
>>> san('/foo/bar')
|
|
||||||
'foo/bar'
|
|
||||||
>>> san('//foo.txt')
|
|
||||||
'foo.txt'
|
|
||||||
>>> san('foo/.././bar.txt')
|
|
||||||
'foo/bar.txt'
|
|
||||||
>>> san('foo../.bar.txt')
|
|
||||||
'foo../.bar.txt'
|
|
||||||
>>> san('\\foo\\bar.txt')
|
|
||||||
'foo/bar.txt'
|
|
||||||
>>> san('D:\\foo.txt')
|
|
||||||
'D/foo.txt'
|
|
||||||
>>> san('\\\\server\\share\\file.txt')
|
|
||||||
'server/share/file.txt'
|
|
||||||
>>> san('\\\\?\\GLOBALROOT\\Volume3')
|
|
||||||
'?/GLOBALROOT/Volume3'
|
|
||||||
>>> san('\\\\.\\PhysicalDrive1\\root')
|
|
||||||
'PhysicalDrive1/root'
|
|
||||||
|
|
||||||
Retain any trailing slash.
|
|
||||||
>>> san('abc/')
|
|
||||||
'abc/'
|
|
||||||
|
|
||||||
Raises a ValueError if the result is empty.
|
|
||||||
>>> san('../..')
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: Empty filename
|
|
||||||
"""
|
|
||||||
|
|
||||||
def allowed(part):
|
|
||||||
return part and part not in {'..', '.'}
|
|
||||||
|
|
||||||
# Remove the drive letter.
|
|
||||||
# Don't use ntpath.splitdrive, because that also strips UNC paths
|
|
||||||
bare = re.sub('^([A-Z]):', r'\1', name, flags=re.IGNORECASE)
|
|
||||||
clean = bare.replace('\\', '/')
|
|
||||||
parts = clean.split('/')
|
|
||||||
joined = '/'.join(filter(allowed, parts))
|
|
||||||
if not joined:
|
|
||||||
raise ValueError("Empty filename")
|
|
||||||
return joined + '/' * name.endswith('/')
|
|
||||||
|
|
||||||
|
|
||||||
class CompleteDirs(InitializedState, SanitizedNames, zipfile.ZipFile):
|
|
||||||
"""
|
"""
|
||||||
A ZipFile subclass that ensures that implied directories
|
A ZipFile subclass that ensures that implied directories
|
||||||
are always included in the namelist.
|
are always included in the namelist.
|
||||||
|
@ -230,16 +183,18 @@ class FastLookup(CompleteDirs):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def namelist(self):
|
def namelist(self):
|
||||||
with contextlib.suppress(AttributeError):
|
return self._namelist
|
||||||
return self.__names
|
|
||||||
self.__names = super().namelist()
|
@functools.cached_property
|
||||||
return self.__names
|
def _namelist(self):
|
||||||
|
return super().namelist()
|
||||||
|
|
||||||
def _name_set(self):
|
def _name_set(self):
|
||||||
with contextlib.suppress(AttributeError):
|
return self._name_set_prop
|
||||||
return self.__lookup
|
|
||||||
self.__lookup = super()._name_set()
|
@functools.cached_property
|
||||||
return self.__lookup
|
def _name_set_prop(self):
|
||||||
|
return super()._name_set()
|
||||||
|
|
||||||
|
|
||||||
def _extract_text_encoding(encoding=None, *args, **kwargs):
|
def _extract_text_encoding(encoding=None, *args, **kwargs):
|
||||||
|
@ -329,7 +284,7 @@ class Path:
|
||||||
>>> str(path.parent)
|
>>> str(path.parent)
|
||||||
'mem'
|
'mem'
|
||||||
|
|
||||||
If the zipfile has no filename, such attributes are not
|
If the zipfile has no filename, such attributes are not
|
||||||
valid and accessing them will raise an Exception.
|
valid and accessing them will raise an Exception.
|
||||||
|
|
||||||
>>> zf.filename = None
|
>>> zf.filename = None
|
||||||
|
@ -388,7 +343,7 @@ class Path:
|
||||||
if self.is_dir():
|
if self.is_dir():
|
||||||
raise IsADirectoryError(self)
|
raise IsADirectoryError(self)
|
||||||
zip_mode = mode[0]
|
zip_mode = mode[0]
|
||||||
if not self.exists() and zip_mode == 'r':
|
if zip_mode == 'r' and not self.exists():
|
||||||
raise FileNotFoundError(self)
|
raise FileNotFoundError(self)
|
||||||
stream = self.root.open(self.at, zip_mode, pwd=pwd)
|
stream = self.root.open(self.at, zip_mode, pwd=pwd)
|
||||||
if 'b' in mode:
|
if 'b' in mode:
|
||||||
|
@ -470,8 +425,7 @@ class Path:
|
||||||
prefix = re.escape(self.at)
|
prefix = re.escape(self.at)
|
||||||
tr = Translator(seps='/')
|
tr = Translator(seps='/')
|
||||||
matches = re.compile(prefix + tr.translate(pattern)).fullmatch
|
matches = re.compile(prefix + tr.translate(pattern)).fullmatch
|
||||||
names = (data.filename for data in self.root.filelist)
|
return map(self._next, filter(matches, self.root.namelist()))
|
||||||
return map(self._next, filter(matches, names))
|
|
||||||
|
|
||||||
def rglob(self, pattern):
|
def rglob(self, pattern):
|
||||||
return self.glob(f'**/{pattern}')
|
return self.glob(f'**/{pattern}')
|
||||||
|
|
20
lib/zipp/_functools.py
Normal file
20
lib/zipp/_functools.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import collections
|
||||||
|
import functools
|
||||||
|
|
||||||
|
|
||||||
|
# from jaraco.functools 4.0.2
|
||||||
|
def save_method_args(method):
|
||||||
|
"""
|
||||||
|
Wrap a method such that when it is called, the args and kwargs are
|
||||||
|
saved on the method.
|
||||||
|
"""
|
||||||
|
args_and_kwargs = collections.namedtuple('args_and_kwargs', 'args kwargs')
|
||||||
|
|
||||||
|
@functools.wraps(method)
|
||||||
|
def wrapper(self, /, *args, **kwargs):
|
||||||
|
attr_name = '_saved_' + method.__name__
|
||||||
|
attr = args_and_kwargs(args, kwargs)
|
||||||
|
setattr(self, attr_name, attr)
|
||||||
|
return method(self, *args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
37
lib/zipp/compat/overlay.py
Normal file
37
lib/zipp/compat/overlay.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
"""
|
||||||
|
Expose zipp.Path as .zipfile.Path.
|
||||||
|
|
||||||
|
Includes everything else in ``zipfile`` to match future usage. Just
|
||||||
|
use:
|
||||||
|
|
||||||
|
>>> from zipp.compat.overlay import zipfile
|
||||||
|
|
||||||
|
in place of ``import zipfile``.
|
||||||
|
|
||||||
|
Relative imports are supported too.
|
||||||
|
|
||||||
|
>>> from zipp.compat.overlay.zipfile import ZipInfo
|
||||||
|
|
||||||
|
The ``zipfile`` object added to ``sys.modules`` needs to be
|
||||||
|
hashable (#126).
|
||||||
|
|
||||||
|
>>> _ = hash(sys.modules['zipp.compat.overlay.zipfile'])
|
||||||
|
"""
|
||||||
|
|
||||||
|
import importlib
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
|
|
||||||
|
import zipp
|
||||||
|
|
||||||
|
|
||||||
|
class HashableNamespace(types.SimpleNamespace):
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(tuple(vars(self)))
|
||||||
|
|
||||||
|
|
||||||
|
zipfile = HashableNamespace(**vars(importlib.import_module('zipfile')))
|
||||||
|
zipfile.Path = zipp.Path
|
||||||
|
zipfile._path = zipp
|
||||||
|
|
||||||
|
sys.modules[__name__ + '.zipfile'] = zipfile # type: ignore[assignment]
|
|
@ -1,5 +1,5 @@
|
||||||
import sys
|
|
||||||
import io
|
import io
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
def _text_encoding(encoding, stacklevel=2, /): # pragma: no cover
|
def _text_encoding(encoding, stacklevel=2, /): # pragma: no cover
|
||||||
|
@ -7,5 +7,7 @@ def _text_encoding(encoding, stacklevel=2, /): # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
text_encoding = (
|
text_encoding = (
|
||||||
io.text_encoding if sys.version_info > (3, 10) else _text_encoding # type: ignore
|
io.text_encoding # type: ignore[unused-ignore, attr-defined]
|
||||||
|
if sys.version_info > (3, 10)
|
||||||
|
else _text_encoding
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
_default_seps = os.sep + str(os.altsep) * bool(os.altsep)
|
_default_seps = os.sep + str(os.altsep) * bool(os.altsep)
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,7 +27,7 @@ class Translator:
|
||||||
"""
|
"""
|
||||||
Given a glob pattern, produce a regex that matches it.
|
Given a glob pattern, produce a regex that matches it.
|
||||||
"""
|
"""
|
||||||
return self.extend(self.translate_core(pattern))
|
return self.extend(self.match_dirs(self.translate_core(pattern)))
|
||||||
|
|
||||||
def extend(self, pattern):
|
def extend(self, pattern):
|
||||||
r"""
|
r"""
|
||||||
|
@ -41,6 +40,14 @@ class Translator:
|
||||||
"""
|
"""
|
||||||
return rf'(?s:{pattern})\Z'
|
return rf'(?s:{pattern})\Z'
|
||||||
|
|
||||||
|
def match_dirs(self, pattern):
|
||||||
|
"""
|
||||||
|
Ensure that zipfile.Path directory names are matched.
|
||||||
|
|
||||||
|
zipfile.Path directory names always end in a slash.
|
||||||
|
"""
|
||||||
|
return rf'{pattern}[/]?'
|
||||||
|
|
||||||
def translate_core(self, pattern):
|
def translate_core(self, pattern):
|
||||||
r"""
|
r"""
|
||||||
Given a glob pattern, produce a regex that matches it.
|
Given a glob pattern, produce a regex that matches it.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue