Metadata-Version: 2.5
Name: kasuvia-media-client
Version: 0.1.0
Summary: Shared internal client for talking to kasuvia-media-system (presigned upload, confirm-upload, delete) — used by every Kasuvia microservice so HTTP/auth/retry logic against the media system is implemented exactly once.
Project-URL: Homepage, https://github.com/jamesadewara/kasuvia-media-client
Author-email: James <jamesadewara1@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: internal-client,kasuvia,media,presigned-url,r2
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP :: Session
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27.2
Requires-Dist: pydantic>=2.0.0
Provides-Extra: test
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'test'
Requires-Dist: pytest>=8.0.0; extra == 'test'
Description-Content-Type: text/markdown

# kasuvia-media-client

Shared internal client for talking to `kasuvia-media-system`. Every Kasuvia
microservice that needs to upload/confirm/delete media goes through this
package instead of hand-rolling its own HTTP client — the goal is that a bug
in "how we auth to kasuvia-media-system" or "how we retry" is fixed in one
file, not four.

## Contents

- `client.py` — `MediaSystemClient`, async (`httpx.AsyncClient`). Use from
  FastAPI services: `kasuvia-store-management`, `kasuvia-cms`,
  `kasuvia-website-builder-system`.
- `sync_client.py` — `SyncMediaSystemClient`, sync (`httpx.Client`), identical
  method contract. Use from `kasuvia-auth` (Django, coded fully synchronously).
- `auth.py` — builds the `X-Internal-Service` header.
- `retry.py` — shared retry policy: 3 attempts, exponential backoff 1s/2s/4s,
  only on network failures and 502/503/504 (not on 4xx — those are permanent
  rejections, retrying wastes the attempt budget).
- `exceptions.py` — `MediaClientError` and subclasses (`MediaUploadError`,
  `PresignedUrlExpiredError`, `MediaValidationError`, `MediaNotFoundError`,
  `MediaServiceUnavailableError`), mapped from HTTP status codes.
- `types.py` — shared request/response models (`PresignedUrlRequest`,
  `PresignedUrlResponse`, `ConfirmUploadRequest`, `MediaAssetResponse`) and the
  `Context` constants used across every domain.

## Usage (async — FastAPI services)

```python
from kasuvia_media_client import MediaSystemClient, PresignedUrlRequest, ConfirmUploadRequest, Context

client = MediaSystemClient(
    base_url=settings.KASUVIA_MEDIA_SYSTEM_URL,
    internal_service_secret=settings.INTERNAL_SERVICE_SECRET,
)

presigned = await client.get_presigned_url(
    PresignedUrlRequest(
        filename=file.filename,
        content_type=file.content_type,
        business_id=business_id,
        context=Context.PRODUCT,
        entity_id=product_id,
    )
)
# ... client PUTs the file bytes directly to `presigned.presigned_url` ...
asset = await client.confirm_upload(
    ConfirmUploadRequest(
        storage_path=presigned.storage_path,
        business_id=business_id,
        original_filename=file.filename,
        content_type=file.content_type,
        size=file_size,
        context=Context.PRODUCT,
        entity_id=product_id,
    )
)
```

## Usage (sync — kasuvia-auth / Django)

Same shapes, no `await`:

```python
from kasuvia_media_client import SyncMediaSystemClient, PresignedUrlRequest, Context

client = SyncMediaSystemClient(
    base_url=settings.KASUVIA_MEDIA_SYSTEM_URL,
    internal_service_secret=settings.INTERNAL_SERVICE_SECRET,
)
presigned = client.get_presigned_url(
    PresignedUrlRequest(
        filename=filename,
        content_type=content_type,
        business_id=business_id,
        context=Context.USER_AVATAR,
        entity_id=user_id,
    )
)
```

## Development

```bash
pip install -e ".[test]"
pytest
```
