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.5.tar.gz (57.5 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.5-py3-none-any.whl (68.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pydomi-0.1.5.tar.gz
  • Upload date:
  • Size: 57.5 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.5.tar.gz
Algorithm Hash digest
SHA256 ba8533fd6987b32166fa25445ad839775775d34689791ccfd6ae4824f4d19e20
MD5 291aef227e183266bc7e513e198e54d5
BLAKE2b-256 75fef751862414c8832419ba5ba543411c44f92079a36d93d7af0c3c8150a451

See more details on using hashes here.

File details

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

File metadata

  • Download URL: pydomi-0.1.5-py3-none-any.whl
  • Upload date:
  • Size: 68.2 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.5-py3-none-any.whl
Algorithm Hash digest
SHA256 a1ba43cc75012614a936cddb3161afdbc84dd7e96af9af8837edaaedcff073c0
MD5 6ef1a37a8110fea7f6bbe306b5b803e5
BLAKE2b-256 cbb6145d09ba0858161346bca8605b791347d328b0583501315cf1c3ed6f50a8

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