Skip to main content

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

ezs3-1.1.0.tar.gz (151.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ezs3-1.1.0-py3-none-any.whl (29.8 kB view details)

Uploaded Python 3

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

Hashes for ezs3-1.1.0.tar.gz
Algorithm Hash digest
SHA256 a17f6eeee1a7afbe4974d77d77effb5d5981922e929736e5084066db7022f2f0
MD5 4b7042d6dd13890a86fc1fc6ffeb29e7
BLAKE2b-256 7284cefbe86be1790471ce1c3c86fcd9db5f0736d10b085e9c919ebb741c41dd

See more details on using hashes here.

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

Hashes for ezs3-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a36774d2d887a56b4e70c4374cffbdc2d87342e2df295eacdf5cd8b61b605811
MD5 ca906c229b02da07fb60e465addaed39
BLAKE2b-256 713c0a5479876e9d84a8cd7d865a528537a1ec364d783db776fa4c7cd3fe7d47

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page