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
Metadata
Release files for s-storagekit 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| s_storagekit-0.2.0.tar.gz | 49.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| s_storagekit-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 54.8 kB
Release files / s_storagekit-0.2.0.tar.gz
| Download URL | s_storagekit-0.2.0.tar.gz |
|---|---|
| Size | 49.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
313d8cb72369227c00270055032953952de25d013e6ba54e0b93eb270d24b761
|
|
BLAKE2b-256 checksum How to use checksums |
fbc41c90cd70c01ab634ae4bdcfed526aa78ff2e0cf3a4b7c5ec368b6ed339da
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / s_storagekit-0.2.0-py3-none-any.whl
| Download URL | s_storagekit-0.2.0-py3-none-any.whl |
|---|---|
| Size | 5.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
51294ce20ca2ea16f99b6a8c131c1fb09690ecb968186c166202c6a5f75b2475
|
|
BLAKE2b-256 checksum How to use checksums |
f9f5b41fd2f05450a53cf204b78c3fc82be43cb8b0d9d5f0b025141f251c3fec
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|