Хранение и резервные копии

Python (boto3)

2 мин чтения

boto3 — официальный AWS SDK для Python. Примеры рассчитаны на Python 3.9+ и boto3 1.36+.

Установка

pip install "boto3>=1.36"

Ключи доступа

Передавайте ключи через переменные окружения — SDK прочитает их автоматически. Никогда не храните ключи в исходном коде и в репозитории.

export AWS_ACCESS_KEY_ID=<Access Key>
export AWS_SECRET_ACCESS_KEY=<Secret Key>

Создание клиента

import boto3
from botocore.config import Config

s3 = boto3.client(
    "s3",
    endpoint_url="https://s3.uzcstorage.uz",
    region_name="default",
    config=Config(
        signature_version="s3v4",
        s3={"addressing_style": "path"},
        request_checksum_calculation="when_required",
        response_checksum_validation="when_required",
        retries={"max_attempts": 5, "mode": "standard"},
    ),
)
BUCKET = "my-bucket"

Основные операции

# Upload a file
s3.upload_file("report.pdf", BUCKET, "docs/report.pdf",
               ExtraArgs={"ContentType": "application/pdf"})

# Upload bytes or a string
s3.put_object(Bucket=BUCKET, Key="docs/hello.txt", Body=b"Hello, UzCloud!")

# Download a file
s3.download_file(BUCKET, "docs/report.pdf", "report-copy.pdf")

# Read an object into memory (small objects only)
text = s3.get_object(Bucket=BUCKET, Key="docs/hello.txt")["Body"].read().decode()

# Copy and delete
s3.copy_object(Bucket=BUCKET, Key="archive/report.pdf",
               CopySource={"Bucket": BUCKET, "Key": "docs/report.pdf"})
s3.delete_object(Bucket=BUCKET, Key="docs/report.pdf")

# Delete up to 1,000 objects in one request
s3.delete_objects(Bucket=BUCKET, Delete={"Objects": [{"Key": "tmp/a.txt"}, {"Key": "tmp/b.txt"}]})

Список всех объектов

Один запрос возвращает не больше 1 000 объектов. Чтобы получить все объекты с префиксом, используйте пагинатор:

paginator = s3.get_paginator("list_objects_v2")
for page in paginator.paginate(Bucket=BUCKET, Prefix="docs/"):
    for obj in page.get("Contents", []):
        print(obj["Key"], obj["Size"], obj["LastModified"])

Большие файлы

upload_file и download_file делят большие файлы на части и передают их параллельно. Размер части и число потоков настраиваются через TransferConfig:

from boto3.s3.transfer import TransferConfig

MB = 1024 * 1024
transfer = TransferConfig(multipart_threshold=64 * MB, multipart_chunksize=64 * MB, max_concurrency=8)

def progress(sent_bytes):
    print(f"+{sent_bytes} bytes")

s3.upload_file("backup.tar.gz", BUCKET, "backups/backup.tar.gz",
               Config=transfer, Callback=progress)

Потоковая передача

Чтобы не загружать большой объект в память целиком, читайте и записывайте его частями:

# Upload from any file-like object (for example, an uploaded file in a web app)
with open("video.mp4", "rb") as f:
    s3.upload_fileobj(f, BUCKET, "media/video.mp4", Config=transfer)

# Download in 1 MB chunks
body = s3.get_object(Bucket=BUCKET, Key="media/video.mp4")["Body"]
with open("video-copy.mp4", "wb") as f:
    for chunk in body.iter_chunks(chunk_size=MB):
        f.write(chunk)

Presigned URL

Presigned URL дает временный доступ к объекту без передачи ключей. Ссылка на загрузку позволяет браузеру или другому сервису загрузить файл прямо в бакет — см. Presigned URL и CORS.

# Download link, valid for 1 hour
url = s3.generate_presigned_url("get_object",
                                Params={"Bucket": BUCKET, "Key": "docs/report.pdf"},
                                ExpiresIn=3600)

# Upload link, valid for 10 minutes. The client must send the same Content-Type.
upload_url = s3.generate_presigned_url("put_object",
                                       Params={"Bucket": BUCKET, "Key": "uploads/photo.jpg",
                                               "ContentType": "image/jpeg"},
                                       ExpiresIn=600)

Ошибки и повторные попытки

Самые частые ошибки — отсутствующий объект (NoSuchKey или 404 для HeadObject) и нет доступа (AccessDenied, 403). Сетевые ошибки и ответы 5xx повторяются автоматически; число попыток задается при создании клиента.

from botocore.exceptions import ClientError

def object_exists(key: str) -> bool:
    try:
        s3.head_object(Bucket=BUCKET, Key=key)
        return True
    except ClientError as e:
        if e.response["Error"]["Code"] in ("404", "NoSuchKey"):
            return False
        raise

try:
    s3.get_object(Bucket=BUCKET, Key="missing.txt")
except ClientError as e:
    code = e.response["Error"]["Code"]
    if code == "NoSuchKey":
        print("Object not found")
    elif code == "AccessDenied":
        print("Check the keys and the bucket policy")
    else:
        raise

Совет

Для проектов на Django используйте django-storages вместо прямых вызовов boto3.

Совет

См. также: