Metadata-Version: 2.4
Name: lztmarket
Version: 0.1.0
Summary: A modern, strongly typed sync and async client for the LOLZTEAM Market API
Keywords: lolzteam,lzt,market,api,async,pydantic,httpx
Author: Sterrist
Author-email: Sterrist <griodred@gmail.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Dist: httpx>=0.28.1,<1
Requires-Dist: pydantic>=2.11,<3
Requires-Python: >=3.14
Description-Content-Type: text/markdown

# lztmarket

> Неофициальный клиент. Проект не связан с владельцами LOLZTEAM/LZT Market и не
> поддерживается ими.

Современный синхронный и асинхронный Python-клиент для
[LOLZTEAM Market API](https://lzt-market.readme.io/reference/information).

Главный принцип библиотеки: публичные методы возвращают Pydantic-объекты, а не
неструктурированные JSON-словари. Например, цена доступна как `item.price` (`Decimal`),
продавец — как `item.seller.username`, а информация о лимите — как
`response.metadata.rate_limit.remaining`.

## Возможности

- Python 3.14, Pydantic 2 и HTTPX;
- синхронный `LZTMarket` и асинхронный `AsyncLZTMarket` с одинаковой структурой;
- типизированные доменные модели, request-модели и enum;
- безопасное хранение токена через `SecretStr` и отсутствие токена в `repr`;
- стандартная иерархия ошибок для 401, 403, 404, 429, транспорта и валидации ответа;
- metadata из rate-limit заголовков на каждом корневом response-объекте;
- retry с exponential backoff только для идемпотентных запросов;
- автоматическая пагинация;
- внедрение собственного `httpx.Client`/`AsyncClient` для прокси, observability и тестов;
- типизированный escape hatch для новых методов API без возврата raw JSON.

## Установка

```bash
pip install lztmarket
```

Для разработки:

```bash
uv sync --all-groups
```

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

```python
from decimal import Decimal

from lztmarketapi import CategoryName, LZTMarket, OrderBy, SearchParams

with LZTMarket("YOUR_ACCESS_TOKEN", locale="ru-RU") as market:
    response = market.categories.search(
        CategoryName.STEAM,
        SearchParams(
            pmin=Decimal("1000"),
            pmax=Decimal("5000"),
            order_by=OrderBy.PRICE_ASC,
            tag_ids=[12, 34],
        ),
    )

    for item in response.items:
        print(item.item_id, item.title, item.price)
        if item.seller is not None:
            print(item.seller.username)

    if response.metadata and response.metadata.rate_limit:
        print(response.metadata.rate_limit.remaining)
```

Категорийные фильтры, специфичные для Steam, Telegram, Riot и других категорий,
можно передать как дополнительные поля `SearchParams`. Pydantic сохранит их, а клиент
сериализует вместе с типизированными общими фильтрами:

```python
params = SearchParams.model_validate(
    {
        "pmin": "500",
        "game[]": [730],
        "hours_played[730]": 100,
    }
)
response = market.categories.search(CategoryName.STEAM, params)
```

## Асинхронный клиент

```python
import asyncio

from lztmarketapi import AsyncLZTMarket


async def main() -> None:
    async with AsyncLZTMarket("YOUR_ACCESS_TOKEN") as market:
        response = await market.items.get(123456789)
        print(response.item.title)


asyncio.run(main())
```

Асинхронная пагинация не загружает все страницы в память:

```python
async for item in market.categories.iter_items(max_pages=3):
    print(item.item_id)
```

## Создание и изменение лота

```python
from decimal import Decimal

from lztmarketapi import (
    AddItemRequest,
    CategoryID,
    Currency,
    EditItemRequest,
    ItemOrigin,
)

created = market.items.add(
    AddItemRequest(
        title="Steam account",
        price=Decimal("2500"),
        category_id=CategoryID.STEAM,
        currency=Currency.RUB,
        item_origin=ItemOrigin.PERSONAL,
        information="login:password",
    )
)

market.items.edit(
    created.item.item_id,
    EditItemRequest(price=Decimal("2400"), allow_ask_discount=True),
)
```

## Обработка ошибок

```python
from lztmarketapi import APIError, RateLimitError, ResponseValidationError

try:
    item = market.items.get(123)
except RateLimitError as error:
    print(error.retry_after)
except APIError as error:
    print(error.status_code, error.error)
except ResponseValidationError:
    # Контракт ответа API изменился или сервер вернул повреждённые данные.
    ...
```

POST-запросы автоматически не повторяются: это предотвращает случайную повторную
покупку, публикацию или денежный перевод. GET, PUT и DELETE повторяются при 429 и
временных 5xx согласно `RetryConfig`.

## Новые и редкие endpoints

Группы `categories`, `items`, `profile`, `cart`, `purchasing`, `payments`, `proxies`
и `general` покрывают основные пользовательские сценарии. Если официальный API добавил
маршрут раньше обновления клиента, используйте типизированный escape hatch:

```python
from lztmarketapi import StatusResponse

result = market.general.request(
    "POST",
    "/123456789/change-password",
    StatusResponse,
    json={"cancel": False},
)
print(result.message)  # известные поля типизированы
print(result.model_extra)  # новые поля API не теряются
```

Путь обязан быть относительным к Market API. Полные внешние URL запрещены, чтобы токен
нельзя было случайно отправить стороннему хосту.

## Локальная проверка

```bash
uv run pytest
uv run ruff check .
uv run mypy src tests
uv build
```

Контракт сверялся с официальной
[документацией Market API](https://lzt-market.readme.io/reference/information) и
актуальной OpenAPI 3.1 схемой версии `1.1.100` (126 операций на момент разработки).

## Лицензия

Исходный код распространяется по лицензии MIT. Полный текст находится в файле
`LICENSE`.
