Python¶
Use boto3. The compatibility suite pins boto3==1.43.77 and runs it against a real
Record Store server.
Client¶
import boto3
s3 = boto3.client(
"s3",
endpoint_url="https://storage.example.com",
aws_access_key_id="<service account access key>",
aws_secret_access_key="<service account secret key>",
region_name="us-east-1",
config=boto3.session.Config(s3={"addressing_style": "path"}),
)
addressing_style="path" is required. Region is arbitrary but must be consistent —
SigV4 signs it.
Prefer environment variables
Read credentials from AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY or your own
variables rather than committing them.
Buckets¶
s3.create_bucket(Bucket="uploads")
for bucket in s3.list_buckets()["Buckets"]:
print(bucket["Name"])
Upload¶
s3.put_object(
Bucket="uploads",
Key="invoices/2026/03/inv-1.pdf",
Body=open("inv-1.pdf", "rb"),
ContentType="application/pdf",
)
upload_file handles large objects with multipart automatically:
Download¶
Or stream it:
response = s3.get_object(Bucket="uploads", Key="invoices/2026/03/inv-1.pdf")
for chunk in response["Body"].iter_chunks():
...
List¶
paginator = s3.get_paginator("list_objects_v2")
for page in paginator.paginate(Bucket="uploads", Prefix="invoices/2026/"):
for obj in page.get("Contents", []):
print(obj["Key"], obj["Size"])
Use the paginator. Listing is always bounded.
Delete¶
Presigned URLs¶
url = s3.generate_presigned_url(
"put_object",
Params={"Bucket": "uploads", "Key": key, "ContentType": "application/pdf"},
ExpiresIn=900,
)
Sign server-side only. See Presigned URLs.
Versioning¶
s3.put_bucket_versioning(
Bucket="uploads",
VersioningConfiguration={"Status": "Enabled"},
)
versions = s3.list_object_versions(Bucket="uploads")
Ranges¶
Checksums¶
If uploads fail with NotImplemented, boto3 is using AWS's aws-chunked trailing
checksums. Turn that off: