Как работать с объектным хранилищем на Python

Файлы в проекте можно хранить разными способами: локально на компьютере, в базе данных или S3-хранилище (объектное хранилище). Последнее — одно из самых популярных решений. Оно отличается надежностью и масштабируемостью. Использовать S3 можно не только в личных целях, но и для решения бизнес-задач. Для специалиста навык работы с объектным хранилищем востребован. Он поможет быстрее дойти до следующего уровня в карьере.
Под катом расскажем о преимуществах S3, научимся загружать и получать файлы, сверстаем небольшой сайт с его использованием!
Используйте навигацию, если не хотите читать текст полностью:
→ Немного об S3
→ Переходим к практике
→ Создаем объектное хранилище
→ Тестируем код/a>
→ Создаем приватный контейнер
→ Заключение
Немного об S3
Если вы слышали, что S3-хранилище — только про Amazon и использование за границей, то это не так. Изначально компания AWS разработала технологию как API для простого доступа к объектам с помощью уникальных URL по HTTP или HTTPS. Однако на рынке уже давно представлены отечественные решения — например, объектное хранилище Selectel.
Технология поддерживает любой тип данных. Вы можете хранить архивные логи, документы, фото и видео — все, что есть на вашем сайте. S3 подходит и в качестве приватного хранилища данных пользователей.
Один из популярных сценариев использования S3 — бэкапирование. Здесь особую роль играет надежность: как только вы загружаете файл в хранилище, он перемещается сразу в три дата-центра. Следовательно, вы не потеряете свои данные даже в случае сбоя одного из них. Рассмотрим другие преимущества технологии.
- Практически бесконечный объем хранилища — вы можете хранить вплоть до нескольких петабайт данных.
- Автоматическое масштабирование. В случае резкого всплеска трафика ресурсы мгновенно пополнятся. Так можно параллельно работать с большим количеством пользователей.
- Разграничение доступов. Для каждого контейнера можно задать список пользователей и команд с различными правами доступа — чтение или чтение и запись.

Переходим к практике
Напишем небольшой скрипт на Python. Позже вы сможете переиспользовать его в любых проектах. Скрипт подключается к объектному хранилищу и работает с файлами.
1. Для начала установим зависимости. Используем одну из самых популярных асинхронных библиотек для работы с S3 — aio-botocore.
pip install aiobotocore
Если вам интересен синхронный вариант, вы пишете на Django или Flask, используйте botocore. В случае с асинхронным кодом оставляйте aio-botocore. Реализация у них идентичная.
2. Далее создадим класс. Назвать его можно как угодно, в нашем случае — S3Client. Это будет универсальный код, не привязанный к конкретной реализации S3 или провайдеру.
class S3Client:
def __init__(
self,
access_key: str,
secret_key: str,
endpoint_url: str,
bucket_name: str,
):
Рассмотрим переменные, которые будем принимать при инициализации проекта:
- ключ доступа,
- Secret Key,
- Access Key,
- Endpoint URL (адрес нашего хранилища),
- Bucket Name.
Среди переменных Secret — это некое «имя пользователя», а Access Key — пароль.
3. Создадим некоторую переменную Config (но вы можете назвать ее как угодно). Здесь дадим несколько ключей для подключения.
self.config = {
"aws_access_key_id": access_key,
"aws_secret_access_key": secret_key,
"endpoint_url": endpoint_url,
}
self.bucket_name = bucket_name
self.session = get_session()
Не обращайте внимания, что переменные в библиотеке называются AWS (Amazon Web Services). Использовать их можно с любым хранилищем. Bucket Name сохраняем в переменной на всякий случай. Чтобы можно было работать с нашим хранилищем, добавим сессию get_session — ее импортируем из библиотеки aio-botocore, которую скачали ранее.
4. Создадим подключение Client. Здесь будем использовать уже знакомую get_session — в контекстном менеджере выбираем функцию Create Client.

Выбор функции Create Client в контекстном менеджере.
Имя задаем S3, раскрываем конфигурационный файл **self.config. Чтобы можно было использовать конструкцию get_client как контекстный менеджер, навесим декоратор asynccontextmanager. Он уже встроен в Python — импортируем из библиотеки context lib.
@asynccontextmanager
async def get_client(self):
async with self.session.create_client("s3", **self.config) as client:
yield client
5. Осталось сделать асинхронную функцию для загрузки файла. Принимать будем несколько параметров. Во-первых — адрес файла. Конечно, на практике вы вряд ли встретитесь с локальным хранением, но так как в нашей реализации нет фронтенда, клиентов и API, пока оставим так. Во-вторых — название, Object Name.
async def upload_file(
self,
file_path: str,
):
6. Откроем контекстный менеджер. Здесь уже ничего не нужно передавать — весь конфиг мы прописали ранее, дальше можем переиспользовать get_client для загрузки, скачивания, удаления и прочих операций. Так как работаем с локальными файлами, естественно, их тоже нужно открывать через контекстный менеджер. Используем read binary mode, rb. Указываем бакет, а Object Name задаем сами. Бакет — это сущность для хранения объектов в S3.
object_name = file_path.split("/")[-1] # /users/artem/cat.jpg
try:
async with self.get_client() as client:
with open(file_path, "rb") as file:
await client.put_object(
Bucket=self.bucket_name,
Key=object_name,
Body=file,
)
Но что значит put в client.put_object? Возможно, если вы пишете API, то уже сталкивались с этим понятием. Put — это изменение. Важно понимать, что в S3 все операции являются перезаписью. Если пишете новый файл, он добавится в систему. Если же файл уже существует и вы пишете put_object, он перезапишется новой версией. Исходный файл во втором сценарии будет утерян.
Создаем объектное хранилище
Пора создать объектное хранилище и получить ключи для авторизации.
1. В панели управления заходим во вкладку Объектное хранилище → Создать контейнер. Имя зададим test-public-bucket1, но вы можете выбрать любое другое. Тип можно выбрать приватный или публичный. Если вы хотите, чтобы ни у кого из интернета не было прямого доступа до ваших файлов — выбирайте приватный. Мы пока рассмотрим публичный.
2. Переходим к классу хранения. Если речь идет о бэкапах, архивных логах или данных, к которым вы редко возвращаетесь, но они очень ценны — выбирайте холодное хранение. Стандартное обычно выбирается для сервисов, где идет постоянная загрузка файлов, их просмотр и т. д. Нажимаем Создать контейнер.

