Type-safe, Path-like S3 utilities built on top of boto3.
Project description
ezs3
A typed, Path-like abstraction over boto3
for working with Amazon S3. Treat keys and prefixes the same way you treat
pathlib.Path objects — slash to compose, read_text/write_text to do I/O,
iterdir/glob/rglob to traverse — with proper type stubs and pathlib-style
exceptions.
import ezs3
bucket = ezs3.Bucket("my-bucket")
(bucket / "reports" / "today.json").write_text('{"ok": true}')
for path in bucket.rglob("*.json"):
print(path, path.read_text())
Table of contents
Installation
pip install ezs3
Credentials follow the standard boto3 resolution chain: environment variables,
~/.aws/credentials, instance role, etc. Override per-client when needed
(see Custom endpoint).
Usage
Clients and buckets
import ezs3
client = ezs3.Client() # default credentials
buckets = client.list_buckets() # list[ezs3.Bucket]
# Create / delete buckets (requires permission).
tmp = client.create_bucket("ezs3-tmp")
client.delete_bucket(tmp, force=True) # force=True empties first
# Reach an existing bucket without listing.
bucket = client.bucket("my-bucket") # local handle (no API call)
bucket = ezs3.Bucket("my-bucket") # equivalent, uses default client
Path composition
Slash composes paths the same way as pathlib.Path. The result is an
ezs3.S3Path (also exported as ezs3.Prefix and ezs3.Key):
prefix = bucket / "project" / "raw"
assert str(prefix) == "s3://my-bucket/project/raw"
key = prefix / "events.json"
assert key.name == "events.json"
assert key.suffix == ".json"
assert key.parent == prefix
S3Path may also be constructed directly. Free paths are not attached to any
bucket; attach them later when you know where they belong:
free = ezs3.Prefix("project/raw") # bucket is None
attached = free.attach(bucket) # now bound
# Other equivalent forms:
ezs3.Prefix(bucket, "project/raw")
ezs3.Prefix("my-bucket", "project/raw")
ezs3.Prefix("s3://my-bucket/project/raw")
Reading and writing
key = bucket / "config.json"
key.write_text('{"flag": true}')
key.write_bytes(b"\x00\x01\x02")
key.read_text() # -> str
key.read_bytes() # -> bytes
bucket.write_text("hello.txt", "hi") # bucket-level shortcut
bucket.read_text("hello.txt")
Existence checks
key.exists() # True if either a key or a prefix exists at this path
key.is_key() # True only if a key exists (== is_file alias)
key.is_prefix() # True if any object exists under this prefix (== is_dir alias)
Listing and globbing
for child in (bucket / "data").iterdir(): # one level deep
print(child)
for path in (bucket / "data").find(): # recursive, every key
print(path)
for path in bucket.glob("*.json", prefix="data"): # one level
...
for path in bucket.rglob("*.json", prefix="data"): # recursive
...
Deletion
key.remove() # delete a single key (== rm alias)
prefix.rmtree() # recursive delete
bucket.remove("a.txt", "b.txt", "c.txt") # batched DeleteObjects
bucket.clear() # empty the bucket
Custom endpoint (MinIO / LocalStack)
client = ezs3.Client(
endpoint_url="http://localhost:9000",
aws_access_key_id="minioadmin",
aws_secret_access_key="minioadmin",
region_name="us-east-1",
)
API at a glance
| Type | Purpose |
|---|---|
ezs3.Client |
Boto3 wrapper for credentials and bucket lifecycle. |
ezs3.Bucket |
Named bucket handle. Supports / and path-style helpers. |
ezs3.S3Path |
Path-like representation of a key or prefix. |
ezs3.Prefix, ezs3.Key |
Aliases for S3Path. Use whichever documents intent best. |
Full API reference is auto-generated from docstrings — see Building the docs.
Exceptions
Hierarchy mirrors pathlib. Every error inherits from both ezs3.S3Error and
the closest stdlib equivalent, so you can catch either:
| ezs3 exception | Stdlib parent |
|---|---|
IsAPrefixError |
IsADirectoryError |
NotAPrefixError |
NotADirectoryError |
S3KeyNotFoundError |
FileNotFoundError |
BucketNotFoundError |
FileNotFoundError |
BucketAlreadyExistsError |
FileExistsError |
PathNotAttachedError |
ValueError |
BucketMismatchError |
ValueError |
try:
prefix.read_text()
except ezs3.IsAPrefixError:
...
try:
key.iterdir()
except ezs3.NotAPrefixError:
...
Requirements
End users:
- Python ≥ 3.9.
- An S3-compatible service and credentials. AWS S3 works out of the box;
MinIO and LocalStack work
via
Client(endpoint_url=...).
Contributors additionally need:
- uv for dependency management.
- just as a task runner.
- Docker (only for integration tests, to run a local MinIO container).
Development
Clone and install the dev dependencies:
git clone https://github.com/nachollica/ezs3
cd ezs3
uv sync --all-groups
Running checks
just cc # lint + typecheck + unit tests
just lint # ruff check
just tc # mypy
just test # pytest (unit, excludes integration marker)
just fix # ruff format + autofix
just tox # run the test suite under every supported Python
Integration tests against MinIO
A local S3-compatible service is needed for the integration test marker.
This project bundles MinIO via Docker:
just s3-local-up # start MinIO on :9000 (console :9001)
just test-integration # run pytest -m integration
just s3-local-down # stop the container
Building the docs
API documentation is generated from Google-style docstrings using pdoc:
just docs # build into ./site/
just docs-serve # serve with live reload at http://localhost:8080
just docs-clean # rm -rf site/
Releasing
just build # uv build (wheel + sdist in dist/)
just publish # uv publish (requires UV_PUBLISH_TOKEN)
License
MIT.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ezs3-1.1.0.tar.gz.
File metadata
- Download URL: ezs3-1.1.0.tar.gz
- Upload date:
- Size: 151.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.18 {"installer":{"name":"uv","version":"0.11.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a17f6eeee1a7afbe4974d77d77effb5d5981922e929736e5084066db7022f2f0
|
|
| MD5 |
4b7042d6dd13890a86fc1fc6ffeb29e7
|
|
| BLAKE2b-256 |
7284cefbe86be1790471ce1c3c86fcd9db5f0736d10b085e9c919ebb741c41dd
|
File details
Details for the file ezs3-1.1.0-py3-none-any.whl.
File metadata
- Download URL: ezs3-1.1.0-py3-none-any.whl
- Upload date:
- Size: 29.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.18 {"installer":{"name":"uv","version":"0.11.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a36774d2d887a56b4e70c4374cffbdc2d87342e2df295eacdf5cd8b61b605811
|
|
| MD5 |
ca906c229b02da07fb60e465addaed39
|
|
| BLAKE2b-256 |
713c0a5479876e9d84a8cd7d865a528537a1ec364d783db776fa4c7cd3fe7d47
|