English · Русский
pylzt
Типизированный async-фреймворк над API lzt.market / lolzteam / AntiPublic — не тонкая обёртка над HTTP
Документация · Для AI-агентов · Гайд по интеграции
Установка
pip install pylzt
Свежий main вместо релиза — pip install "git+https://github.com/open-lzt/pylzt.git".
Python 3.12+. Зависимости: pydantic>=2.7, httpx[socks]>=0.27, structlog>=24.1.
Быстрый старт
import asyncio
from pylzt import Client
from pylzt.models.lot import LotFilter
from pylzt.types import Category
async def main() -> None:
async with Client(["<market-token>"]) as client:
lot = await client.market.get_lot(item_id=42)
print(lot.item_id, lot.price, lot.title)
async for lot in client.market.list_lots(LotFilter(category=Category.STEAM)):
print(lot.item_id, lot.price)
asyncio.run(main())
Три доменных неймспейса: client.market · client.forum · client.antipublic. Каждый эндпоинт официальной спеки — реальный метод на своём неймспейсе (client.forum.threads_get(...), client.antipublic.license_check_license()).
Токены можно не передавать в конструктор — они читаются из LZT_TOKENS.
Sync и async — один движок
SyncClient — не вторая реализация рейт-лимитов и ретраев. Он крутит тот же async-движок на фоновом потоке с event-loop (sync/runner.py), и типы возврата совпадают с async-аналогами под mypy --strict.
async with Client(["<market-token>"]) as client:
lot = await client.market.get_lot(item_id=42)
from pylzt.sync.client import SyncClient
with SyncClient(["<market-token>"]) as client:
lot = client.market.get_lot(item_id=42) # без await
Пагинация
from decimal import Decimal
from pylzt.models.lot import LotFilter
from pylzt.types import Category, OrderBy
filt = LotFilter(category=Category.STEAM, pmax=Decimal("500"), order_by=OrderBy.PRICE_ASC)
async for lot in client.market.list_lots(filt, max_pages=5):
...
all_lots = await client.market.list_lots(filt).collect(limit=200) # в список
first = await client.market.list_lots(filt).first_page() # только первая страница
Батчинг N вызовов в один запрос
Три входа — выбирайте по тому, как вызовы возникают в вашем коде.
from pylzt.methods.catalog import GetLot
from pylzt.methods.categories import CategoryParams
from pylzt.types import Category, ItemId
# 1. Список известен заранее — один POST /batch.
results = await client.execute_batch([
GetLot(item_id=ItemId(1)),
CategoryParams(category=Category.STEAM),
])
# 2. Вызовы разбросаны по функции — оберните участок, каждый execute() внутри
# склеится в /batch вместо отдельного запроса.
async with client.batching():
lot, categories = await asyncio.gather(
client.execute(GetLot(item_id=ItemId(1))),
client.execute(CategoryParams(category=Category.STEAM)),
)
# 3. Оборачивать нечего (вызовы из несвязанных мест) — job() склеивает со всеми
# другими параллельными job() через общий сборщик на время жизни клиента.
lot = await client.job(GetLot(item_id=ItemId(1)))
Загрузка файлов
from pylzt import Media
avatar = Media.from_path("avatar.png")
await client.forum.users_avatar_upload(user_id="me", avatar=avatar)
Опциональный кэш байтов после загрузки — media_storage=, см. гайд по интеграции.
AntiPublic
Отдельный лицензионный ключ, не токен маркета — в общую ротацию он не попадает никогда.
async with Client(["<market-token>"], antipublic_key="<antipublic-license-key>") as client:
remaining = await client.antipublic.license_available_queries()
hit = await client.antipublic.license_check_lines(lines=("user:pass",))
Вызов client.antipublic.* без antipublic_key= поднимает CredentialMissing — падаем громко, а не молча ничего не делаем.
Ошибки
Всё, что поднимает SDK, — подклассы LztError. Ловите тот тип, от которого умеете восстанавливаться, остальное пусть летит выше.
from pylzt import AuthFailed, NotFound, RateLimited, TransportError
from pylzt.types import ItemId
try:
lot = await client.market.get_lot(item_id=ItemId(999_999_999))
except NotFound:
... # лота нет или он не виден этому токену
except RateLimited as exc:
... # exc.retry_after — пул токенов уже отступил сам
except AuthFailed:
... # токен мёртв — убрать из ротации, см. reconfigure()
except TransportError:
... # upstream 5xx, ретраи исчерпаны
Полная таблица ошибок, DI, фейки для тестов, reconfigure() для горячей ротации токенов — docs/integration-guide.md.
Почему фреймворк, а не библиотека
Обёртка даёт типизированные методы над HTTP-клиентом. pylzt даёт операционную обвязку, которая нужна боевой интеграции, уже собранную:
- Пул токенов (
token_pool/round_robin.py) — round-robin по многим токенам, каждый со своим ведром наRateClassпо официальным потолкам (Market 120/мин + 20/мин Category Search, Forum 300/мин). У AntiPublic свой пул на одну учётку (token_pool/_static.py). - Пул прокси (
proxy_pool/) — sticky-per-token или round-robin, HTTP/HTTPS/SOCKS5, circuit breaker на каждый прокси. - Устойчивость (
transport/base.py,lib/retry.py) — ретраи с джиттером и уважениемRetry-After, самрегистрирующаяся типизированная иерархия ошибок, склейка запросов в/batch, TTL-кэш по серверномуcacheTTL. - Метод как класс (
methods/base.py) — каждый эндпоинт это frozenBaseMethod[T]на Pydantic. Кривое поле запроса падает при конструировании, а не на проводе.Client.execute(method)— единственный путь исполнения запроса для всех фасадов. - Сгенерировано, а не переписано руками (
dev/codegen/) — методы, модели ответов, енумы и фасады рендерятся из официальной OpenAPI-спеки за гейтом ruff+mypy.format: binaryавтоматически становится типомMedia.
Кодогенерация
Две фазы: generate рендерит в staging и не трогает библиотеку, install продвигает staging в src/pylzt/ за гейтом и откатывается при любой ошибке. Библиотека на диске никогда не остаётся сломанной после регена.
python -m dev.codegen build # generate + install, обычный случай
python -m dev.codegen build --scrape # сначала перескрейпить спеку
python -m dev.codegen generate # только рендер в dev/codegen/generated/
python -m dev.codegen install # только продвижение staging → библиотека
python -m dev.codegen scrape # только скрейп + слияние спеки
python -m dev.codegen check # только гейт ruff+mypy+import, без регена
| Флаг | Где | Что делает |
|---|---|---|
--api market|forum|antipublic |
generate, build |
ограничить одним API (повторяемый); по умолчанию все три |
--scrape |
generate, build |
перекачать readme.io-справочник перед рендером |
--refresh |
*--scrape, scrape |
игнорировать дисковый кэш страниц |
--model-backend {pydantic,dataclass} |
generate, build |
таргет для DTO ответов; по умолчанию pydantic |
--no-validate |
install, build |
пропустить гейт (опасно, только для локального просмотра) |
--site market|forum|antipublic |
scrape |
ограничить скрейп одним сайтом |
Слитые спеки dev/generated/openapi/lzt_{market,forum,antipublic}.json закоммичены — свежий клон собирается без скрейпа. Остальное под dev/generated/ в gitignore.
Каждый сгенерированный файл несёт Generated by forge — DO NOT EDIT: спека не всегда совпадает с тем, что API реально отдаёт, и реген затрёт правку руками. У части моделей в docstring стоит пометка о ручном патче — там записано, что показали живые данные и почему поле выглядит именно так.
Разработка
git clone https://github.com/open-lzt/pylzt && cd pylzt
uv sync --extra dev
git config core.hooksPath .githooks # локальный гейт ruff+mypy+pytest на push
uv run pytest -q
E2E-тесты бьют по живому API, требуют LZT_E2E_TOKEN и исключены из прогона по умолчанию:
uv run pytest -m e2e -q
.github/workflows/ci.yml гоняет ruff, mypy и pytest на каждый push и PR. .githooks/pre-push — тот же гейт локально, чтобы красное ловилось до пуша, а не после.
Релизы едут по тегу v* — сборка, гейт и публикация на PyPI автоматические, порядок для мейнтейнера описан в CONTRIBUTING.md.
Экосистема
lzt-testnet — мок-маркет для тестов · lzt-eventus — движок событий · auto-lzt — no-code автоматизации · lzt-mcp — сервер для AI-агентов · весь стенд
Лицензия
Release files for pylzt 0.2.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pylzt-0.2.3.tar.gz | 753.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pylzt-0.2.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.2 MB
Release files / pylzt-0.2.3.tar.gz
| Download URL | pylzt-0.2.3.tar.gz |
|---|---|
| Size | 753.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9ba1db2da01bf6348033ae7ea6da843a9e8da85c95184e149dc40310976b6a41
|
|
BLAKE2b-256 checksum How to use checksums |
e70d0854220b089c5cac467482590c7234bd188f1335fb486aa3a77f004b2520
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / pylzt-0.2.3-py3-none-any.whl
| Download URL | pylzt-0.2.3-py3-none-any.whl |
|---|---|
| Size | 465.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b975679c4ff2a4ae1b5db6b10f289507a6660715d3d0975e7065dac4434a5925
|
|
BLAKE2b-256 checksum How to use checksums |
6d10e0be8f22e55381d97e64efbd2f044121bf3ab63be67ee6ef6f9cec0f8b81
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|