Объектное хранилище (ObjectStorage protocol + MinIO adapter + image normalization for Skillery)
Project description
s-storagekit
Объектное хранилище для Skillery: портабельное async-API для key→bytes операций с реализациями S3-compatible протокола (MinIO) и вспомогательными утилитами нормализации изображений (Pillow).
Установка
pip install s-storagekit>=0.1.0
Быстрый старт
Использование MinIO
from storagekit import MinioObjectStorage, ObjectNotFoundError
# Инициализация
storage = MinioObjectStorage(
endpoint="minio:9000",
access_key="minioadmin",
secret_key="minioadmin",
bucket="my-bucket",
secure=False, # http в dev, https в prod
)
# Запись объекта
await storage.put("avatars/user_1/photo.jpg", image_bytes, "image/jpeg")
# Чтение объекта
data, content_type = await storage.get("avatars/user_1/photo.jpg")
# Удаление объекта
await storage.delete("avatars/user_1/photo.jpg")
Обработка изображений
from storagekit import (
normalize_image,
normalize_square_avatar,
square_avatar_variants,
)
# Квадратный аватар одного размера
jpeg_bytes, ct = normalize_square_avatar(raw_image, size=256)
await storage.put(f"avatars/user_1/{token}.jpg", jpeg_bytes, ct)
# Несколько вариантов размеров (responsive)
variants = square_avatar_variants(raw_image, sizes=[64, 128, 256])
for size, blob in variants:
key = f"avatars/user_1/{token}_{size}.jpg"
await storage.put(key, blob, "image/jpeg")
# Масштабирование обложки (сохраняет пропорции)
cover_data, ct = normalize_image(raw_image, max_px=1200)
await storage.put(f"skills/42/cover_{token}.jpg", cover_data, ct)
Тестирование
import pytest
from storagekit.testing import InMemoryObjectStorage, fake_image_bytes
@pytest.fixture
def storage():
return InMemoryObjectStorage()
@pytest.mark.asyncio
async def test_avatar_upload(storage):
raw = fake_image_bytes(100, 100)
jpeg_bytes, _ = normalize_square_avatar(raw, size=256)
await storage.put("avatars/test.jpg", jpeg_bytes, "image/jpeg")
data, ct = await storage.get("avatars/test.jpg")
assert ct == "image/jpeg"
assert len(data) > 0
Presigned URL (без auth)
from storagekit import PresignedUrlGenerator
gen = PresignedUrlGenerator(
endpoint="minio:9000",
access_key="minioadmin",
secret_key="minioadmin",
bucket="my-bucket",
)
# Генерировать URL с TTL (по умолчанию 1 час)
url = await gen.generate_presigned_get_url("avatars/user_1/photo.jpg")
# Клиент может скачать файл по этому URL без токена
API
Протоколы
class ObjectStorage(Protocol):
"""Async key→bytes хранилище."""
async def put(self, key: str, data: bytes, content_type: str) -> None:
"""Записать/перезаписать объект."""
...
async def get(self, key: str) -> tuple[bytes, str]:
"""Вернуть (data, content_type). Бросает ObjectNotFoundError."""
...
async def delete(self, key: str) -> None:
"""Удалить объект. Отсутствие не ошибка (idempotent)."""
...
Исключения
class ObjectNotFoundError(Exception):
"""Объект не найден. Имеет атрибут .key."""
def __init__(self, key: str) -> None: ...
Реализации
# S3-compatible хранилище (prod/dev)
class MinioObjectStorage:
def __init__(
self,
*,
endpoint: str, # host:port БЕЗ схемы
access_key: str,
secret_key: str,
bucket: str,
secure: bool = False, # http (dev) vs https (prod)
) -> None: ...
Image-утилиты
# Список допустимых MIME-типов входных файлов
ALLOWED_IMAGE_CONTENT_TYPES: frozenset[str]
# Квадратный аватар (center cover-crop) → JPEG
def normalize_square_avatar(data: bytes, *, size: int) -> tuple[bytes, str]: ...
# Несколько вариантов размеров одного аватара
def square_avatar_variants(
data: bytes, *, sizes: list[int]
) -> list[tuple[int, bytes]]: ...
# Масштабирование по длинной стороне (сохраняет пропорции)
def normalize_image(data: bytes, *, max_px: int) -> tuple[bytes, str]: ...
Утилиты
# Канонический ключ snapshot'а версии
def snapshot_key(*, skill_id: int | str, semver: str) -> str:
"""Возвращает f'skills/{skill_id}/versions/{semver}.tar.gz'."""
Тестирование
# Фейк-хранилище в памяти
class InMemoryObjectStorage:
async def put(self, key: str, data: bytes, content_type: str) -> None: ...
async def get(self, key: str) -> tuple[bytes, str]: ...
async def delete(self, key: str) -> None: ...
# Генерировать тестовый PNG-файл
def fake_image_bytes(width: int = 100, height: int = 100) -> bytes: ...
Параметризация
| Параметр | Значение | Примечание |
|---|---|---|
| MinIO endpoint | host:port (БЕЗ схемы) |
dev: minio:9000, prod: minio.example.com:9000 |
| MinIO secure | False (dev), True (prod) |
http vs https |
| Avatar sizes | [64, 128, 256] (default) |
Можно переопределить в клиенте |
| Skill cover max px | 1200 (constant) |
Не параметризуется |
| JPEG quality | 85 (constant) |
Не параметризуется |
Зависимости
- minio>=7.0 — S3 SDK
- anyio>=3.0 — async threading
- Pillow>=10.0 — image processing
Лицензия
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
s_storagekit-0.1.0.tar.gz
(9.7 kB
view details)
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 s_storagekit-0.1.0.tar.gz.
File metadata
- Download URL: s_storagekit-0.1.0.tar.gz
- Upload date:
- Size: 9.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.27 {"installer":{"name":"uv","version":"0.9.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":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 |
27dca9659a4c8950aeca698bc49ffb9e6ce64e236294356be0d4a2d274175f03
|
|
| MD5 |
2b58b14205624024bdc5fa48e5f156aa
|
|
| BLAKE2b-256 |
eab872718faeb184485cffd95302c308defe627a80aecf27986158a11152fa2f
|
File details
Details for the file s_storagekit-0.1.0-py3-none-any.whl.
File metadata
- Download URL: s_storagekit-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.27 {"installer":{"name":"uv","version":"0.9.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":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 |
e68ecc9f143bca0fd1bd2e9fb7054508e528a876f001c06ac85d154aa38c5794
|
|
| MD5 |
f726178a2c70e102b64bda272f597848
|
|
| BLAKE2b-256 |
dab683e00eef463e0dd64727cc0a32c415dc80dadcae80b9c541603637b609b2
|