Metadata-Version: 2.4
Name: opengater-storage-client
Version: 0.2.0
Summary: Client library for the opengater-storage service
Project-URL: Homepage, https://gitlab.com/toltol1992toltol/opengater-storage-client
Author: Opengater
License: MIT
Keywords: httpx,presigned,s3,storage
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.11
Requires-Dist: httpx>=0.27.0
Requires-Dist: pydantic<3.0.0,>=2.0.0
Description-Content-Type: text/markdown

# opengater-storage-client

Тонкий async-клиент к сервису **opengater-storage** — инкапсулирует `POST /uploads` (получение presigned-PUT URL) и `DELETE /objects` (удаление файла). Аутентификация через заголовок `X-API-Key`.

По структуре аналогичен `opengater-globalauth-client`: без FastAPI, без брокера, только httpx + pydantic.

---

## Установка

После публикации на PyPI:

```bash
uv add opengater-storage-client
```

На время разработки — git-зависимость на тег:

```bash
uv add "opengater-storage-client @ git+https://gitlab.com/toltol1992toltol/opengater-storage-client.git@v0.1.0"
```

---

## Быстрый старт

```python
from opengater_storage_client import StorageClient

storage = StorageClient(base_url="http://opengater-storage:8000", api_key="...")

# Создать загрузку — получить presigned-PUT URL
res = await storage.create_upload("image/png", prefix="news/articles")
# res.upload_url — PUT сюда файл напрямую из браузера/клиента
# res.key        — ключ объекта в хранилище
# res.public_url — публичный URL после загрузки

# Удалить объект (по public_url или по key)
await storage.delete_object(public_url=res.public_url)
# или
await storage.delete_object(key=res.key)

await storage.aclose()
```

---

## API

### `StorageClient(base_url, api_key, timeout=30.0)`

| Параметр   | Тип     | По умолчанию | Описание                            |
|------------|---------|--------------|-------------------------------------|
| `base_url` | `str`   | обязательный | Адрес сервиса opengater-storage     |
| `api_key`  | `str`   | обязательный | Значение заголовка `X-API-Key`      |
| `timeout`  | `float` | `30.0`       | Таймаут HTTP-запросов (в секундах)  |

### `create_upload(content_type, prefix="") -> UploadResult`

Отправляет `POST /uploads`. Возвращает `UploadResult` с полями:

| Поле         | Тип   | Описание                              |
|--------------|-------|---------------------------------------|
| `upload_url` | `str` | Presigned-PUT URL — именно сюда грузить файл |
| `key`        | `str` | Ключ объекта в хранилище              |
| `public_url` | `str` | Публичный URL после загрузки          |

### `upload_file(content, filename, content_type, prefix="") -> StoredObject`

Отправляет `POST /uploads/file` multipart-запросом. Файл загружается на сервер напрямую — без промежуточного presigned-URL. Возвращает `StoredObject`:

| Поле         | Тип   | Описание                        |
|--------------|-------|---------------------------------|
| `key`        | `str` | Ключ объекта в хранилище        |
| `public_url` | `str` | Публичный URL загруженного файла |

```python
res = await storage.upload_file(content=b"...", filename="pic.png", content_type="image/png", prefix="news/articles")
# res.key / res.public_url  (StoredObject — без upload_url)
```

### `delete_object(public_url=None, key=None) -> None`

Отправляет `DELETE /objects`. Нужно передать хотя бы один из параметров; если не передан ни один — поднимает `ValueError`.

> **Важно:** при удалении по `public_url` сервис сам срезает URL до ключа по своей базе. Передавать `key` надёжнее, если он у вас есть.

### `aclose() -> None`

Закрывает внутренний `httpx.AsyncClient`. Вызывайте по завершении работы (или используйте клиент через `async with`).

---

## Ошибки

Все ошибки — наследники `StorageError`. Библиотека честно поднимает их — **best-effort-политику (например, глушить удаление чужого URL) реализует потребитель**.

| Класс                    | Когда                                  |
|--------------------------|----------------------------------------|
| `StorageError`           | База; неожиданный HTTP-статус          |
| `StorageBadRequest`      | HTTP 400                               |
| `StorageAuthError`       | HTTP 401 / 403                         |
| `StoragePayloadTooLarge` | HTTP 413 (файл превышает лимит)        |
| `StorageUnavailable`     | HTTP 5xx, таймаут, сетевая ошибка      |

Атрибуты `.status_code` (`int | None`) и `.detail` (`str | None`) доступны на всех экземплярах.

```python
from opengater_storage_client import StorageClient, StorageAuthError, StorageUnavailable

try:
    res = await storage.create_upload("image/jpeg")
except StorageAuthError as e:
    # неверный ключ
    raise
except StorageUnavailable as e:
    # сервис недоступен или таймаут — логируйте e.detail
    raise
```
