Skip to content

Objects whose keys end in '/' are listed as files but cannot be opened or read with negative offsets #994

Description

@laughingman7743

Problem

An object whose key ends in / and has content (for example dir/ with body abc) cannot be read through info()-based paths. fsspec's _strip_protocol() removes the trailing / (s3://bucket/dir/ and s3://bucket/dir both become bucket/dir). info() then heads dir, finds no object, and reports a directory of size 0, so:

Most keys ending in / are zero-byte folder markers (for example, from the S3 console's "Create folder"), and treating them as directories is the usual interpretation. The problem is limited to such keys that hold data, and to the inconsistency between ls() and info()/open().

Expected: decide whether a key ending in / is addressable as a file. If it is, info(), open() and negative offsets reach it by its exact key. If it is not, ls() should not return it as a file entry, and the limitation should be documented.

Reproduction

Checked on live S3 with three objects: p/only/ (b"abc"), p/slash/ (b"abc") and p/slash/child (b"x").

from pyathena import connect
from pyathena.filesystem.s3 import S3FileSystem

fs = S3FileSystem(connect(), skip_instance_cache=True)
p = "s3://<bucket>/p/slash/"
print(fs.info(p)["type"], fs.info(p)["size"])  # directory 0
print(fs.isfile(p))                            # False
print(fs.cat_file(p))                          # b'abc'
print(fs.cat_file(p, start=0, end=1))          # b'a'
print(fs.cat_file(p, start=-2))                # master: b'abc'; #987: FileNotFoundError
with fs.open(p, "rb") as f:                    # #987: FileNotFoundError
    print(f.read())                            # master: b''
print([(e["name"], e["type"], e["size"]) for e in fs.ls(p, detail=True)])
# [('<bucket>/p/slash/', 'file', 3), ('<bucket>/p/slash/child', 'file', 1)]

p/only/ (no children) gives the same results.

s3fs 2026.9.0 on the same objects: info() reports a directory of size 0, isfile() is False, open().read() returns b"", and ls() returns the same file entry. cat_file(p, start=-2) returns b"bc", because s3fs sends a suffix range (bytes=-2) without looking up the size.

Environment

Proposed fix (optional)

This needs a maintainer decision before implementation:

  • Keep keys ending in / as directories (fsspec path model, as s3fs does for info()/open()). Then exclude such keys from ls() file entries, or document them as unreadable except through cat_file(). Optionally, resolve cat_file(start=-N) with a suffix range (bytes=-N) as s3fs does, which needs no size lookup.
  • Make them addressable as files. info(), open() and the directory cache would have to distinguish dir/ from dir, contrary to fsspec's normalization. This touches info, ls, dircache keys, invalidate_cache, S3File paths and pickling, and diverges from s3fs.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions