diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 6b43e22..3c77f06 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -11,10 +11,10 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.8, 3.9, "3.10", 3.11, 3.12] + python-version: [3.8, 3.9, "3.10", 3.11, 3.12, 3.13] steps: - uses: actions/checkout@v2 - + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: diff --git a/s3path/current_version.py b/s3path/current_version.py index b77f83e..b96a5c6 100644 --- a/s3path/current_version.py +++ b/s3path/current_version.py @@ -57,6 +57,10 @@ class PureS3Path(PurePath): S3 is not a file-system but we can look at it like a POSIX system. """ _flavour = flavour + + if sys.version_info >= (3, 13): + parser = _S3Flavour() + __slots__ = () def __init__(self, *args): @@ -70,7 +74,10 @@ def __init__(self, *args): new_parts.remove(part) self._raw_paths = new_parts - self._load_parts() + if sys.version_info >= (3, 13): + self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path) + else: + self._load_parts() @classmethod def from_uri(cls, uri: str): @@ -276,7 +283,7 @@ def is_mount(self) -> Literal[False]: return False -class S3Path(_PathNotSupportedMixin, Path, PureS3Path): +class S3Path(_PathNotSupportedMixin, PureS3Path, Path): def stat(self, *, follow_symlinks: bool = True) -> accessor.StatResult: """ Returns information about this path (similarly to boto3's ObjectSummary). @@ -433,6 +440,27 @@ def iterdir(self): # todo: -> Generator[S3Path, None, None]: for name in accessor.listdir(self): yield self._make_child_relpath(name) + def _make_child_relpath(self, name): + # _make_child_relpath was removed from Python 3.13 in + # 30f0643e36d2c9a5849c76ca0b27b748448d0567 + if sys.version_info < (3, 13): + return super()._make_child_relpath(name) + + path_str = str(self) + tail = self._tail + if tail: + path_str = f'{path_str}{self._flavour.sep}{name}' + elif path_str != '.': + path_str = f'{path_str}{name}' + else: + path_str = name + path = self.with_segments(path_str) + path._str = path_str + path._drv = self.drive + path._root = self.root + path._tail_cached = tail + [name] + return path + def open( self, mode: Literal['r', 'w', 'rb', 'wb'] = 'r', @@ -452,7 +480,7 @@ def open( errors=errors, newline=newline) - def glob(self, pattern: str): # todo: -> Generator[S3Path, None, None]: + def glob(self, pattern: str, *, case_sensitive=None, recurse_symlinks=False): # todo: -> Generator[S3Path, None, None]: """ Glob the given relative pattern in the Bucket / key prefix represented by this path, yielding all matching files (of any kind) @@ -461,11 +489,11 @@ def glob(self, pattern: str): # todo: -> Generator[S3Path, None, None]: general_options = accessor.configuration_map.get_general_options(self) glob_new_algorithm = general_options['glob_new_algorithm'] if not glob_new_algorithm: - yield from super().glob(pattern) + yield from self._glob_py313(pattern) return yield from self._glob(pattern) - def rglob(self, pattern: str): # todo: -> Generator[S3Path, None, None]: + def rglob(self, pattern: str, *, case_sensitive=None, recurse_symlinks=False): # todo: -> Generator[S3Path, None, None]: """ This is like calling S3Path.glob with "**/" added in front of the given relative pattern """ @@ -670,6 +698,9 @@ class VersionedS3Path(PureVersionedS3Path, S3Path): << VersionedS3Path('//', version_id='') """ + def __init__(self, *args, version_id): + super().__init__(*args) + def _is_wildcard_pattern(pat): # Whether this pattern needs actual matching using fnmatch, or can diff --git a/tests/test_path_operations.py b/tests/test_path_operations.py index 5f84b71..ae3d75b 100644 --- a/tests/test_path_operations.py +++ b/tests/test_path_operations.py @@ -471,13 +471,13 @@ def test_iterdir(s3_mock): s3_path = S3Path('/test-bucket/docs') assert sorted(s3_path.iterdir()) == [ - S3Path('/test-bucket/docs/Makefile'), S3Path('/test-bucket/docs/_build'), S3Path('/test-bucket/docs/_static'), S3Path('/test-bucket/docs/_templates'), S3Path('/test-bucket/docs/conf.py'), S3Path('/test-bucket/docs/index.rst'), S3Path('/test-bucket/docs/make.bat'), + S3Path('/test-bucket/docs/Makefile'), ]