You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Paths are parsed with one regular expression that treats every ? as the start of a version query, and the version that a file or listing refers to is not carried into the paths it produces.
Keys containing ? cannot be used.PATTERN_PATH only accepts a key without ? and only accepts a ?versionId=-style query after it (pyathena/filesystem/s3.py:125-128, parse_path() at :254-275). S3 allows ? in keys, and ls()/find() return such keys, but every call that parses the path raises ValueError: Invalid S3 path format:
info(), exists() and open() on s3://bucket/dir/what?.txt;
rm("s3://bucket/dir", recursive=True) on a prefix that contains such a key, because _delete_objects() parses each listed path (s3.py:938). No key under the prefix is deleted.
ls(versions=True, detail=False) returns names that do not identify a version. Each version is listed by its plain key name (s3.py:295-304, :519-546), so two versions of one key appear as two identical strings, and neither can be passed back to open()/info() to reach that version. detail=True entries carry version_id, but their name is also the plain key. test_ls_versions pins the plain names (tests/pyathena/filesystem/test_s3.py:563-585), so changing them needs a maintainer decision.
Expected:
keys containing ? can be read, listed and deleted; only a trailing ?versionId= (and its spellings) is treated as a version query;
S3File.metadata(), getxattr() and url() use S3File.version_id;
versions returned by ls(versions=True) can be addressed from their names.
With version_aware=True, after the object was overwritten between open() and read(), read() returned the first version's bytes and f.metadata() sent HeadObject without VersionId.
Environment
PyAthena master e0e85da, fsspec 2026.9.0, botocore from uv.lock, Python 3.13.1.
Found in a full audit of pyathena/filesystem/. Unless noted, checked offline with mocked S3 responses (fs._call replaced) or botocore's Stubber with dummy credentials.
Proposed fix (optional)
Parse the version query only when the path ends with ?versionId=/?versionID=/?versionid=/?version_id=, and take everything before it as the key, so [^?] is no longer required. Update PR Invalidate the object path when a version is deleted #960's path splitting the same way.
Make S3File.metadata(), getxattr() and url() pass version_id=self.version_id (adding version_id to S3FileSystem.metadata()/getxattr()/sign(), as info() already has).
If the maintainers agree, name versioned listing entries bucket/key?versionId=<id> and update test_ls_versions.
Problem
Paths are parsed with one regular expression that treats every
?as the start of a version query, and the version that a file or listing refers to is not carried into the paths it produces.Keys containing
?cannot be used.PATTERN_PATHonly accepts a key without?and only accepts a?versionId=-style query after it (pyathena/filesystem/s3.py:125-128,parse_path()at:254-275). S3 allows?in keys, andls()/find()return such keys, but every call that parses the path raisesValueError: Invalid S3 path format:info(),exists()andopen()ons3://bucket/dir/what?.txt;rm("s3://bucket/dir", recursive=True)on a prefix that contains such a key, because_delete_objects()parses each listed path (s3.py:938). No key under the prefix is deleted.PR Invalidate the object path when a version is deleted #960 (
invalidate_cache()for versioned paths) also splits paths at the first?, relying on keys not containing it.S3File.metadata(),getxattr()andurl()ignore the pinned version. Withversion_aware=True,S3Filepins the version it observed at open time inS3File.version_id(s3.py:2220-2225), and reads use it.metadata(),getxattr()andurl()pass onlyself.pathto the filesystem (s3.py:2438,:2452,:2466), so they describe and sign the latest version instead. (open(path, version_id=...)itself is S3FileSystem.open() and cat_file() raise TypeError when given version_id #936, fixed by PR Accept version_id in S3FileSystem.open() and cat_file() #958.)ls(versions=True, detail=False)returns names that do not identify a version. Each version is listed by its plain key name (s3.py:295-304,:519-546), so two versions of one key appear as two identical strings, and neither can be passed back toopen()/info()to reach that version.detail=Trueentries carryversion_id, but theirnameis also the plain key.test_ls_versionspins the plain names (tests/pyathena/filesystem/test_s3.py:563-585), so changing them needs a maintainer decision.Expected:
?can be read, listed and deleted; only a trailing?versionId=(and its spellings) is treated as a version query;S3File.metadata(),getxattr()andurl()useS3File.version_id;ls(versions=True)can be addressed from their names.Reproduction
Other observed results:
version_aware=True, after the object was overwritten betweenopen()andread(),read()returned the first version's bytes andf.metadata()sent HeadObject withoutVersionId.Environment
uv.lock, Python 3.13.1.pyathena/filesystem/. Unless noted, checked offline with mocked S3 responses (fs._callreplaced) or botocore'sStubberwith dummy credentials.Proposed fix (optional)
?versionId=/?versionID=/?versionid=/?version_id=, and take everything before it as the key, so[^?]is no longer required. Update PR Invalidate the object path when a version is deleted #960's path splitting the same way.S3File.metadata(),getxattr()andurl()passversion_id=self.version_id(addingversion_idtoS3FileSystem.metadata()/getxattr()/sign(), asinfo()already has).bucket/key?versionId=<id>and updatetest_ls_versions.