Контейнер создается за несколько секунд. Но работать с ним пока можно только из браузера.
3. Чтобы взаимодействовать по API, следует создать сервисного пользователя. Переходим в Управление доступом → Сервисные пользователи → Добавить пользователя. Задаем имя пользователя и генерируем пароль, в поле Роль выбираем Администратор объектного хранилища. Выбираем наш проект из списка и нажимаем Добавить пользователя.

4. Получим ключи. Во вкладке Управление пользователями выбираем нужного и в поле S3 ключи нажимаем Добавить ключ. В окне Добавление S3 ключа выбираем наш проект и нажимаем Сгенерировать.

Окно добавления S3 ключа.

Полученные ключи.
5. Ключи копируем и добавляем в проект. Чтобы обращаться к бакету, указываем endpoint_url — s3.storage.selcloud.ru. Задаем bucket-name — в нашем случае это test-public-bucket1. Создаем асинхронную функцию main, в которой создаем объект хранилища S3Client. Загружаем файл test.txt и указываем относительный путь — await S3_client.upload_file («test.txt»).
async def main():
s3_client = S3Client(
access_key="",
secret_key="",
endpoint_url="",
bucket_name="",
)
# Проверка, что мы можем загрузить, скачать и удалить файл
await s3_client.upload_file("test.txt")
await s3_client.get_file("test.txt", "text_local_file.txt")
await s3_client.delete_file("test.txt")
if __name__ == "__main__":
asyncio.run(main())
Тестируем код
Переходим к тестированию нашего кода — запускаем скрипт. Проверим, есть ли изменения в объектах. В контейнере должен отображаться новый файл. Здесь же можно получить ссылку на объект.


Помимо прочего, из панели управления вы можете проставить лимиты. С помощью них можно ограничить объем данных, которые загружаются в хранилище.

Раздел «Лимиты» во вкладке объектного хранилища.
Посмотрим, как загружать файлы из S3 на сайте. Будем использовать простейший одностраничный сайт, исключительно для проверки функций. Создаем файл index.html и выбираем пятую версию html. Положим сюда любой объект из хранилища, в качестве примера используем изображение и видеофайл.
Document
Переходим на сайт. Видим, что файлы пришли из S3-хранилища. Видеофайл jetski.mov на 212 МБ присылается чанками (порционно). Это помогает S3 не сталкиваться с нагрузкой из-за большого трафика.

Панель администратора на созданном сайте.
Создаем приватный контейнер
Вернемся к выбору типа контейнера в панели управления. В этот раз создадим приватный контейнер с холодным классом хранения. Будем использовать его для бэкапов. Это хранилище, к которому мы также можем обращаться по API, например, через скрипт для резервного копирования. О том, как выбрать тип бэкапа — в обзоре.

Создание приватного контейнера с холодным хранением в панели управления.
Ключевое отличие от созданного ранее контейнера в том, что никто не сможет получить доступ к этому бакету. Здесь нет публичной ссылки и мы не можем расшарить файл, потому что это приватное хранилище. Но мы можем сгенерировать одноразовую ссылку и наложить на нее временные ограничения.

Настройки доступа к файлу в приватном контейнере.

Заключение
Мы поработали с приватными и публичными бакетами, поняли, как загружать и получать данные, как их отображать на сайте. Одно из важных преимуществ объектного хранилища, которое можно рассмотреть в заключение статьи — цена является его цена.
Допустим, у нас есть маркетплейс на 10 000 клиентов, где мы храним 1 ГБ карточек товаров в сжатом виде. Каждый клиент смотрит по 30 фотографий — суммарно уходит 10 ГБ исходящего трафика в месяц. При таких показателях стоимость будет около 24 ₽/мес.

Естественно, аренда объектного хранилища гораздо выгоднее, чем построение надежной и масштабируемой системы своими силами. В случае с бэкапами и холодным хранением цены, как правило, будут еще ниже. Рассчитать стоимость аренды объектного хранилища S3 вы можете на сайте.
Демонстрация работы S3-хранилища и другие подробности о его функциях — в видео
Автор: Артем Шумейко, автор канала на YouTube.