Python SDK for DOM.Inventory CMDB — typed REST client with bulk, async, OPTIONS-introspection and OpenAPI-driven codegen.
Project description
pydomi — Python SDK для DOM.Inventory
Идиоматичный клиент REST API: api.<app>.<endpoint>.<verb>(...).
Полный CRUD, bulk-операции, async-вариант, OPTIONS-introspection,
автогенерация типизированных классов из OpenAPI-схемы.
import pydomi
api = pydomi.Api("http://your-server:8000", token="<api-token>")
# список — авто-пагинация
for device in api.dcim.devices.filter(site=1):
print(device.id, device.name, device.serial)
# одиночный объект
site = api.dcim.sites.get(slug="dc-north")
# создание
new = api.dcim.sites.create(name="DC-South", slug="dc-south")
# редактирование
new.description = "Резервная площадка"
new.save() # PATCH только изменённых полей
# удаление
new.delete()
Установка
pip install pydomi # ядро
pip install pydomi[async] # + httpx для AsyncApi
pip install pydomi[dev] # + pytest, respx, twine
pip install pydomi[docs] # + sphinx
Требования: Python ≥ 3.10, requests ≥ 2.31.
Шаг 2 (важный): сгенерировать типизированные классы под ваш сервер
Сразу после установки запустите:
python -m pydomi update-classes http://your-server:8000 --token <api-token>
# или
pydomi update-classes http://your-server:8000 --token <api-token>
Команда:
- Скачивает OpenAPI-схему с
/api/schema/. - Генерирует типизированные классы для каждой модели в
pydomi/generated/models.py+.pyiстабы для IDE-автодополнения. - Складывает метаданные (
FK_MAP,M2M_MAP,REVERSE_FK_MAP,READ_ONLY_FIELDS,ACTIONS_MAP,FILTERS_MAP) вpydomi/generated/__init__.py.
После этого автоматически включаются:
| Фича | Что это даёт |
|---|---|
| Типизированные классы | isinstance(dev, Device), IDE видит поля |
| Lazy FK | dev.site.name (один GET с кешем) вместо ручного api.dcim.sites.get(dev.site) |
| M2M-менеджеры | dev.tags.add("prod") / .remove(...) / .set(...) |
| Reverse-FK | dev.interfaces.all(), site.devices.count() |
| Filter validation | filter(unknown_kwarg=X) ловится UserWarning локально |
| Strict-create | create(..., strict=True) валидирует required / choices / max_length до POST |
| Slug → id auto-resolve | create(site="dc-1") сам резолвит FK |
Запускайте
update-classesснова после каждого расширения серверной модели — SDK сразу подхватит новые поля и endpoint'ы.python -m pydomi diffпокажет diff между текущимgenerated/и тем, что отдаёт сервер.
Без этого шага SDK тоже работает, но в «динамическом» режиме: все объекты
будут Record, FK останутся int-ами, IDE-автодополнения не будет.
Аутентификация
DOM.Inventory принимает три способа:
| Способ | Как передать |
|---|---|
| API-токен (рекомендуется) | pydomi.Api(url, token="...") — отправит Authorization: Token <key> |
| Session login | api.login(username, password) — Django form login с CSRF, cookie в session |
| Свой Session | pydomi.Api(url, session=<requests.Session>) — для кастомных adapter'ов/auth |
Управление API-токенами из SDK:
t = api.tokens.create("ci-bot")
print("STORE:", t["raw_key"]) # показывается ОДИН РАЗ
for t in api.tokens.list():
print(t["id"], t["name"], t["prefix"], t["is_active"])
api.tokens.rotate(7) # старый ключ моментально перестаёт работать
api.tokens.revoke(7) # is_active=False, не удаляет
api.tokens.delete(7) # hard delete
Поддерживаемые приложения и эндпоинты
Список генерится из серверного OpenAPI-описания и кодифицирован в
src/pydomi/registry.py. После update-classes доступны:
| App | Endpoints |
|---|---|
core |
tags, statuses, custom_fields, custom_field_values, uniqueness_rules, audit_logs (read-only), auth_tokens |
organizations |
organizations |
tenancy |
tenant_groups, tenants, contacts |
dcim |
regions, sites, locations, racks, manufacturers, device_types, platforms, device_roles, cluster_types, clusters, devices |
network |
vrfs, prefix_roles, prefixes, interfaces, ip_addresses, nat_rules |
virtualization |
virtual_machines |
circuits |
providers, circuit_types, circuits |
agents |
agent_types, agents, health_check_rules |
automated_systems |
system_groups, automated_systems, auto_as_rules |
ansible |
connection_profiles, ansible_inventories |
containers |
registries, registry_proxies, repositories, images, containers |
services |
services, service_dependencies, dns_zones, dns_records, load_balancers, lb_vips, lb_backends |
Из кода:
api.apps() # ['core', 'organizations', 'tenancy', 'dcim', …]
api.endpoints() # {'dcim': ['clusters', 'devices', …], …}
CRUD на каждый день
# all() — лениво пагинируется
list(api.dcim.devices.all())
# filter / search / order_by / slicing / first / last / exists
api.dcim.devices.filter(site=1, status="active")
api.dcim.devices.search("router")
api.dcim.devices.order_by("-created_at")
api.dcim.devices.all()[:10]
api.dcim.devices.first()
# count — один запрос, без выгрузки страниц
api.dcim.devices.count(status="active")
# get(id) или get(**lookup)
api.dcim.devices.get(42)
api.dcim.devices.get(name="router-msk-1")
# create — slug→id auto-resolve для FK и стрип read-only
dev = api.dcim.devices.create(name="d1", device_type=1, site="dc-1")
# strict-проверка до POST: required / unknown / choices / max_length
dev = api.dcim.devices.create(name="d2", site=1, strict=True)
# save / update / refresh / delete
dev.serial = "ABC"
dev.save()
dev.update(serial="DEF", description="…")
dev.refresh()
dev.delete()
# diff — что я изменил до save()
dev.serial = "XYZ"
dev.diff() # {'serial': ('ABC', 'XYZ')}
# describe — pretty-print для REPL / Jupyter
print(dev.describe())
Bulk и атомарность
Серверная сторона предоставляет bulk / bulk_update / bulk_delete действия
на каждом ViewSet (apps/api/bulk.py). SDK использует их под капотом — это
один HTTP-запрос на батч, обёрнутый в transaction.atomic() на сервере.
Если хоть один элемент не валиден, БД откатится целиком.
# bulk_create — один POST, один rollback при ошибке
created = api.core.tags.bulk_create([
{"name": "prod", "slug": "prod", "color": "#FF0000"},
{"name": "dev", "slug": "dev", "color": "#00FF00"},
])
# → [Record, Record], в порядке входа
# bulk_update — один PATCH с массивом, требует ``id`` в каждом item
api.dcim.devices.bulk_update([
{"id": 1, "description": "edited"},
{"id": 2, "description": "edited"},
])
# bulk_delete — один DELETE с {"ids": [...]}
api.dcim.devices.bulk_delete([1, 2, 3])
Cap размера батча — 1000 строк (override на сервере bulk_max_items per ViewSet).
Если на старом сервере bulk-эндпоинтов нет (404), SDK автоматически откатывается
на per-row цикл — параметр fallback=False отключает откат и пробрасывает ошибку.
Скорость: на тесте 50 объектов — create ×46, delete ×50 быстрее чем
последовательные POST/DELETE.
Async
import asyncio
from pydomi import AsyncApi
async def main():
async with AsyncApi("http://your-server:8000", token=tk) as api:
# три endpoint'а параллельно
sites, devices, tags = await asyncio.gather(
api.dcim.sites.all().all_list(),
api.dcim.devices.all().all_list(),
api.core.tags.all().all_list(),
)
# async iteration + auto-pagination
async for dev in api.dcim.devices.search("router"):
print(dev.id, dev.name)
asyncio.run(main())
Доступен после pip install pydomi[async] (тянет httpx).
Те же codegen-метаданные используются — pydomi.AsyncApi и pydomi.Api
работают с одним и тем же pydomi/generated/.
Кеш справочников и dry-run
api = pydomi.Api(
"http://server",
token=tk,
cache_ttl=60, # TTL-LRU кеш для GET-detail на справочниках
dry_run=False, # True → мутации логируются, не отправляются
)
cache_ttl > 0 включает кеш для read-mostly endpoint'ов (Tag, Status, Site, Tenant,
AS и т.д.). Lazy-FK через dev.site.name тогда стоит один GET на ~5 минут, а не
сотни GET'ов при перебое устройств.
Обработка ошибок
from pydomi.exceptions import (
AuthenticationError, # 401, 403
NotFoundError, # 404
ValidationError, # 400 (DRF dict {field: [msg]})
ThrottledError, # 429 (есть .retry_after)
RequestError, # всё остальное / сетевая ошибка
ConfigurationError, # неправильное использование SDK
)
try:
api.dcim.sites.create(name="dup", slug="dup")
except ValidationError as e:
print(e.field_errors) # {'slug': ['This field must be unique.']}
except ThrottledError as e:
print("retry after", e.retry_after, "s")
Retry-policy для 5xx и 429 (с уважением Retry-After) включён по умолчанию
через urllib3 Retry-adapter:
pydomi.Api(url, retries=3, backoff_factor=0.3)
CLI
pydomi update-classes <url> # см. выше
pydomi diff # сравнить local generated/ с сервером
pydomi schema dump > schema.json # сырая OpenAPI
pydomi get devices --filter site=1 --fields name,serial --format table
pydomi get devices 42 --format json
pydomi create tags name=prod slug=prod color=#FF0000
pydomi shell # IPython REPL с готовым `api`
Все команды читают DOMI_URL / DOMI_TOKEN из env, либо принимают
--url / --token.
Контекст-менеджер
with pydomi.Api("http://your-server:8000", token=tk) as api:
print(api.status()) # {'title': '...', 'version': '...'}
Кастомный requests.Session
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
s = requests.Session()
s.mount("https://", HTTPAdapter(max_retries=Retry(total=5)))
api = pydomi.Api("https://srv", token=tk, session=s, timeout=10)
Разработка
pip install -e .[dev,async,docs]
pytest # 215+ unit-тестов через responses + respx, без сети
ruff check src tests
cd docs && make html # Sphinx → docs/_build/html/index.html
Структура:
src/pydomi/
api.py # Api — top-level, sync
async_api.py # AsyncApi — same shape, через httpx
endpoint.py # CRUD, bulk, OPTIONS metadata, strict validate
queryset.py # QuerySet: filter/search/order_by/first/last/iterator
record.py # Record: save/update/refresh/diff/delete/describe
relations.py # _LazyFK / _M2MManager / _ReverseManager
tokens.py # api.tokens.{list,create,rotate,revoke,delete}
cache.py # TTL-LRU для GET-detail
codegen.py # OpenAPI → models.py + models.pyi + метаданные
cli.py # python -m pydomi
exceptions.py # типизированная иерархия
registry.py # статический app → endpoint map
generated/ # placeholder; перезаписывается update-classes
examples/
load_inventory.py # idempotent get-or-create
load_inventory_bulk.py # CSV bulk import
audit_unassigned.py # отчёт «без AS / без site / без role»
tag_devices_by_subnet.py # тэгнуть устройства внутри CIDR
async_bulk_export.py # параллельная выгрузка в JSON
watch_changelog.py # poll-er свежих changelog событий
Известные ограничения 0.1.0
- Изменение значения lazy-FK (например,
dev.site = 5) ставит правильный id в_dataи приsave()отправит его, но кешdev.siteRecord не инвалидируется до следующегоrefresh(). Workaround:dev.update(site=5). - Custom actions с
detail=Trueавтоматически не привязываются — используйтеapi.X.call_action_on(pk, "name", method="POST", json=...). - Standalone эндпоинты вне ViewSet (
/api/changelog/, кастомные@api_view) доступны черезapi.request("GET", "/api/changelog/", params=...).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pydomi-0.1.6.tar.gz.
File metadata
- Download URL: pydomi-0.1.6.tar.gz
- Upload date:
- Size: 58.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a1b589a444a5de98fd156ced60de1b91414870721865a2c51cf1a68899baac53
|
|
| MD5 |
a17e0a7e464db9f1647eb41fbc1635b8
|
|
| BLAKE2b-256 |
f7a23dc41e3cbfd45e713d246a471104d8992c01efdd79a72daed76ae16d8ce0
|
File details
Details for the file pydomi-0.1.6-py3-none-any.whl.
File metadata
- Download URL: pydomi-0.1.6-py3-none-any.whl
- Upload date:
- Size: 68.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
366f078a728ff6c48e9b048d4531e15b876a5d6f3d6372a8b83a91d1705bf856
|
|
| MD5 |
53447637a35181c3a129af547b7a3057
|
|
| BLAKE2b-256 |
b9cf381f1b8008d9f2f8ee7f726f356203a227ce7848121be072f4f9cd587ae4
|