Skip to main content

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>

Команда:

  1. Скачивает OpenAPI-схему с /api/schema/.
  2. Генерирует типизированные классы для каждой модели в pydomi/generated/models.py + .pyi стабы для IDE-автодополнения.
  3. Складывает метаданные (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.site Record не инвалидируется до следующего 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pydomi-0.1.1.tar.gz (54.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pydomi-0.1.1-py3-none-any.whl (65.5 kB view details)

Uploaded Python 3

File details

Details for the file pydomi-0.1.1.tar.gz.

File metadata

  • Download URL: pydomi-0.1.1.tar.gz
  • Upload date:
  • Size: 54.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for pydomi-0.1.1.tar.gz
Algorithm Hash digest
SHA256 af1c43a71316cf46052629bdf336a0a560c2f90aba963ed59e516e1eec8c4c63
MD5 c1270e8f1108d00728834abf15a226f3
BLAKE2b-256 e1b48cd8166dcdd5706dd0dde8bd4eb5676974249b1127881e5750139e2f7c1c

See more details on using hashes here.

File details

Details for the file pydomi-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: pydomi-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 65.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for pydomi-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a8f84a6ecaffeaabc7c07db26ab3cdf1d733793a783ea27ec0c71f0a34408cae
MD5 f3d4cc397df7ef56388045f04227e26b
BLAKE2b-256 7c465a848f88257f6359bc7e2452480f258b8020e6c3105b09e3a87f35ca23aa

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page