S3 filesystem¶
PyAthena ships its own fsspec-compatible
filesystem implementation for Amazon S3 (S3FileSystem), built on boto3, with an API
surface compatible with s3fs for users migrating from it.
The filesystem is used internally by the pandas/polars result sets to read query results from S3, and can also be used independently for S3 file operations.
fsspec registration¶
Importing pyathena.pandas or pyathena.polars registers S3FileSystem as the fsspec
s3 / s3a protocols via pyathena.filesystem.register_s3_filesystem. This replaces
fsspec’s default lazy mapping of the s3 protocol to s3fs, which means
fsspec.filesystem("s3") returns PyAthena’s implementation and s3fs-specific settings
(such as the S3FS_LOGGING_LEVEL environment variable) have no effect.
A filesystem class that has already been registered explicitly is also overwritten, with a warning log, so that the replacement is diagnosable. To restore another implementation, re-register it afterwards:
import fsspec
import s3fs
import pyathena.pandas # Registers PyAthena's S3FileSystem.
fsspec.register_implementation("s3", s3fs.S3FileSystem, clobber=True)
Basic usage¶
The filesystem can be constructed from a PyAthena connection, or directly with s3fs-compatible credential arguments:
from pyathena import connect
from pyathena.filesystem.s3 import S3FileSystem
fs = S3FileSystem(connect(region_name="us-west-2"))
# Or with direct credentials (s3fs-compatible arguments).
fs = S3FileSystem(key="YOUR_ACCESS_KEY", secret="YOUR_SECRET_KEY")
# Or anonymously for public buckets.
fs = S3FileSystem(anon=True)
Standard fsspec operations work as expected:
fs.ls("s3://YOUR_S3_BUCKET/path/to/")
fs.find("s3://YOUR_S3_BUCKET/path/to/")
fs.exists("s3://YOUR_S3_BUCKET/path/to/object")
fs.info("s3://YOUR_S3_BUCKET/path/to/object")
with fs.open("s3://YOUR_S3_BUCKET/path/to/object", "rb") as f:
data = f.read()
fs.pipe("s3://YOUR_S3_BUCKET/path/to/object", b"data")
fs.cat("s3://YOUR_S3_BUCKET/path/to/object")
fs.cp("s3://YOUR_S3_BUCKET/src", "s3://YOUR_S3_BUCKET/dst")
fs.rm("s3://YOUR_S3_BUCKET/path/to/", recursive=True)
Writes with pipe/pipe_file issue a single PutObject request for data up to the
block size (5 MiB by default); larger data is uploaded as a parallel multipart upload
through the buffered file path. Inside an
fsspec transaction,
writes are deferred until the transaction commits and are discarded on rollback.
Error translation¶
S3 error responses are translated into standard Python exceptions, so filesystem
operations raise natural errors instead of botocore’s ClientError:
S3 error |
Python exception |
|---|---|
|
|
|
|
|
|
|
|
Others |
|
Multipart upload management¶
Incomplete multipart uploads continue to accrue storage costs until they are completed or aborted. The filesystem can discover and abort them:
uploads = fs.list_multipart_uploads("s3://YOUR_S3_BUCKET")
for upload in uploads:
print(upload.key, upload.upload_id, upload.initiated)
# Abort all incomplete uploads under a bucket or key prefix.
fs.clear_multipart_uploads("s3://YOUR_S3_BUCKET/path/to/")
Versioning¶
With version_aware=True, reads pin the object version observed at open time, so a
file handle keeps returning consistent data even if the object is overwritten while
reading. Explicit versions can always be read with the ?versionId= suffix or the
version_id argument.
fs = S3FileSystem(connect(region_name="us-west-2"), version_aware=True)
with fs.open("s3://YOUR_S3_BUCKET/path/to/object", "rb") as f:
data = f.read() # Pinned to the version observed at open time.
# List all versions of the objects under a prefix.
fs.ls("s3://YOUR_S3_BUCKET/path/to/", versions=True, detail=True)
# Typed version information, including delete markers if requested.
versions = fs.object_version_info("s3://YOUR_S3_BUCKET/path/to/object")
for version in versions:
print(version.version_id, version.is_latest, version.last_modified)
Version-aware operations require the s3:GetObjectVersion and
s3:ListBucketVersions permissions.
Bucket lifecycle¶
Bucket creation and deletion are infrastructure-level changes and are disabled by
default: mkdir/makedirs and rmdir raise PermissionError when they would
create or delete a bucket. Pass the opt-in flags to enable them:
fs = S3FileSystem(
connect(region_name="us-west-2"),
allow_bucket_creation=True,
allow_bucket_deletion=True,
)
fs.mkdir("s3://YOUR_NEW_BUCKET")
fs.rmdir("s3://YOUR_NEW_BUCKET") # The bucket must be empty.
Creating a key prefix under an existing bucket requires no operation (S3 has no real directories below the bucket level) and is always a no-op.
Async filesystem¶
AioS3FileSystem provides the same functionality on top of fsspec’s
AsyncFileSystem, dispatching parallel operations through the asyncio event loop.
See AioS3FileSystem for details.