Imager Client — Python
Клиент микросервиса Imager на Python: построение путей/URL ассетов и админ-методы.
- English version
- Обзор
- Демо — пример работы микросервиса и клиентской части
Установка
Пакет imager_client (Python ≥ 3.10, без внешних зависимостей):
pip install imager_client
Или из репозитория:
pip install git+https://gitverse.ru/pkg-ru/imager-client
Импорт
from imager import Imager
from imager import AssetType, AssetPath, ImagerOptions, ImagerServerOptions, Segment
Инициализация
imager = Imager(options: ImagerOptions | dict | None = None)
Класс Imager использует __slots__ (фиксированный набор полей, без __dict__).
ImagerOptions vs ImagerServerOptions
ImagerOptions— TypedDict клиентской части:dpr,format,formats,baseURL,sort. Не содержитtoken/adminURL.ImagerServerOptions(ImagerOptions)— расширяет его полямиtoken,adminURL(для админ-методов).
Конструктор Imager принимает любой dict и читает из него все семь ключей (включая token/adminURL), поэтому для админ-методов можно передавать как ImagerServerOptions, так и обычный dict. Типизация ImagerOptions просто не описывает серверные ключи.
| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
token |
str |
"" |
токен админ-методов (только AdminGenerate/AdminDelete); есть в ImagerServerOptions |
dpr |
int |
0 |
итоговое dpr по умолчанию; 0-1 — не используется, 2-3 — используется |
format |
str |
"" |
формат генерации по умолчанию (""/"auto" — формат исходника; если исходник не картинка — jpg) |
formats |
list[str] |
[] |
список форматов по умолчанию; если пуст — используется format; дубли удаляются (jpeg → jpg) |
baseURL |
str |
"/" |
база URL ассетов; нормализуется (всегда с завершающим /) |
adminURL |
str |
"" |
базовый URL админ-API (без завершающего /); есть в ImagerServerOptions |
sort |
bool |
False |
сортировать размерные сегменты в GetAssets по width/height (см. ниже) |
Нормализация baseURL: пустой/не задан → "/"; без завершающего / → добавляется. adminURL: завершающий / удаляется.
Сортировка сегментов (sort: true)
При sort: true сегменты в GetAssets сортируются до построения путей и вычисления dpr:
- Сегменты с
width > 0— по возрастаниюwidth; при равномwidth— по возрастаниюheight. height = 0/отсутствует — в конец своейwidth-группы.- Сегменты без
width(x400,thumb) — в самый конец, между собой не сортируются (сохраняют исходный порядок). - Если ни у одного сегмента нет ни
width, ниheight— сортировка не выполняется. dprвычисляется после сортировки: первый (наименьший) размерный сегмент задаётbase_width/base_height, поэтому все коэффициенты ≥ 1.
Пример: ["300x", "100x100", "200x", "x400", "thumb"] → 100x100 → 200x → 300x → x400 → thumb.
imager = Imager({
"baseURL": "https://imgs.example.com/images",
"format": "webp",
"dpr": 2,
"token": "secret",
"adminURL": "https://imager.example.com",
})
Методы
GetAsset
def GetAsset(
self,
source: str,
segment: Segment | None = None,
format: str | None = None,
dpr: int | str | None = None,
) -> AssetType: ...
Возвращает один AssetType. paths содержит все варианты dpr от 1 до итогового.
| Параметр | Тип | Описание |
|---|---|---|
source |
str |
путь к исходнику (/test.gif, test.gif, thumbs/photo.jpg) |
segment |
str | {width,height} | [w,h] | None |
сегмент; не задан → "x" |
format |
str | None |
итоговый формат; "auto"/"" → формат исходника (если исходник не картинка — jpg); не задан → настройки format → формат исходника |
dpr |
int | str | None |
итоговое dpr; не задан → настройки dpr; < 1 → не используется |
GetAssets
def GetAssets(
self,
source: str,
segments: Segment | list[Segment] | None = None,
formats: str | list[str] | None = None,
dprs: int | str | None = None,
) -> list[AssetType]: ...
Возвращает список AssetType — по одному на каждый формат (все ассеты с одинаковым типом объединяются в один AssetType). Внутри paths порядок сегмент-мажорный: для каждого сегмента все dpr-шаги подряд. dpr каждого пути пересчитывается из фактических размеров: база — ширина (или высота) первого участника с известным размером, dpr = фактическая ширина / базовая ширина. segments не задан → ["x"]. formats не задан → настройки formats → [format] → [формат исходника].
Список форматов дедуплицируется: jpeg нормализуется к jpg, дубли удаляются (первое вхождение сохраняет позицию). Элемент "auto" (или "") означает формат исходника; если исходник не картинка (видео, нет расширения) — подставляется jpg. Поддерживаемые форматы картинок: jpg, jpeg, png, webp, avif, heif, heic, apng, jxl, gif.
imager.GetAssets("/test.jpg", "100x100", ["webp", "avif", "jpg", "webp", "auto"]) # → webp, avif, jpg
imager.GetAssets("/test.jpg", "100x100", ["webp", "auto"]) # → webp, jpg
imager.GetAssets("/test.jpg", "100x100", ["jpg", "auto"]) # → jpg
imager.GetAssets("/test.mov", "100x100", ["jpg", "auto"]) # → jpg
imager.GetAssets("/test.jpg", "100x100", ["webp", "jpg", "auto"]) # → webp, jpg
GetAssetsHtml
def GetAssetsHtml(
self,
source: str,
segments: Segment | list[Segment] | None = None,
formats: str | list[str] | None = None,
dprs: int | str | None = None,
options: dict | None = None,
) -> str: ...
Возвращает строку — HTML-разметку <picture>/<img> по тем же ассетам, что и GetAssets (ассеты группируются по типу). Один вызов генерирует ровно один тег <picture> (или <img>, если формат один).
options — HTML-атрибуты:
| Атрибут | Куда попадает | Примечание |
|---|---|---|
class, id, data-*, прочие |
<picture> |
— |
alt, sizes, loading |
<img> |
— |
decoding, fetchpriority |
<img> |
нативные атрибуты <img> (async-декодирование, приоритет загрузки) |
lazy |
<img> |
булев: превращается в loading="lazy" |
width, height |
<img> |
перекрываются автоматическими значениями из базового path (для CLS) |
imgAttrs |
<img> |
объект атрибутов, попадающих именно на <img>; объединяется с перенаправленными атрибутами, имеет приоритет |
Правила:
- Атрибуты выводятся в порядке вставки (порядок ключей dict/options); никакой сортировки не выполняется.
- Булевы атрибуты (
lazy: true,loading: true) — без значения;false/null— пропускаются. - Значения HTML-экранируются (
&→&,<→<,>→>,"→",'→'). - Внутри
<picture>теги делятся по типу (формату): все пути одного формата объединяются в одинsrcset. - Для
<img>выбирается группа: 1) формат исходника (source_format: true); 2) первый поддерживаемый всеми браузерами (all_support: true); 3) последняя. Остальные форматы —<source type="...">. srcset-дескрипторы: если вoptionsпереданsizes— w-режим:w-дескрипторы по ширине (200w,400w); пути без ширины (сегментx, оригинальный размер) в srcset не попадают — оригинал остаётся только вsrcу<img>как fallback; если в группе толькоx-пути — srcset пуст. Иначе — x-режим:x-дескрипторы по dpr (1x,2x,3x); если dpr нет, но есть высота — dpr вычисляется из отношения высот (дробные допустимы, напр.1.5x);x-пути включаются в srcset с эвристикой max+1:max_dprвычисляется только из размерных путей (у которых есть width или height); dpr-поляx-путей (шаги 1, 2, 3...) в расчёт не участвуют. Если размерные пути есть (max_dpr > 0) — дескриптор =(max_dpr + 1) × dpr-шаг(приблизительный, т.к. реальный размер оригинала неизвестен); если размерных путей нет, но есть dpr-шаги (dprs ≥ 2) — дескриптор = dpr-шаг (1x,2x,3x...); если нет ни размерных путей, ни dpr-шагов (dprs = 0) — путь в srcset без дескриптора (просто путь, без1x). Размеры автоматически не генерируются.<source>всегда содержитsrcset(никогдаsrc), даже при одном пути в группе; если srcset пуст (в w-режиме все пути —x) —<source>не выводится.<img>:srcsetдобавляется только если путей > 1 и srcset не пуст; иначе у<img>толькоsrc.width/heightна<img>добавляются из базового (первого) path, если известны — для CLS. Если пользователь задал своиwidth/height(напрямую или черезimgAttrs) — автоматические не добавляются (без дублирования).imgAttrsобъединяется с перенаправленными img-атрибутами (alt,sizes,loading,width,height,decoding,fetchpriority): значения изimgAttrsимеют приоритет. Сам ключimgAttrsв<picture>не попадает.- Если ассетов нет — возвращается пустая строка.
- Python-реализация при пустом результате
GetAssetsбросаетIndexError(обращение кpaths[0]) — известная особенность, см. README.
Пример:
html = imager.GetAssetsHtml(
"/test.png",
"200x200",
["webp", "png"],
2,
{"class": "photo", "id": "main", "alt": "Hello & <world>", "lazy": True},
)
<picture class="photo" id="main">
<source type="image/webp" srcset="/test-png/200x200.webp 1x, /test-png/200x200@2.webp 2x">
<img src="/test-png/200x200.png" srcset="/test-png/200x200.png 1x, /test-png/200x200@2.png 2x" alt="Hello & <world>" loading="lazy" width="200" height="200">
</picture>
GetAssetPath
def GetAssetPath(
self,
source: str,
segment: Segment | None = None,
format: str | None = None,
dpr: int | str | None = None,
) -> str: ...
Возвращает строку — URL основного варианта ассета. Эквивалент GetAsset(...).paths[0].path.
AdminGenerate
def AdminGenerate(
self,
target: str | AssetType | list[AssetType] | list[str],
wait: bool = False,
) -> bool: ...
POST {adminURL}/admin/assets/generate, заголовок Authorization: Bearer <token>. Возвращает True при HTTP 200/202, иначе False.
AdminDelete
def AdminDelete(
self,
target: str | AssetType | list[AssetType] | list[str],
wait: bool = False,
) -> bool: ...
DELETE {adminURL}/admin/assets/delete — то же тело и заголовок. Возвращает True при HTTP 200, иначе False.
Маппинг типов входа target
Тип входа target |
Режим | Тело запроса |
|---|---|---|
str |
A (source) | {"source": "<строка как есть>", "wait": ...} |
AssetType |
B (assets) | {"assets": [paths[].path из AssetType], "wait": ...} |
list[AssetType] |
B (assets) | {"assets": [paths[].path из ВСЕХ AssetType, подряд], "wait": ...} |
list[str] |
B (assets) | {"assets": ["<элемент 1>", "<элемент 2>", ...], "wait": ...} |
str— режим A: путь к исходнику передаётся вsourceкак есть.AssetType/list[AssetType]— режим B: собираются всеpaths[].pathв один списокassets.list[str]— режим B: элементы — уже готовые пути к ассетам, передаются вassetsкак есть, без валидации и без преобразований.
Если token или adminURL пусты — админ-методы возвращают False без HTTP-запроса. HTTP-клиент — urllib.request (стандартная библиотека).
Segment
segment — строка как есть ("preset", "200x200", "200x", "x200", "x"), словарь {width, height} (оба необязательны), список/кортеж [width, height] (оба обязательны) или не задан → "x".
| Вход | Результат |
|---|---|
"preset" |
"preset" |
{"width": 200, "height": 200} |
"200x200" |
{"width": 200} |
"200x" |
{"height": 200} |
"x200" |
{} / {"width": 0, "height": 0} |
"x" |
[200, 200] |
"200x200" |
[0, 400] |
"x400" |
dpr
dpr/dprs— число или строка-цифра ("2").- Итог < 1 → не используется (один path-объект без суффикса и без поля
dpr). - Итог = 1 (задан явно) → один path-объект без поля
dpr(1x — дефолтный дескриптор;dpr: 1добавляется только если в группе есть путь сdpr > 1). - Итог = 2 →
[без суффикса, @2]. - Итог = 3 →
[без суффикса, @2, @3]. - Итог > 3 → трактуется как 3.
Формат URL ассета
{baseURL}{path}/{source_name}-{source_format}/{segment}@{dpr}.{output_format}
path— логический путь исходника (ведущий/отбрасывается):/thumbs/photo.jpg→path="thumbs",name="photo",format="jpg".source_format— расширение в нижнем регистре.@{dpr}— добавляется к сегменту только при dpr ≥ 2.output_format— итоговый формат (аргумент → настройки → формат исходника).
Структура AssetType
class AssetPath(TypedDict, total=False):
path: str
dpr: int | float
width: int
height: int
class AssetType(TypedDict):
type: str
paths: list[AssetPath]
source_format: bool # только при true
all_support: bool # только при true
Пример результата GetAsset("/test.gif", {"width": 200, "height": 200}, "gif", 2):
{
"type": "image/gif",
"paths": [
{"path": "https://imgs.example.com/images/test-gif/200x200.gif", "width": 200, "height": 200},
{"path": "https://imgs.example.com/images/test-gif/200x200@2.gif", "width": 400, "height": 400, "dpr": 2}
],
"source_format": true,
"all_support": true
}
Правила заполнения path-объектов:
path— полный URL, всегда.dpr— при dpr ≥ 2;dpr: 1— только если в группе есть путь сdpr > 1(иначе 1x — дефолтный дескриптор, без поля).width/height— только для size-сегмента (200x200,{w,h},[w,h],xи т.п.); при dpr ≥ 2 умножаются на dpr. Для именованного пресета не добавляются.type— MIME итогового формата: всегда"image/" + format(дляjpg—image/jpeg). Пустая строка невозможна:mime_forопределён для любого формата.source_format—true, если итоговый формат совпадает с исходным форматом файла; в JSON присутствует только приtrue.all_support—true, если итоговый формат ∈ {jpg,jpeg,gif,png} (поддерживается всеми браузерами); в JSON присутствует только приtrue.
Функции форматов
Модуль imager.ImagerTypes экспортирует:
IMAGE_FORMATS— frozenset поддерживаемых форматов картинок:jpg, jpeg, png, webp, avif, heif, heic, apng, jxl, gif. Всё, что не входит в список (видео и прочее), приformat="auto"/""трактуется как не-картинка.normalize_format(format)— нормализация: lower-case,jpeg→jpg.resolve_format(format, source_format)— резолв одного формата:"auto"/""→ исходный формат, если он картинка, иначеjpg.dedupe_formats(formats)— дедупликация списка форматов (jpeg→jpg, первое вхождение сохраняет позицию).mime_for(format)— MIME по итоговому формату: всегда"image/" + format(дляjpg—image/jpeg). Не входит в__all__пакета, но доступен черезfrom imager.ImagerTypes import mime_for.
MIME по формату
| Формат | MIME |
|---|---|
jpeg, jpg |
image/jpeg |
png |
image/png |
webp |
image/webp |
gif |
image/gif |
avif |
image/avif |
heif |
image/heif |
heic |
image/heic |
apng |
image/apng |
jxl |
image/jxl |
Разные варианты вызова
# segment: четыре формы записи
imager.GetAsset("/test.gif") # не задан → "x", формат исходника
imager.GetAsset("/test.gif", "thumb") # строка: именованный пресет
imager.GetAsset("/test.gif", "200x200") # строка: размер
imager.GetAsset("/test.gif", {"width": 200}) # dict: только ширина → "200x"
imager.GetAsset("/test.gif", {"height": 400}) # dict: только высота → "x400"
imager.GetAsset("/test.gif", (200, 200)) # кортеж → "200x200"
# format: строка или список, dpr: число или строка
imager.GetAsset("/test.gif", "200x200", "webp") # + формат
imager.GetAsset("/test.gif", "200x200", "webp", 2) # + dpr
imager.GetAsset("/test.gif", "200x200", "webp", "2") # dpr строкой
imager.GetAssets("/test.gif", ["200x200", "x400"], ["webp", "gif"], 2) # 2 ассета (по одному на формат)
Кэш source
Imager кэширует разбор последнего source (_src_cache: source → path, имя, формат, префикс). Повторные вызовы методов с тем же source переиспользуют разбор и работают быстрее. Кэш хранит только одну запись — при смене source происходит полный пересчёт. Это внутренняя оптимизация: на результат она не влияет.
Примеры
from imager import Imager
imager = Imager({"baseURL": "https://imgs.example.com/images/", "format": "webp"})
# Один ассет
asset = imager.GetAsset("/test.gif", {"width": 200, "height": 200}, "gif", 2)
print(asset["type"]) # image/gif
print(asset["paths"]) # [{'path': '.../200x200.gif', 'width': 200, 'height': 200},
# {'path': '.../200x200@2.gif', 'width': 400, 'height': 400, 'dpr': 2}]
# Список ассетов (по одному AssetType на формат)
assets = imager.GetAssets("/test.gif", [{"width": 200, "height": 200}, {"height": 400}], ["webp", "gif"], 1)
print(len(assets)) # 2
# Только URL основного варианта
url = imager.GetAssetPath("/test.gif", "200x200", "webp")
print(url) # https://imgs.example.com/images/test-gif/200x200.webp
# Админ-методы
admin = Imager({"token": "secret", "adminURL": "https://imager.example.com"})
ok = admin.AdminGenerate("/test.gif", wait=True) # POST .../admin/assets/generate
ok = admin.AdminDelete(asset, wait=False) # DELETE .../admin/assets/delete
Тесты
python test/test.py
Раннер читает test/fixture.json и выполняет golden-кейсы. Полный прогон всех языков — make test (оркестратор — go run test/test.go).
Metadata
Release files for imager-client 2.0.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| imager_client-2.0.6.tar.gz | 82.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| imager_client-2.0.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 133.3 kB
Release files / imager_client-2.0.6.tar.gz
| Download URL | imager_client-2.0.6.tar.gz |
|---|---|
| Size | 82.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
aaf1491cd63f61b7d082e92d79b9bd403d76599914c3b1eede28b02631f34a77
|
|
BLAKE2b-256 checksum How to use checksums |
da50d846eaddd2e3083c3ec76c4bdf4efbedae953b74e7e1490a126d97c865aa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.
Transparency logRelease files / imager_client-2.0.6-py3-none-any.whl
| Download URL | imager_client-2.0.6-py3-none-any.whl |
|---|---|
| Size | 50.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c232c142dfdc14cc14a8435c027eb22a512d5cd28010340c40cf4a9accd576ce
|
|
BLAKE2b-256 checksum How to use checksums |
2d331ae7ba4e97e10db11a229f49f2f0a5ed809cf1c9df823495625303da8d4f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.
Transparency log