From 60b74c51dfaa7f6aa8b0aed0d52148a4d79cdb67 Mon Sep 17 00:00:00 2001 From: "Masaki.Nakano" Date: Mon, 13 Apr 2026 15:03:04 +0900 Subject: [PATCH 1/2] Drop Python 3.9 (EOL) --- .github/workflows/python-package.yml | 2 -- .python-versions | 1 - README.md | 2 +- pfio/v2/pathlib.py | 19 ++----------------- pyproject.toml | 5 ++--- 5 files changed, 5 insertions(+), 24 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 6ba7c38..c2d80ac 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -13,8 +13,6 @@ jobs: strategy: matrix: include: - - python-version: "3.9" - tox-env: py39 - python-version: "3.10" tox-env: py310 - python-version: "3.11" diff --git a/.python-versions b/.python-versions index c6a9edf..f1d9c78 100644 --- a/.python-versions +++ b/.python-versions @@ -1,4 +1,3 @@ -3.9 3.10 3.11 3.12 diff --git a/README.md b/README.md index a37a156..e87e686 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ learning training with batteries included. It supports ## Dependency - HDFS client and libhdfs for HDFS access -- CPython >= 3.9 +- CPython >= 3.10 ## Installation and Document build diff --git a/pfio/v2/pathlib.py b/pfio/v2/pathlib.py index 93d878c..1cc1d0d 100644 --- a/pfio/v2/pathlib.py +++ b/pfio/v2/pathlib.py @@ -48,10 +48,7 @@ def _has_directory_feature(fs: FS) -> bool: def _removeprefix(text: str, prefix: str) -> str: - # NOTE: `str.removeprefix` supports version 3.9 or higher. - if text.startswith(prefix): - return text[len(prefix):] - return text + return text.removeprefix(prefix) def _compare_fs(lhs: FS, rhs: FS) -> bool: @@ -273,11 +270,6 @@ def is_absolute(self) -> bool: return self._pure.is_absolute() def is_relative_to(self, *other: Union[str, PathLike]) -> bool: - if python_version_info.minor < 9: - raise NotImplementedError( - "`is_relative_to()` supports python 3.9 or higher" - ) - # Same rationale as `relative_to`: unwrap pfio PurePath so # 3.14's stdlib does not skip its `PurePosixPath` coercion, # and shim the multi-arg form that stdlib removed in 3.14. @@ -375,14 +367,7 @@ def with_name(self: SelfPurePathType, name: str) -> SelfPurePathType: return self.with_segments(self._pure.with_name(name)) def with_stem(self: SelfPurePathType, stem: str) -> SelfPurePathType: - if python_version_info.minor < 9: - raise NotImplementedError( - "`with_stem()` supports python 3.9 or higher" - ) - else: - return self.with_segments( - self._pure.with_stem(stem) # type: ignore - ) + return self.with_segments(self._pure.with_stem(stem)) def with_suffix(self: SelfPurePathType, suffix: str) -> SelfPurePathType: return self.with_segments(self._pure.with_suffix(suffix)) diff --git a/pyproject.toml b/pyproject.toml index 4054b0e..e2c0018 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,6 @@ classifiers = [ "Intended Audience :: Developers", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -26,7 +25,7 @@ classifiers = [ "Programming Language :: Python :: 3.14", "Topic :: System :: Filesystems", ] -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [ "boto3", "deprecation", @@ -78,7 +77,7 @@ module = [ ignore_missing_imports = true [tool.tox] -env_list = ["py39", "py310", "py311", "py312", "py313", "py314", "doc"] +env_list = ["py310", "py311", "py312", "py313", "py314", "doc"] [tool.tox.env_run_base] runner = "uv-venv-runner" From 68fecc9ad4edb19b82fc94dcd1442974c09c4afc Mon Sep 17 00:00:00 2001 From: "Masaki.Nakano" Date: Tue, 14 Apr 2026 10:11:05 +0900 Subject: [PATCH 2/2] replace pfio.v2.pathlib._removeprefix with native removeprefix --- pfio/v2/pathlib.py | 14 +++++--------- tests/v2_tests/test_pathlib.py | 7 +++---- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/pfio/v2/pathlib.py b/pfio/v2/pathlib.py index 1cc1d0d..e9bec56 100644 --- a/pfio/v2/pathlib.py +++ b/pfio/v2/pathlib.py @@ -47,10 +47,6 @@ def _has_directory_feature(fs: FS) -> bool: return True -def _removeprefix(text: str, prefix: str) -> str: - return text.removeprefix(prefix) - - def _compare_fs(lhs: FS, rhs: FS) -> bool: if type(lhs) is type(rhs): assert isinstance(lhs.cwd, str) @@ -251,7 +247,7 @@ def as_uri(self) -> str: prefix = self._fs.cwd assert isinstance(prefix, str) - key = _removeprefix(self.as_posix(), self.sep) + key = self.as_posix().removeprefix(self.sep) parsed = ParseResult( scheme=self.scheme, @@ -406,7 +402,7 @@ def __init__( super().__init__(*args, fs=fs, scheme=scheme) def _as_relative_to_fs(self) -> str: - return _removeprefix(self.as_posix(), self.anchor) + return self.as_posix().removeprefix(self.anchor) # --------------------------------------- # pathlib.Path compatible classmethods @@ -607,13 +603,13 @@ def rename( # we implemented by `copy` + `remove`. # it is generic but poor performance... target = self.with_segments( - _removeprefix(str(target), self.anchor) + str(target).removeprefix(self.anchor) ) copy(self, target) unlink(self) else: target = ( - _removeprefix(target.as_posix(), self.anchor) + target.as_posix().removeprefix(self.anchor) if isinstance(target, PurePath) else target ) @@ -645,7 +641,7 @@ def rglob( case_sensitive: Optional[bool] = None, ) -> Iterator[SelfPathType]: return self.glob( - joinpath("**", _removeprefix(pattern, self.sep)), + joinpath("**", pattern.removeprefix(self.sep)), case_sensitive=case_sensitive, ) diff --git a/tests/v2_tests/test_pathlib.py b/tests/v2_tests/test_pathlib.py index b1c6ef3..1a66190 100644 --- a/tests/v2_tests/test_pathlib.py +++ b/tests/v2_tests/test_pathlib.py @@ -12,8 +12,7 @@ from moto import mock_aws from pfio.v2 import Local, from_url -from pfio.v2.pathlib import (Path, PurePath, _has_directory_feature, - _removeprefix, unlink) +from pfio.v2.pathlib import Path, PurePath, _has_directory_feature, unlink class TemporaryS3Bucket: @@ -423,7 +422,7 @@ def test_purepath_compatible_methods( unparsed = ParseResult( parsed.scheme, parsed.netloc, - os.path.join(parsed.path, _removeprefix(path, "/")), + os.path.join(parsed.path, path.removeprefix("/")), parsed.params, parsed.query, parsed.fragment, @@ -476,7 +475,7 @@ def test_purepath_compatible_methods( ) def test_path_stat(storage: str, path: str) -> None: with from_url(url=storage) as fs: - p = Path(_removeprefix(path, "/"), fs=fs) + p = Path(path.removeprefix("/"), fs=fs) if p.is_file(): stat = p.stat() assert stat.size > 0 or stat.last_modified is not None