main 1.0.0
Dmitriy Smirnov 1 month ago
commit 28678fb8f0

@ -0,0 +1,25 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: end-of-file-fixer
- id: file-contents-sorter
files: .*mapping.yml
args:
- --unique
- id: no-commit-to-branch
name: no-commit-to-branch (main, master, dev_*)
args:
- --pattern
- dev_.*
- repo: https://github.com/pycqa/flake8
rev: 7.1.2
hooks:
- id: flake8
name: flake8 (1/2)
files: src/dynamix_sdk/types\.py|.*__init__\.py|.*_api\.py
args:
- --extend-ignore=F401,F403,F405
- id: flake8
name: flake8 (2/2)
exclude: src/dynamix_sdk/types\.py|.*__init__\.py|.*_api\.py

@ -0,0 +1,28 @@
# Список изменений в версии 1.0.0
## Добавлено
### Глобально
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BPYS-3 | Добавлена возможность авторизации через DECS3O. |
| BPYS-7 | Добавлена возможность авторизации через BVS. |
### Функциональный интерфейс
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BPYS-5 | Добавлена функция API `/system/usermanager/whoami`. |
| BPYS-4 | Добавлена функция API `/cloudapi/compute/get`. |
| BPYS-6 | Добавлена функция API `/cloudapi/kvmx86/create`. |
| BPYS-12 | Добавлена функция API `/cloudapi/compute/delete`. |
| BPYS-21 | Добавлена функция API `/cloudapi/user/get`. |
| BPYS-15 | Добавлена функция API `/cloudapi/compute/update`. |
| BPYS-29 | Добавлена функция API `/cloudapi/account/updateUser`. |
| BPYS-33 | Добавлена функция API `/cloudapi/account/enable`. |
| BPYS-32 | Добавлена функция API `/cloudapi/account/disable`. |
| BPYS-45 | Добавлена функция API `/cloudapi/rg/list`. |
| BPYS-20 | Добавлена функция API `/cloudapi/account/addUser`. |
| BPYS-30 | Добавлена функция API `/cloudapi/account/deleteUser`. |
| BPYS-51 | Добавлена функция API `/cloudapi/rg/get`. |
| BPYS-52 | Добавлена функция API `/cloudapi/rg/create`. |
| BPYS-35 | Добавлена функция API `/cloudapi/compute/list`. |

@ -0,0 +1,4 @@
dev:
pip install -e .
pip install -r requirements-dev.txt
pre-commit install

@ -0,0 +1,536 @@
# Dynamix Python SDK
- [Описание](#описание)
- [Системные требования](#системные-требования)
- [Соответствие версий платформы версиям SDK](#соответствие-версий-платформы-версиям-sdk)
- [Зависимости](#зависимости)
- [Установка](#установка)
- [Использование](#использование)
- [Конфигурация](#конфигурация)
- [Функциональный интерфейс](#функциональный-интерфейс)
- [Вызов функции](#вызов-функции)
- [Результат выполнения функции](#результат-выполнения-функции)
- [Соответствие названий](#соответствие-названий)
- [Типы данных SDK](#типы-данных-sdk)
- [Ошибки и исключения (exceptions)](#ошибки-и-исключения-exceptions)
- [Ошибки валидации данных](#ошибки-валидации-данных)
- [Ошибки HTTP](#ошибки-http)
- [Доступный функционал](#доступный-функционал)
- [Способы авторизации](#способы-авторизации)
- [Функции API](#функции-api)
- [Cloudapi](#cloudapi)
- [System](#system)
## Описание
**Dynamix Python SDK** предоставляет удобный интерфейс для интеграции взаимодействия с API **Dynamix Enterprise** в программный код на языке Python.
**Примечание:** проект находится в стадии активной разработки, [доступный функционал](#доступный-функционал) будет расширяться с каждой новой версией.
## Системные требования
### Соответствие версий платформы версиям SDK
| Версия платформы | Версия SDK |
| --- | --- |
| 4.2.0 | 1.0.x |
### Зависимости
Зависимости указаны в конфигурационном файле проекта [pyproject.toml](/pyproject.toml):
- версия Python: раздел `[project]`, ключ `requires-python`
- библиотеки: раздел `[project]`, ключ `dependencies`
**Важно:** использование более поздних версий Python и/или библиотек допустимо, но работоспособность не гарантируется, так как зависит от обратной совместимости с указанными версиями.
## Установка
Установка с помощью системы управления пакетами Python (pip), где `version` - тег с номером версии SDK (см. доступные теги в репозитории):
```sh
pip install git+https://repository.basistech.ru/BASIS/dynamix-python-sdk.git@version
```
Посмотреть версию установленного пакета:
```sh
pip show dynamix-sdk
```
## Использование
### Конфигурация
Для работы с SDK необходимо создать экземпляр класса **Dynamix**, конструктор которого принимает на вход параметры конфигурации SDK.
<details><summary>Авторизация с JWT</summary>
```python
from dynamix_sdk import Dynamix
JWT = '...'
dx = Dynamix(
url='https://...',
auth=JWT,
)
```
</details>
<details><summary>Авторизация через DECS3O</summary>
```python
from dynamix_sdk import Dynamix, DECS3OAuth
dx = Dynamix(
url='https://...',
auth=DECS3OAuth(
url='https://...',
client_id='...',
client_secret='...',
),
)
```
</details>
<details><summary>Авторизация через BVS</summary>
```python
from dynamix_sdk import Dynamix, BVSAuth
dx = Dynamix(
url='https://...',
auth=BVSAuth(
url='https://...',
domain='...',
client_id='...',
client_secret='...',
username='...',
password='...',
),
)
```
</details>
<details><summary>Настройка повторных попыток при 503</summary>
По умолчанию SDK при получении кода ответа 503 производит 10 повторных попыток с интервалом 5 секунд. Данное поведение можно скорректировать:
```python
from dynamix_sdk import Dynamix, DECS3OAuth, BVSAuth
# Для HTTP-запросов к Dynamix
Dynamix(
...,
http503_attempts = 10, # количество повторных попыток при 503
http503_attempts_interval = 5, # интервал в секундах между повторными попытками
)
# Для HTTP-запросов к DECS3O
DECS3OAuth(
...,
http503_attempts = 10, # количество повторных попыток при 503
http503_attempts_interval = 5, # интервал в секундах между повторными попытками
)
# Для HTTP-запросов к BVS
BVSAuth(
...,
http503_attempts = 10, # количество повторных попыток при 503
http503_attempts_interval = 5, # интервал в секундах между повторными попытками
)
```
</details>
<details><summary>Отключение проверки сертификата SSL</summary>
**Важно:** отключение проверки сертификата SSL даёт возможность произвести атаку типа MitM, поэтому пользоваться данной возможостью допустимо только в защищённой среде.
```python
import urllib3
from dynamix_sdk import Dynamix, DECS3OAuth, BVSAuth
urllib3.disable_warnings() # отключение вывода предупреждений безопасности SSL
# Для HTTP-запросов к Dynamix
Dynamix(
...,
verify_ssl=False, # отключение проверки сертификата SSL
)
# Для HTTP-запросов к DECS3O
DECS3OAuth(
...,
verify_ssl=False, # отключение проверки сертификата SSL
)
# Для HTTP-запросов к BVS
BVSAuth(
...,
verify_ssl=False, # отключение проверки сертификата SSL
)
```
</details>
### Функциональный интерфейс
Функциональный интерфейс предоставляет функции, соответствующие функциям API платформы.
#### Вызов функции
<details><summary>Выбор функции, передача параметров и сохранение результата</summary>
![](/demo/select_api_function_and_passing_params_and_save_result.gif)
</details>
<details><summary>Передача вложенных параметров </summary>
```python
from dynamix_sdk import Dynamix, types
dx = Dynamix(...)
data_disk_1 = types.DiskAPIParamsNM(name='data_disk_1', size=10)
# /cloudapi/kvmx86/create
new_vm_id = dx.api.cloudapi.kvmx86.create(
rg_id=123,
name='new_vm',
cpu=1,
ram=1024,
image_id=456,
data_disks=[data_disk_1],
)
```
</details>
<details><summary>Передача параметров перечисляемого типа (enum) </summary>
```python
from dynamix_sdk import Dynamix, types
dx = Dynamix(...)
# /cloudapi/kvmx86/create
new_vm_id = dx.api.cloudapi.kvmx86.create(
rg_id=123,
name='new_vm',
cpu=1,
ram=1024,
image_id=456,
chipset=types.Chipset.Q35, # enum
numa_affinity=types.NumaAffinity.none, # enum
)
```
</details>
#### Результат выполнения функции
Тип данных, возвращаемый функцией API SDK зависит от типа данных, возвращаемого соответствующей функцией API платформы.
Если функция API платформы возвращает структуру данных в виде набора пар ключ/значение (с заранее известными ключами), то функция API SDK возвращает модель Pydantic, которая описывает эту структуру.
<details><summary>Модель</summary>
```python
import pydantic
from dynamix_sdk import Dynamix
dx = Dynamix(...)
# /cloudapi/compute/get
get_result = dx.api.cloudapi.compute.get(compute_id=1)
print(type(get_result)) # <class '....CloudapiComputeGetResultModel'>
print(isinstance(get_result, pydantic.BaseModel)) # True
```
</details>
<details><summary>Обращение к полю модели</summary>
```python
from dynamix_sdk import Dynamix
dx = Dynamix(...)
# /cloudapi/compute/get
vm_name = dx.api.cloudapi.compute.get(compute_id=1).name
```
</details>
Вложенные структуры данных также представлены в виде моделей Pydantic:
<details><summary>Вложенная модель</summary>
```python
import pydantic
from dynamix_sdk import Dynamix
dx = Dynamix(...)
# /cloudapi/compute/get
vm_disk1 = dx.api.cloudapi.compute.get(compute_id=1).disks[0]
print(type(vm_disk1)) # <class '....DiskAPIResultNM'>
print(isinstance(vm_disk1, pydantic.BaseModel)) # True
```
</details>
<details><summary>Обращение к полю вложенной модели</summary>
```python
from dynamix_sdk import Dynamix
dx = Dynamix(...)
# /cloudapi/compute/get
vm_disk1_id = dx.api.cloudapi.compute.get(compute_id=1).disks[0].id
```
</details>
Некоторые поля моделей автоматически генерируются на основе полученных данных от API платформы.<br>
Например, при наличии в модели поля, содержащего временную метку (timestamp), для удобства генерируется поле, содержащее дату и время в виде `datetime`.
<details><summary>Генерируемое поле</summary>
```python
from dynamix_sdk import Dynamix
dx = Dynamix(...)
# /cloudapi/compute/get
get_result = dx.api.cloudapi.compute.get(compute_id=1)
# Поле, возвращаемое платформой
print(type(get_result.created_timestamp)) # <class 'int'>
# Генерируемое поле
print(type(get_result.created_datetime)) # <class 'datetime.datetime'>
```
</details>
Если функция API платформы возвращает примитивный тип данных (строка, число), то функция API SDK возвращает экземпляр класса, наследуемый от класса этого типа данных.
<details><summary>Класс, наследуемый от примитивного типа данных</summary>
```python
from dynamix_sdk import Dynamix
dx = Dynamix(...)
# /cloudapi/kvmx86/create
new_vm_id = dx.api.cloudapi.kvmx86.create(...)
print(type(new_vm_id)) # <class '....CloudapiKvmx86CreateResultInt'>
print(isinstance(new_vm_id, int)) # True
```
</details>
Исключением является булев тип данных. Так как наследование от класса `bool` в Python недопустимо, функция API SDK возвращает экземпляр класса, который обладает функциональностью для использования в булевых выражениях (реализован метод `__bool__`), но само булево значение хранится в атрибуте `value`:
<details><summary>Класс с булевым значением</summary>
```python
from dynamix_sdk import Dynamix
dx = Dynamix(...)
# /cloudapi/compute/delete
delete_result = dx.api.cloudapi.compute.delete(compute_id=1)
print(type(delete_result)) # <class '....CloudapiComputeDeleteResultBool'>
print(type(delete_result.value)) # <class 'bool'>
```
Использование в булевом выражении:
```python
from dynamix_sdk import Dynamix
dx = Dynamix(...)
# /cloudapi/compute/delete
if dx.api.cloudapi.compute.delete(compute_id=1):
print('The VM has been deleted.')
```
</details>
Все объекты, возвращаемые функциями API SDK, обладают общими специальными атрибутами:
| Атрибут | Описание |
| --- | --- |
| `_api_params` | Модель Pydantic, описывающая параметры, переданные в функцию. |
| `_http_response` | HTTP-ответ API платформы в виде экземпляра класса `requests.Response`. |
#### Соответствие названий
В SDK названия из API платформы приведены к стилю "snake_case", принятому в Python, а в некоторых случаях имеют иные отличия, при этом сохраняя смысл исходного названия в API платформы.
Для соответствия названий в SDK используются таблицы соответствия, с помощью которых можно узнать исходное название в API платформы.
<br>Каждая такая таблица хранится в виде словаря в файле YAML:
- [api/path_mapping.yml](/src/dynamix_sdk/api/path_mapping.yml) - таблица соответствия названий частей маршрута (URL path) к функции API.
<br>Данная таблица содержит только те названия, которые отличаются от названий в API платформы.
- [api/name_mapping.yml](/src/dynamix_sdk/api/name_mapping.yml) - таблица соответствия названий параметров функций и полей возвращаемых данных.
<br>Данная таблица содержит все названия, в том числе и те, которые совпадают с названиями в API платформы.
<br>Кроме общих соответствий, данная таблица также содержит индивидуальные соответствия, которые применяются только к классу модели, указанному в формате `sdk_name__model_class_name`.
### Типы данных SDK
Все типы данных (классы) SDK, которые могут пригодиться при разработке ПО с использованием SDK, доступны через модуль `dynamix_sdk.types`.
### Ошибки и исключения (exceptions)
SDK может вызывать исключения, которые должны быть обработаны в соответствии с требованиями к разрабатываемому вами ПО.
#### Ошибки валидации данных
SDK производит валидацию параметров функций API с помощью библиотеки **Pydantic**.<br>
Поэтому, при ошибке валидации, будет вызвано исключение `pydantic.ValidationError`.
Таким же образом валидируются данные HTTP-ответов API платформы. Причиной ошибки валидации данных HTTP-ответа API платформы может быть:
1. Несоответствие версии SDK и версии платформы.
1. Программная ошибка, допущенная при разработке SDK или платформы. Получить подтверждение наличия такой ошибки, а также ускорить её устранение, можно сообщив о ней.
Получить подробную информацию об исключении `pydantic.ValidationError` можно [в соответствующем разделе официальной документации Pydantic](https://docs.pydantic.dev/latest/errors/errors/) (ссылка ведёт на документацию для последней версии, поэтому важно выбрать версию, соответствующую используемой в SDK).
#### Ошибки HTTP
SDK для выполнения HTTP-запросов использует библиотеку **Requests**.<br>
Поэтому, при ошибке HTTP-подключения, будет вызвано соответствующее исключение библиотеки **Requests**.
Для проверки соответствует ли код ответа успешному выполнению запроса, SDK вызывает метод `requests.Response.raise_for_status()`, поэтому при неуспешном HTTP-запросе также будет вызвано исключение библиотеки **Requests**.
**Примечание:** если код ответа 503, то SDK предпримет несколько повторных попыток. Подробнее в разделе [Конфигурация](#конфигурация).
<details><summary>Обработка ошибок HTTP</summary>
```python
from requests.exceptions import RequestException, HTTPError
from dynamix_sdk import Dynamix
dx = Dynamix(...)
vm_id = 1
try:
get_result = dx.api.cloudapi.compute.get(compute_id=vm_id)
except HTTPError as e:
resp = e.response
if resp.status_code == 404:
print(f'The VM ID={vm_id} not found.')
else:
print(f'{e}: {resp.text}')
except RequestException as e:
print(e)
```
</details>
Подробную информацию об исключениях библиотеки **Requests** можно получить в [соответствующем разделе официальной документации Requests](https://requests.readthedocs.io/en/latest/user/quickstart/#errors-and-exceptions).
## Доступный функционал
### Способы авторизации
- с JWT
- через DECS3O
- через BVS
Подробнее в разделе [Конфигурация](#конфигурация).
### Функции API
#### Cloudapi
<details><summary>account</summary>
- /cloudapi/account/addUser
- /cloudapi/account/deleteUser
- /cloudapi/account/disable
- /cloudapi/account/enable
- /cloudapi/account/updateUser
</details>
<details><summary>compute</summary>
- /cloudapi/compute/delete
- /cloudapi/compute/get
- /cloudapi/compute/list
- /cloudapi/compute/update
</details>
<details><summary>kvmx86</summary>
- /cloudapi/kvmx86/create
</details>
<details><summary>rg</summary>
- /cloudapi/rg/create
- /cloudapi/rg/get
- /cloudapi/rg/list
</details>
<details><summary>user</summary>
- /cloudapi/user/get
</details>
#### System
<details><summary>usermanager</summary>
- /system/usermanager/whoami
</details>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

@ -0,0 +1,27 @@
[project]
name = "dynamix-sdk"
authors = [{name = "Dmitriy Smirnov"}]
version = "1.0.0"
readme = "README.md"
requires-python = ">=3.10.12"
dependencies = [
"requests>=2.32.3",
"pydantic>=2.10.5",
"pyyaml>=6.0.2",
]
[project.urls]
Repository = "https://repository.basistech.ru/BASIS/dynamix-python-sdk"
[build-system]
requires = [
"setuptools",
"setuptools-scm",
]
build-backend = "setuptools.build_meta"
[tool.setuptools.package-data]
src = ["*.yml"]

@ -0,0 +1 @@
pre-commit==4.1.0

@ -0,0 +1,3 @@
from dynamix_sdk.decs3o import DECS3OAuth
from dynamix_sdk.bvs import BVSAuth
from dynamix_sdk.dynamix import Dynamix

@ -0,0 +1,2 @@
from ._api import *
from ._nested import *

@ -0,0 +1,8 @@
import dynamix_sdk.base as _base
from .cloudapi import *
from .system import *
class API(_base.BaseAPI):
cloudapi: CloudapiAPI
system: SystemAPI

@ -0,0 +1,3 @@
from .params import *
from .result import *
from .enums import *

@ -0,0 +1,161 @@
from dynamix_sdk.utils import (
AutoNameEnum as _AutoNameEnum,
enum_auto as _enum_auto,
)
class AccessType(str, _AutoNameEnum):
ARCXDU = _enum_auto()
CXDRAU = _enum_auto()
RCX = _enum_auto()
R = _enum_auto()
class AffinityMode(str, _AutoNameEnum):
ANY = _enum_auto()
EQ = _enum_auto()
NE = _enum_auto()
class AffinityPolicy(str, _AutoNameEnum):
RECOMMENDED = _enum_auto()
REQUIRED = _enum_auto()
class AffinityTopology(str, _AutoNameEnum):
compute = _enum_auto()
node = _enum_auto()
class BootDevice(str, _AutoNameEnum):
cdrom = _enum_auto()
hd = _enum_auto()
network = _enum_auto()
class Chipset(str, _AutoNameEnum):
Q35 = _enum_auto()
i440fx = _enum_auto()
class Driver(str, _AutoNameEnum):
KVM_X86 = _enum_auto()
SVA_KVM_X86 = _enum_auto()
class NetType(str, _AutoNameEnum):
DPDK = _enum_auto()
EMPTY = _enum_auto()
EXTNET = _enum_auto()
VFNIC = _enum_auto()
VINS = _enum_auto()
class NumaAffinity(str, _AutoNameEnum):
none = _enum_auto()
strict = _enum_auto()
loose = _enum_auto()
class TXMode(str, _AutoNameEnum):
iothread = _enum_auto()
selected_by_hypervisor = 'selected by hypervisor'
timer = _enum_auto()
class Ioeventfd(str, _AutoNameEnum):
off = _enum_auto()
on = _enum_auto()
selected_by_hypervisor = 'selected by hypervisor'
class EventIdx(str, _AutoNameEnum):
off = _enum_auto()
on = _enum_auto()
selected_by_hypervisor = 'selected by hypervisor'
class ResourceGroupStatus(str, _AutoNameEnum):
CREATED = _enum_auto()
DELETED = _enum_auto()
DESTROYED = _enum_auto()
DESTROYING = _enum_auto()
DISABLED = _enum_auto()
DISABLING = _enum_auto()
ENABLED = _enum_auto()
ENABLING = _enum_auto()
MODELED = _enum_auto()
RESTORING = _enum_auto()
class LockStatus(str, _AutoNameEnum):
LOCKED = _enum_auto()
UNLOCKED = _enum_auto()
class ComputeFeature(str, _AutoNameEnum):
cpupin = _enum_auto()
dpdk = _enum_auto()
hugepages = _enum_auto()
numa = _enum_auto()
vfnic = _enum_auto()
class RGDefaultNetType(str, _AutoNameEnum):
NONE = _enum_auto()
PRIVATE = _enum_auto()
PUBLIC = _enum_auto()
class RGResourceType(str, _AutoNameEnum):
compute = _enum_auto()
k8s = _enum_auto()
lb = _enum_auto()
vins = _enum_auto()
class DiskStatus(str, _AutoNameEnum):
ASSIGNED = _enum_auto()
CREATED = _enum_auto()
DELETED = _enum_auto()
DESTROYED = _enum_auto()
DESTROYING = _enum_auto()
MODELED = _enum_auto()
PURGED = _enum_auto()
REPLICATION = _enum_auto()
class DiskTechStatus(str, _AutoNameEnum):
ALLOCATED = _enum_auto()
UNALLOCATED = _enum_auto()
class VMStatus(str, _AutoNameEnum):
CREATED = _enum_auto()
DELETED = _enum_auto()
DELETING = _enum_auto()
DESTROYED = _enum_auto()
DESTROYING = _enum_auto()
DISABLED = _enum_auto()
ENABLED = _enum_auto()
MODELED = _enum_auto()
REDEPLOYING = _enum_auto()
class VMTechStatus(str, _AutoNameEnum):
BACKUP_RUNNING = _enum_auto()
BACKUP_STOPPED = _enum_auto()
DOWN = _enum_auto()
MIGRATING = _enum_auto()
PAUSED = _enum_auto()
PAUSING = _enum_auto()
SCHEDULED = _enum_auto()
STARTED = _enum_auto()
STARTING = _enum_auto()
STOPPED = _enum_auto()
STOPPING = _enum_auto()
class DiskType(str, _AutoNameEnum):
B = _enum_auto()
D = _enum_auto()

@ -0,0 +1,20 @@
import dynamix_sdk.base as _base
from . import enums as _enums
class DiskAPIParamsNM(_base.BaseAPIParamsNestedModel):
name: str
size: int
description: None | str = None
image_id: None | int = None
sep_id: None | int = None
sep_pool_name: None | str = None
class InterfaceAPIParamsNM(_base.BaseAPIParamsNestedModel):
net_id: int
net_type: _enums.NetType
ip_addr: None | str = None
mtu: None | int = None

@ -0,0 +1,343 @@
import dynamix_sdk.base as _base
from dynamix_sdk.utils import EmptyStr as _EmptyStr
from . import enums as _enums
class AccessAPIResultNM(_base.BaseAPIResultNestedModel):
explicit: bool
guid: str
access_type: _enums.AccessType
status: str
type: str
user_group_id: str
class AffinityRuleAPIResultNM(_base.BaseAPIResultNestedModel):
guid: str
key: str
mode: _enums.AffinityMode
policy: _enums.AffinityPolicy
topology: _enums.AffinityTopology
value: str
class QOSAPIResultNM(_base.BaseAPIResultNestedModel):
egress_rate: int
guid: str
ingress_burst: int
ingress_rate: int
class VMACLAPIResultNM(_base.BaseAPIResultNestedModel):
account: list[AccessAPIResultNM]
compute: list[AccessAPIResultNM]
rg: list[AccessAPIResultNM]
class LibvirtSettingsAPIResultNM(_base.BaseAPIResultNestedModel):
event_idx: _EmptyStr | _enums.EventIdx
guid: str
ioeventfd: _EmptyStr | _enums.Ioeventfd
queues: int
rx_queue_size: int
tx_mode: _EmptyStr | _enums.TXMode
tx_queue_size: int
class BaseInterfaceAPIResultNM(_base.BaseAPIResultNestedModel):
bus_number: int
conn_id: int
conn_type: str
default_gw: str
enabled: bool
flip_group_id: int
guid: str
ip_addr: str
libvirt_settings: LibvirtSettingsAPIResultNM
listen_ssh: bool
mac: str
mtu: int
name: str
net_id: int
net_mask: int
net_type: _enums.NetType
node_id: int
pci_slot: int
target: str
type: str
vnf_ids: list[int]
class InterfaceAPIResultNM(BaseInterfaceAPIResultNM):
qos: QOSAPIResultNM
class OSUserAPIResultNM(_base.BaseAPIResultNestedModel):
login: str
password: str
pub_key: str
guid: str
class SnapshotSetAPIResultNM(_base.BaseAPIResultNestedModel):
disks: list[int]
guid: str
label: str
timestamp: int
@property
def datetime(self):
return self._get_datetime_from_timestamp(self.timestamp)
class IOTuneAPIResultNM(_base.BaseAPIResultNestedModel):
read_bytes_sec: None | int = None
read_bytes_sec_max: None | int = None
read_iops_sec: None | int = None
read_iops_sec_max: None | int = None
size_iops_sec: None | int = None
total_bytes_sec: None | int = None
total_bytes_sec_max: None | int = None
total_iops_sec: None | int = None
total_iops_sec_max: None | int = None
write_bytes_sec: None | int = None
write_bytes_sec_max: None | int = None
write_iops_sec: None | int = None
write_iops_sec_max: None | int = None
class SnapshotAPIResultNM(_base.BaseAPIResultNestedModel):
guid: str
label: str
reference_id: str
res_id: str
snapshot_set_guid: str
snapshot_set_timestamp: int
timestamp: int
@property
def datetime(self):
return self._get_datetime_from_timestamp(self.timestamp)
@property
def snapshot_set_datetime(self):
return self._get_datetime_from_timestamp(self.snapshot_set_timestamp)
class ReplicationAPIResultNM(_base.BaseAPIResultNestedModel):
disk_id: int
pool_id: str
role: str
self_volume_id: str
storage_id: str
volume_id: str
class BaseDiskAPIResultNM(_base.BaseAPIResultNestedModel):
bus_number: int
id: int
pci_slot: int
class DiskAPIResultNM(BaseDiskAPIResultNM):
account_id: int
acl: dict
boot_partition: int
created_timestamp: int
deleted_timestamp: int
description: str
destruction_timestamp: int
disk_path: str
grid_id: int
guid: int
image_id: int
images: list
io_tune: IOTuneAPIResultNM
iqn: str
login: str
milestones: int
name: str
order: int
params: str
parent_id: int
password: str
present_to: list[int]
purge_timestamp: int
reality_device_number: int
replication_dict: dict
res_id: str
role: str
sep_id: int
sep_pool_name: str
shareable: bool
size_max: int
size_used: float
snapshots: list[SnapshotAPIResultNM]
status: _enums.DiskStatus
tech_status: _enums.DiskTechStatus
type: _enums.DiskType
updated_timestamp: int = 0
@property
def created_datetime(self):
return self._get_datetime_from_timestamp(self.created_timestamp)
@property
def deleted_datetime(self):
return self._get_datetime_from_timestamp(self.deleted_timestamp)
@property
def destruction_datetime(self):
return self._get_datetime_from_timestamp(self.destruction_timestamp)
@property
def updated_datetime(self):
return self._get_datetime_from_timestamp(self.updated_timestamp)
@property
def purge_datetime(self):
return self._get_datetime_from_timestamp(self.purge_timestamp)
@property
def replication(self):
if self.replication_dict:
return ReplicationAPIResultNM(**self.replication_dict)
class TenantQuotasAPIResultNM(_base.BaseAPIResultNestedModel):
cpu: int
disk_size: int
ext_traffic: int
gpu: int
public_ip: int
ram: float | int
storage: int
class ResourceGroupAPIResultNM(_base.BaseAPIResultNestedModel):
account_id: int
account_name: str
acl: list[AccessAPIResultNM]
compute_features: list[_enums.ComputeFeature]
cpu_allocation_parameter: str
cpu_allocation_ratio: int
created_by: str
created_timestamp: int
default_net_id: int
default_net_type: _enums.RGDefaultNetType
deleted_by: str
deleted_timestamp: int
description: str
grid_id: int
guid: int
id: int
lock_status: _enums.LockStatus
milestones: int
name: str
quotas: TenantQuotasAPIResultNM
register_computes: bool
resource_types: list[_enums.RGResourceType]
secret: str
status: _enums.ResourceGroupStatus
uniq_pools: list[str]
updated_by: str
updated_timestamp: int
vins_ids: list[int]
vm_ids: list[int]
dirty: bool | None = None
@property
def created_datetime(self):
return self._get_datetime_from_timestamp(self.created_timestamp)
@property
def deleted_datetime(self):
return self._get_datetime_from_timestamp(self.deleted_timestamp)
@property
def updated_datetime(self):
return self._get_datetime_from_timestamp(self.updated_timestamp)
class BaseVMAPIResultNM(_base.BaseAPIResultNestedModel):
account_id: int
account_name: str
affinity_label: str
affinity_rules: list[AffinityRuleAPIResultNM]
affinity_weight: int
anti_affinity_rules: list[AffinityRuleAPIResultNM]
arch: str
auto_start: bool
boot_disk_size: int
boot_order: list[_enums.BootDevice]
cd_image_id: int
chipset: _enums.Chipset
clone_ids: list[int]
clone_reference: int
compute_ci_id: int
cpu: int
cpu_pin: bool
created_by: str
created_timestamp: int
custom_fields: dict
deleted_by: str
deleted_timestamp: int
description: str
devices: dict
driver: _enums.Driver
gpu_ids: list[int]
grid_id: int
guid: int
hp_backed: bool
id: int
image_id: int
lock_status: _enums.LockStatus
manager_id: int
manager_type: str
migration_job: int
milestones: int
name: str
need_reboot: bool
numa_affinity: _enums.NumaAffinity
numa_node_id: int
pinned: bool
preferred_cpu_cores: list[int]
ram: int
reference_id: str
registered: bool
res_name: str
reserved_cpu_cores: list[int]
rg_id: int
rg_name: str
snapshot_sets: list[SnapshotSetAPIResultNM]
stateless_sep_id: int
stateless_sep_type: str
status: _enums.VMStatus
tags: dict
tech_status: _enums.VMTechStatus
updated_by: str
updated_timestamp: int
user_managed: bool
virtual_image_id: int
@property
def created_datetime(self):
return self._get_datetime_from_timestamp(self.created_timestamp)
@property
def deleted_datetime(self):
return self._get_datetime_from_timestamp(self.deleted_timestamp)
@property
def updated_datetime(self):
return self._get_datetime_from_timestamp(self.updated_timestamp)
class VMAPIResultNM(BaseVMAPIResultNM):
acl: list # BDX-7995
disks: list[BaseDiskAPIResultNM]
interfaces: list[BaseInterfaceAPIResultNM]
total_disks_size: int
vins_count: int

@ -0,0 +1,14 @@
import dynamix_sdk.base as _base
from .account import *
from .compute import *
from .kvmx86 import *
from .rg import *
from .user import *
class CloudapiAPI(_base.BaseAPI):
account: CloudapiAccountAPI
compute: CloudapiComputeAPI
kvmx86: CloudapiKvmx86API
rg: CloudapiRgAPI
user: CloudapiUserAPI

@ -0,0 +1,17 @@
import dynamix_sdk.base as _base
from .add_user import *
from .delete_user import *
from .disable import *
from .enable import *
from .update_user import *
class CloudapiAccountAPI(
_base.BaseAPI,
CloudapiAccountAddUserProtocol,
CloudapiAccountDeleteUserProtocol,
CloudapiAccountDisableProtocol,
CloudapiAccountEnableProtocol,
CloudapiAccountUpdateUserProtocol,
):
pass

@ -0,0 +1,17 @@
import dynamix_sdk.base as _base
import dynamix_sdk.api._nested as _nested
class CloudapiAccountAddUserResultBool(_base.BaseAPIResultBool):
pass
class CloudapiAccountAddUserProtocol(_base.BasePostAPIFunctionProtocol):
def add_user(
self,
*,
account_id: int,
access_type: _nested.AccessType,
user_id: str,
) -> CloudapiAccountAddUserResultBool:
...

@ -0,0 +1,15 @@
import dynamix_sdk.base as _base
class CloudapiAccountDeleteUserResultBool(_base.BaseAPIResultBool):
pass
class CloudapiAccountDeleteUserProtocol(_base.BasePostAPIFunctionProtocol):
def delete_user(
self,
*,
account_id: int,
user_id: str,
) -> CloudapiAccountDeleteUserResultBool:
...

@ -0,0 +1,14 @@
import dynamix_sdk.base as _base
class CloudapiAccountDisableResultBool(_base.BaseAPIResultBool):
pass
class CloudapiAccountDisableProtocol(_base.BasePostAPIFunctionProtocol):
def disable(
self,
*,
account_id: int,
) -> CloudapiAccountDisableResultBool:
...

@ -0,0 +1,14 @@
import dynamix_sdk.base as _base
class CloudapiAccountEnableResultBool(_base.BaseAPIResultBool):
pass
class CloudapiAccountEnableProtocol(_base.BasePostAPIFunctionProtocol):
def enable(
self,
*,
account_id: int,
) -> CloudapiAccountEnableResultBool:
...

@ -0,0 +1,17 @@
import dynamix_sdk.base as _base
import dynamix_sdk.api._nested as _nested
class CloudapiAccountUpdateUserResultBool(_base.BaseAPIResultBool):
pass
class CloudapiAccountUpdateUserProtocol(_base.BasePostAPIFunctionProtocol):
def update_user(
self,
*,
account_id: int,
access_type: _nested.AccessType,
user_id: str,
) -> CloudapiAccountUpdateUserResultBool:
...

@ -0,0 +1,15 @@
import dynamix_sdk.base as _base
from .delete import *
from .get import *
from .list import *
from .update import *
class CloudapiComputeAPI(
_base.BaseAPI,
CloudapiComputeDeleteProtocol,
CloudapiComputeGetProtocol,
CloudapiComputeListProtocol,
CloudapiComputeUpdateProtocol,
):
pass

@ -0,0 +1,17 @@
import dynamix_sdk.base as _base
class CloudapiComputeDeleteResultBool(_base.BaseAPIResultBool):
pass
class CloudapiComputeDeleteProtocol(_base.BasePostAPIFunctionProtocol):
def delete(
self,
*,
compute_id: int,
detach_disks: bool = False,
permanently: bool = False,
) -> CloudapiComputeDeleteResultBool:
...

@ -0,0 +1,31 @@
import dynamix_sdk.base as _base
import dynamix_sdk.api._nested as _nested
class CloudapiComputeGetResultModel(
_base.BaseAPIResultModel,
_nested.BaseVMAPIResultNM,
):
acl: _nested.VMACLAPIResultNM
ci_user_data: dict
disks: list[_nested.DiskAPIResultNM]
image_name: None | str
interfaces: list[_nested.InterfaceAPIResultNM]
os_users: list[_nested.OSUserAPIResultNM]
virtual_image_name: None | str
vns_password: str
natable_vins_id: int = 0
natable_vins_ip: str = ''
natable_vins_name: str = ''
natable_vins_network: str = ''
natable_vins_network_name: str = ''
class CloudapiComputeGetProtocol(_base.BasePostAPIFunctionProtocol):
def get(
self,
*,
compute_id: int,
) -> CloudapiComputeGetResultModel:
...

@ -0,0 +1,29 @@
import dynamix_sdk.base as _base
import dynamix_sdk.api._nested as _nested
class CloudapiComputeListResultModel(_base.BaseAPIResultModel):
data: list[_nested.VMAPIResultNM]
entry_count: int
class CloudapiComputeListProtocol(_base.BasePostAPIFunctionProtocol):
def list(
self,
*,
account_id: int | None = None,
ext_net_id: int | None = None,
ext_net_name: str | None = None,
id: int | None = None,
include_deleted: bool = False,
ip_addr: str | None = None,
name: str | None = None,
page_number: int | None = None,
page_size: int | None = None,
rg_id: int | None = None,
rg_name: str | None = None,
sort_by: str | None = None,
status: _nested.VMStatus | None = None,
tech_status: _nested.VMTechStatus | None = None,
) -> CloudapiComputeListResultModel:
...

@ -0,0 +1,23 @@
import dynamix_sdk.base as _base
import dynamix_sdk.api._nested as _nested
class CloudapiComputeUpdateResultBool(_base.BaseAPIResultBool):
pass
class CloudapiComputeUpdateProtocol(_base.BasePostAPIFunctionProtocol):
def update(
self,
*,
compute_id: int,
auto_start: None | bool = None,
chipset: None | _nested.Chipset = None,
cpu_pin: None | bool = None,
description: None | str = None,
hp_backed: None | bool = None,
name: None | str = None,
numa_affinity: None | _nested.NumaAffinity = None,
preferred_cpu_cores: None | list[int] = None,
) -> CloudapiComputeUpdateResultBool:
...

@ -0,0 +1,9 @@
import dynamix_sdk.base as _base
from .create import *
class CloudapiKvmx86API(
_base.BaseAPI,
CloudapiKvmx86CreateProtocol,
):
pass

@ -0,0 +1,37 @@
import dynamix_sdk.base as _base
import dynamix_sdk.api._nested as _nested
class CloudapiKvmx86CreateResultInt(_base.BaseAPIResultInt):
pass
class CloudapiKvmx86CreateProtocol(_base.BasePostAPIFunctionProtocol):
def create(
self,
*,
cpu: int,
name: str,
ram: int,
rg_id: int,
boot_disk_size: None | int = None,
chipset: _nested.Chipset = _nested.Chipset.i440fx,
ci_user_data: None | dict = None,
cpu_pin: bool = False,
custom_fields: None | str = None,
data_disks: None | list[_nested.DiskAPIParamsNM] = None,
description: None | str = None,
driver: _nested.Driver = _nested.Driver.KVM_X86,
hp_backed: bool = False,
image_id: None | int = None,
interfaces: None | list[_nested.InterfaceAPIParamsNM] = None,
ipa_type: None | str = None,
numa_affinity: _nested.NumaAffinity = _nested.NumaAffinity.none,
preferred_cpu_cores: None | list[int] = None,
sep_id: None | int = None,
sep_pool_name: None | str = None,
start: bool = True,
system_name: None | str = None,
without_boot_disk: bool = False,
) -> CloudapiKvmx86CreateResultInt:
...

@ -0,0 +1,13 @@
import dynamix_sdk.base as _base
from .create import *
from .get import *
from .list import *
class CloudapiRgAPI(
_base.BaseAPI,
CloudapiRgCreateProtocol,
CloudapiRgGetProtocol,
CloudapiRgListProtocol,
):
pass

@ -0,0 +1,31 @@
import dynamix_sdk.base as _base
import dynamix_sdk.api._nested as _nested
class CloudapiRgCreateResultBoolInt(_base.BaseAPIResultInt):
pass
class CloudapiRgCreateProtocol(_base.BasePostAPIFunctionProtocol):
def create(
self,
*,
account_id: int,
grid_id: int,
name: str,
cpu_quota: int = -1,
default_net_type: _nested.RGDefaultNetType = (
_nested.RGDefaultNetType.PRIVATE
),
description: str | None = None,
ext_net_id: int = 0,
ext_net_ip: str | None = None,
ext_traffic_quota: int = -1,
ip_cidr: str | None = None,
owner: str | None = None,
public_ip_quota: int = -1,
ram_quota: int = -1,
register_computes: bool = False,
storage_quota: int = -1,
) -> CloudapiRgCreateResultBoolInt:
...

@ -0,0 +1,18 @@
import dynamix_sdk.base as _base
import dynamix_sdk.api._nested as _nested
class CloudapiRgGetResultModel(
_base.BaseAPIResultModel,
_nested.ResourceGroupAPIResultNM,
):
pass
class CloudapiRgGetProtocol(_base.BasePostAPIFunctionProtocol):
def get(
self,
*,
rg_id: int,
) -> CloudapiRgGetResultModel:
...

@ -0,0 +1,27 @@
import dynamix_sdk.base as _base
import dynamix_sdk.api._nested as _nested
class CloudapiRgListResultModel(_base.BaseAPIResultModel):
data: list[_nested.ResourceGroupAPIResultNM]
entry_count: int
class CloudapiRgListProtocol(_base.BasePostAPIFunctionProtocol):
def list(
self,
*,
account_id: int | None = None,
account_name: str | None = None,
created_after_timestamp: int | None = None,
created_before_timestamp: int | None = None,
id: int | None = None,
include_deleted: bool = False,
lock_status: _nested.LockStatus | None = None,
name: str | None = None,
page_number: int | None = None,
page_size: int | None = None,
sort_by: str | None = None,
status: _nested.ResourceGroupStatus | None = None,
) -> CloudapiRgListResultModel:
...

@ -0,0 +1,9 @@
import dynamix_sdk.base as _base
from .get import *
class CloudapiUserAPI(
_base.BaseAPI,
CloudapiUserGetProtocol,
):
pass

@ -0,0 +1,12 @@
import dynamix_sdk.base as _base
class CloudapiUserGetResultModel(_base.BaseAPIResultModel):
data: dict
email_addresses: list[str]
user_name: str
class CloudapiUserGetProtocol(_base.BasePostAPIFunctionProtocol):
def get(self, *, user_name: str) -> CloudapiUserGetResultModel:
...

@ -0,0 +1,224 @@
# sdk_name[__model_class_name]: dynamix_name
access_type: accesstype
access_type__AccessAPIResultNM: right
account: accountAcl
account_id: accountId
account_name: accountName
acl: acl
acl__CloudapiComputeGetResultModel: ACL
admin: admin
affinity_label: affinityLabel
affinity_rules: affinityRules
affinity_weight: affinityWeight
anti_affinity_rules: antiAffinityRules
arch: arch
auto_start: autoStart
boot_disk_size: bootdiskSize
boot_disk_size__CloudapiKvmx86CreateParamsModel: bootDisk
boot_order: bootOrder
boot_partition: bootPartition
bus_number: bus_number
cd_image_id: cdImageId
chipset: chipset
ci_user_data: userdata
clone_ids: clones
clone_reference: cloneReference
compute: computeAcl
compute_ci_id: computeciId
compute_features: computeFeatures
compute_id: computeId
conn_id: connId
conn_type: connType
cpu: cpu
cpu__CloudapiComputeGetResultModel: cpus
cpu__TenantQuotasAPIResultNM: CU_C
cpu__VMAPIResultNM: cpus
cpu_allocation_parameter: cpu_allocation_parameter
cpu_allocation_ratio: cpu_allocation_ratio
cpu_pin: cpupin
cpu_quota: maxCPUCapacity
created_after_timestamp: createdAfter
created_before_timestamp: createdBefore
created_by: createdBy
created_timestamp: createdTime
custom_fields: customFields
data: data
data_disks: dataDisks
default_gw: defGw
default_net_id: def_net_id
default_net_type: def_net_type
default_net_type__CloudapiRgCreateParamsModel: def_net
deleted_by: deletedBy
deleted_timestamp: deletedTime
description: desc
destruction_timestamp: destructionTime
detach_disks: detachDisks
devices: devices
dirty: dirty
disk_id: diskId
disk_path: diskPath
disk_size: CU_D
disks: disks
driver: driver
egress_rate: eRate
email_addresses: emailaddresses
enabled: enabled
entry_count: entryCount
event_idx: event_idx
explicit: explicit
ext_net_id: extNetId
ext_net_ip: extIp
ext_net_name: extNetName
ext_traffic: CU_NP
ext_traffic_quota: maxNetworkPeerTransfer
flip_group_id: flipgroupId
gpu: gpu_units
gpu_ids: vgpus
grid_id: gid
guid: guid
hp_backed: hpBacked
id: id
id__CloudapiComputeListParamsModel: by_id
id__CloudapiRgListParamsModel: by_id
image_id: imageId
image_name: imageName
images: images
include_deleted: includedeleted
ingress_burst: inBurst
ingress_rate: inRate
interfaces: interfaces
io_tune: iotune
ioeventfd: ioeventfd
ip_addr: ipAddress
ip_addr__InterfaceAPIParamsNM: ipAddr
ip_cidr: ipcidr
ipa_type: ipaType
iqn: iqn
key: key
label: label
libvirt_settings: libvirtSettings
listen_ssh: listenSsh
lock_status: lockStatus
login: login
mac: mac
manager_id: managerId
manager_type: managerType
migration_job: migrationjob
milestones: milestones
mode: mode
mtu: mtu
name: name
name__DiskAPIParamsNM: diskName
natable_vins_id: natableVinsId
natable_vins_ip: natableVinsIp
natable_vins_name: natableVinsName
natable_vins_network: natableVinsNetwork
natable_vins_network_name: natableVinsNetworkName
need_reboot: needReboot
net_id: netId
net_mask: netMask
net_type: netType
node_id: nodeId
numa_affinity: numaAffinity
numa_node_id: numaNodeId
order: order
os_users: osUsers
owner: owner
page_number: page
page_size: size
params: params
parent_id: parentId
password: password
password__DiskAPIResultNM: passwd
pci_slot: pciSlot
permanently: permanently
pinned: pinned
policy: policy
pool_id: poolId
preferred_cpu_cores: preferredCpu
present_to: presentTo
pub_key: pubkey
public_ip: CU_I
public_ip_quota: maxNumPublicIP
purge_timestamp: purgeTime
qos: qos
queues: queues
quotas: resourceLimits
ram: ram
ram__TenantQuotasAPIResultNM: CU_M
ram_quota: maxMemoryCapacity
read_bytes_sec: read_bytes_sec
read_bytes_sec_max: read_bytes_sec_max
read_iops_sec: read_iops_sec
read_iops_sec_max: read_iops_sec_max
reality_device_number: realityDeviceNumber
reference_id: referenceId
register_computes: registerComputes
registered: registered
replication_dict: replication
res_id: resId
res_name: resName
reserved_cpu_cores: reservedNodeCpus
resource_types: resourceTypes
rg: rgAcl
rg_id: rgId
rg_name: rgName
role: role
roles: roles
rx_queue_size: rx_queue_size
secret: secret
self_volume_id: selfVolumeId
sep_id: sepId
sep_pool_name: pool
shareable: shareable
size__DiskAPIParamsNM: size
size_iops_sec: size_iops_sec
size_max: sizeMax
size_used: sizeUsed
snapshot_set_guid: snapSetGuid
snapshot_set_timestamp: snapSetTime
snapshot_sets: snapSets
snapshots: snapshots
sort_by: sortBy
start: start
stateless_sep_id: statelessSepId
stateless_sep_type: statelessSepType
status: status
storage: CU_DM
storage_id: storageId
storage_quota: maxVDiskCapacity
system_name: IS
tags: tags
target: target
tech_status: techStatus
timestamp: timestamp
topology: topology
total_bytes_sec: total_bytes_sec
total_bytes_sec_max: total_bytes_sec_max
total_disks_size: totalDisksSize
total_iops_sec: total_iops_sec
total_iops_sec_max: total_iops_sec_max
tx_mode: txmode
tx_queue_size: tx_queue_size
type: type
uniq_pools: uniqPools
updated_by: updatedBy
updated_timestamp: updatedTime
user_group_id: userGroupId
user_id: userId
user_managed: userManaged
user_name: username
value: value
vins_count: vinsConnected
vins_ids: vins
virtual_image_id: virtualImageId
virtual_image_name: virtualImageName
vm_ids: vms
vnf_ids: vnfs
vns_password: vncPasswd
volume_id: volumeId
without_boot_disk: withoutBootDisk
write_bytes_sec: write_bytes_sec
write_bytes_sec_max: write_bytes_sec_max
write_iops_sec: write_iops_sec
write_iops_sec_max: write_iops_sec_max

@ -0,0 +1,4 @@
# sdk_path_part: dynamix_path_part
add_user: addUser
delete_user: deleteUser
update_user: updateUser

@ -0,0 +1,6 @@
import dynamix_sdk.base as _base
from .usermanager import *
class SystemAPI(_base.BaseAPI):
usermanager: SystemUsermanagerAPI

@ -0,0 +1,9 @@
import dynamix_sdk.base as _base
from .whoami import *
class SystemUsermanagerAPI(
_base.BaseAPI,
SystemUsermanagerWhoamiProtocol,
):
pass

@ -0,0 +1,12 @@
import dynamix_sdk.base as _base
class SystemUsermanagerWhoamiResultModel(_base.BaseAPIResultModel):
admin: bool
name: str
roles: list[str]
class SystemUsermanagerWhoamiProtocol(_base.BasePostAPIFunctionProtocol):
def whoami(self) -> SystemUsermanagerWhoamiResultModel:
...

@ -0,0 +1,490 @@
from abc import ABC, abstractmethod
from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime
import inspect
import os
import re
import time
from types import GenericAlias, UnionType
from typing import Any, Literal, ParamSpec, Protocol, TypeVar, get_args
import requests
from pydantic import (
AliasGenerator,
BaseModel as PydanticBaseModel,
ConfigDict,
PrivateAttr,
create_model,
)
import yaml
from dynamix_sdk.config import Config, ConfigWithAuth
from dynamix_sdk.utils import HTTPMethod, gen_cls_name_from_url_path
NAME_MAPPING_FILE_PATH = os.path.join(
os.path.dirname(__file__),
'api',
'name_mapping.yml',
)
PATH_MAPPING_FILE_PATH = os.path.join(
os.path.dirname(__file__),
'api',
'path_mapping.yml',
)
def read_mapping_file(file_path: str):
with open(file_path) as file:
parsed_data: Any = yaml.safe_load(file)
if not isinstance(parsed_data, dict):
raise TypeError
result_dict: dict[Any, Any] = parsed_data
with open(file_path) as file:
name_mapping_file_lines_amount = sum(
1 for s in file if s.lstrip() and not s.startswith('#')
)
if len(result_dict) < name_mapping_file_lines_amount:
raise AssertionError(
f'File {file_path} contains more code lines than'
f' keys in the parsed data. Check the file for duplicate keys.'
)
for k, v in result_dict.items():
if not isinstance(k, str) or not isinstance(v, str):
raise TypeError
result_str_dict: dict[str, str] = result_dict
return result_str_dict
path_mapping_dict = read_mapping_file(PATH_MAPPING_FILE_PATH)
name_mapping_dict = read_mapping_file(NAME_MAPPING_FILE_PATH)
common_mappings_values = [
v for k, v in name_mapping_dict.items() if '__' not in k
]
if len(common_mappings_values) > len(set(common_mappings_values)):
raise AssertionError(
f'File {NAME_MAPPING_FILE_PATH} can contain duplicate values'
f' only for individual mapping (attr_name__model_class_name),'
f' not common. Check common mappings for duplicate values.'
)
class BaseAPIFunctionProtocol(Protocol):
pass
class BasePostAPIFunctionProtocol(BaseAPIFunctionProtocol):
pass
class BaseGetAPIFunctionProtocol(BaseAPIFunctionProtocol):
pass
class BaseModel(PydanticBaseModel, ABC):
@staticmethod
def _get_datetime_from_timestamp(timestamp: float) -> None | datetime:
if timestamp > 0:
return datetime.fromtimestamp(timestamp)
class BaseModelWithFrozenStrictExtraForbid(BaseModel, ABC):
model_config = ConfigDict(
extra='forbid',
strict=True,
frozen=True,
)
class BaseAPIParamsModel(BaseModelWithFrozenStrictExtraForbid, ABC):
pass
class BaseAPIParamsNestedModel(BaseModelWithFrozenStrictExtraForbid, ABC):
def __init_subclass__(
cls,
*args: tuple[Any],
**kwargs: dict[str, Any],
) -> None:
super().__init_subclass__(*args, **kwargs)
postfix = 'APIParamsNM'
if not cls.__qualname__.endswith(postfix):
raise ValueError(
f'Name of {cls} must end with {postfix}.',
)
@dataclass
class APIResultContext:
api_params: BaseAPIParamsModel
http_response: requests.Response
class BaseAPIResult(ABC):
_api_params: BaseAPIParamsModel
_http_response: requests.Response
class BaseAPIResultModel(
BaseModelWithFrozenStrictExtraForbid,
BaseAPIResult,
ABC,
):
_api_params: BaseAPIParamsModel = PrivateAttr()
_http_response: requests.Response = PrivateAttr()
def model_post_init(self, __context: APIResultContext):
self._api_params = __context.api_params
self._http_response = __context.http_response
class BaseAPIResultNestedModel(BaseModelWithFrozenStrictExtraForbid, ABC):
pass
class BaseAPIResultBasicType(BaseAPIResult, ABC):
_frozen: bool = False
@abstractmethod
def __new__(cls, value: Any, context: APIResultContext):
return super().__new__(cls)
def __setattr__(self, name: str, value: Any) -> None:
if self._frozen:
raise AttributeError(
'Instance is frozen.',
)
return super().__setattr__(name, value)
class BaseAPIResultStr(str, BaseAPIResultBasicType, ABC):
def __new__(cls, value: str, context: APIResultContext):
instance = super().__new__(cls, value)
instance._api_params = context.api_params
instance._http_response = context.http_response
instance._frozen = True
return instance
class BaseAPIResultInt(int, BaseAPIResultBasicType, ABC):
def __new__(cls, value: int, context: APIResultContext):
instance = super().__new__(cls, value)
instance._api_params = context.api_params
instance._http_response = context.http_response
instance._frozen = True
return instance
class BaseAPIResultBool(BaseAPIResultBasicType, ABC):
value: bool
def __bool__(self):
return self.value
def __str__(self):
return f'{self.__class__.__qualname__}: {self.value}'
def __new__(cls, value: bool, context: APIResultContext):
if not isinstance(value, bool):
raise TypeError
instance = super().__new__(cls, value, context)
instance.value = value
instance._api_params = context.api_params
instance._http_response = context.http_response
instance._frozen = True
return instance
def set_alias_gen(
model_cls: type[BaseModel],
alias_type: Literal['validation', 'serialization'],
name_mapping_dict: dict[str, str]
):
if model_cls.model_config.get('alias_generator'):
return
def get_model_classes_from_annotation(
annotation: type[Any] | None,
classes: list[type[BaseModel]] | None = None,
):
_classes = classes or []
if annotation:
if isinstance(annotation, (UnionType, GenericAlias)):
for cls in get_args(annotation):
_classes += get_model_classes_from_annotation(cls)
elif issubclass(annotation, BaseModel):
_classes.append(annotation)
return _classes
for field in model_cls.model_fields.values():
for cls in get_model_classes_from_annotation(field.annotation):
set_alias_gen(
model_cls=cls,
alias_type=alias_type,
name_mapping_dict=name_mapping_dict,
)
def alias_gen(field_name: str):
individual_alias = name_mapping_dict.get(
f'{field_name}__{model_cls.__qualname__}',
)
if individual_alias:
return individual_alias
else:
if field_name in name_mapping_dict:
return name_mapping_dict[field_name]
else:
raise KeyError(
f'{field_name} not found in name mapping dictionary.'
f' Class model: {model_cls.__name__}.'
)
kwargs = {
f'{alias_type}_alias': alias_gen
}
model_cls.model_config['alias_generator'] = AliasGenerator(**kwargs)
model_cls.model_rebuild(force=True)
params_model_classes: dict[str, type[BaseAPIParamsModel]] = {}
APIParamsP = ParamSpec('APIParamsP')
APIResultT = TypeVar(
'APIResultT',
bound=BaseAPIResultModel | BaseAPIResultBasicType,
)
class BaseAPI(ABC):
_config: Config
_base_api_path: None | str
_path_mapping_dict: dict[str, str] = path_mapping_dict
_last_call_api_path: None | str = None
_name_mapping_dict: dict[str, str] = name_mapping_dict
_post_json: bool = True
def __init_subclass__(
cls,
path_mapping_dict: None | dict[str, str] = None,
name_mapping_dict: None | dict[str, str] = None,
post_json: None | bool = None,
) -> None:
if path_mapping_dict:
cls._path_mapping_dict = path_mapping_dict
if name_mapping_dict:
cls._name_mapping_dict = name_mapping_dict
if post_json is not None:
cls._post_json = post_json
def __init__(
self,
config: Config,
base_api_path: None | str = None
):
self._config = config
self._base_api_path = base_api_path
def __getattribute__(self, name: str) -> Any:
if name.startswith('_'):
return super().__getattribute__(name)
else:
path_part = self._path_mapping_dict.get(name, name)
if name in self.__annotations__:
annotation = self.__annotations__[name]
if issubclass(annotation, BaseAPI):
api_cls = annotation
return api_cls(
config=self._config,
base_api_path=(
f'{self._base_api_path or ""}/{path_part}'
)
)
else:
self._last_call_api_path = (
f'{self._base_api_path or ""}/{path_part}'
)
attr_value = super().__getattribute__(name)
if not inspect.ismethod(attr_value):
raise ValueError
return self._make_api_function(attr_value)
def _make_api_function(
self,
protocol_method: Callable[APIParamsP, APIResultT],
/,
) -> Callable[APIParamsP, APIResultT]:
return_type = inspect.signature(protocol_method).return_annotation
if not issubclass(return_type, get_args(APIResultT.__bound__)):
raise TypeError(
f'Return type annotation of {protocol_method}'
f' must be subclass of {get_args(APIResultT.__bound__)}.'
)
api_result_cls: type[APIResultT] = return_type
def api_function(
*args: APIParamsP.args,
**kwargs: APIParamsP.kwargs,
):
config = self._config
if self._last_call_api_path:
api_path = self._last_call_api_path
else:
raise ValueError
api_params_cls_name = gen_cls_name_from_url_path(
url_path=re.sub(r'[\[\]]', '', api_path),
postfix=BaseAPIParamsModel.__qualname__.removeprefix(
'BaseAPI'
),
)
api_params_cls = params_model_classes.get(
api_params_cls_name,
None,
)
if not api_params_cls:
field_definitions = {}
parameters = inspect.signature(protocol_method).parameters
inspect_empty = inspect.Parameter.empty
for p in parameters.values():
field_definitions[p.name] = (
p.annotation,
... if p.default is inspect_empty else p.default,
)
api_params_cls = create_model(
api_params_cls_name,
__base__=BaseAPIParamsModel,
__module__=api_result_cls.__module__,
**field_definitions,
)
params_model_classes[api_params_cls_name] = api_params_cls
set_alias_gen(
model_cls=api_params_cls,
alias_type='serialization',
name_mapping_dict=self._name_mapping_dict,
)
api_params = api_params_cls(*args, **kwargs)
req_headers = None
if isinstance(config, ConfigWithAuth):
req_headers = {
'Authorization': f'bearer {config.jwt}',
}
model_data = api_params.model_dump(
by_alias=True,
exclude_none=True,
)
for api_mixin_cls in self.__class__.__bases__[1:]:
if hasattr(api_mixin_cls, protocol_method.__name__):
if issubclass(api_mixin_cls, BasePostAPIFunctionProtocol):
http_method = HTTPMethod.POST
break
elif issubclass(api_mixin_cls, BaseGetAPIFunctionProtocol):
http_method = HTTPMethod.GET
break
else:
raise RuntimeError
req_params = None
req_json = None
req_data = None
match http_method:
case HTTPMethod.GET:
req_params = model_data
case HTTPMethod.POST:
if self._post_json:
req_json = model_data
else:
req_data = model_data
case _:
raise RuntimeError
http_request = requests.Request(
method=http_method,
url=config.get_api_url(api_path=api_path),
headers=req_headers,
params=req_params,
json=req_json,
data=req_data,
).prepare()
attempts = config.http503_attempts
with requests.Session() as session:
while True:
http_response = session.send(
request=http_request,
verify=config.verify_ssl,
)
if http_response.status_code == 503:
if attempts < 1:
break
else:
attempts -= 1
time.sleep(config.http503_attempts_interval)
else:
break
http_response.raise_for_status()
api_result_context = APIResultContext(
api_params=api_params,
http_response=http_response,
)
if issubclass(api_result_cls, BaseAPIResultModel):
set_alias_gen(
model_cls=api_result_cls,
alias_type='validation',
name_mapping_dict=self._name_mapping_dict,
)
result_extra_allow = (
api_result_cls.model_config.get('extra') == 'allow'
)
if config.result_extra_allow != result_extra_allow:
if config.result_extra_allow:
api_result_cls.model_config['extra'] = 'allow'
else:
api_result_cls.model_config['extra'] = (
BaseAPIResultModel.model_config.get('extra')
)
api_result_cls.model_rebuild(force=True)
api_result = api_result_cls.model_validate_json(
json_data=http_response.content,
context=api_result_context,
)
else:
try:
decoded_content = http_response.json()
except requests.JSONDecodeError:
decoded_content = http_response.text
api_result = api_result_cls(
value=decoded_content,
context=api_result_context,
)
return api_result
return api_function

@ -0,0 +1,92 @@
from dynamix_sdk import base, config
class BVSAuth(config.AuthenticatorProtocol):
_config: config.BaseConfigWithDomain
client_id: str
client_secret: str
password: str
user_name: str
def __init__(
self,
url: str,
domain: str,
client_id: str,
client_secret: str,
user_name: str,
password: str,
verify_ssl: bool = True,
http503_attempts: int = 10,
http503_attempts_interval: int = 5,
result_extra_allow: bool = True,
):
self._config = config.BaseConfigWithDomain(
url=url,
verify_ssl=verify_ssl,
domain=domain,
http503_attempts=http503_attempts,
http503_attempts_interval=http503_attempts_interval,
result_extra_allow=result_extra_allow,
)
self.client_id = client_id
self.client_secret = client_secret
self.user_name = user_name
self.password = password
@property
def api(self):
return BVSAPI(config=self._config)
def get_jwt(self):
return self.api.token(
client_id=self.client_id,
client_secret=self.client_secret,
grant_type='password',
password=self.password,
response_type='token',
scope='openid',
user_name=self.user_name,
).access_token
class BVSTokenResult(base.BaseAPIResultModel):
access_token: str
class BVSTokenProtocol(base.BasePostAPIFunctionProtocol):
def token(
self,
*,
client_id: str,
client_secret: str,
grant_type: str,
password: str,
response_type: str,
scope: str,
user_name: str,
) -> BVSTokenResult:
...
class BVSAPI(
base.BaseAPI,
BVSTokenProtocol,
path_mapping_dict={
'token': 'realms/[domain]/protocol/openid-connect/token',
},
name_mapping_dict={
'access_token': 'access_token',
'client_id': 'client_id',
'client_secret': 'client_secret',
'grant_type': 'grant_type',
'password': 'password',
'response_type': 'response_type',
'scope': 'scope',
'user_name': 'username',
'user_name': 'username',
},
post_json=False,
):
pass

@ -0,0 +1,45 @@
from dataclasses import dataclass
import re
from typing import Protocol
@dataclass(kw_only=True)
class Config:
url: str
base_api_path: str = ''
verify_ssl: bool
http503_attempts: int = 10
http503_attempts_interval: int = 5
result_extra_allow: bool = False
def get_api_url(self, api_path: str):
substitutions = re.findall(r'\[\w+\]', api_path)
_api_path = api_path[:]
for s in substitutions:
attr_name = f'{s.strip("[]")}'
attr_value = getattr(self, attr_name)
_api_path = _api_path.replace(s, attr_value)
return f'{self.url}{self.base_api_path}{_api_path}'
class AuthenticatorProtocol(Protocol):
def get_jwt(self) -> str:
...
@dataclass(kw_only=True)
class ConfigWithAuth(Config):
auth: str | AuthenticatorProtocol
@property
def jwt(self):
if isinstance(self.auth, str):
return self.auth
else:
return self.auth.get_jwt()
@dataclass(kw_only=True)
class BaseConfigWithDomain(Config):
domain: str

@ -0,0 +1,74 @@
from dynamix_sdk import base, config
class DECS3OAuth(config.AuthenticatorProtocol):
_config: config.Config
client_id: str
client_secret: str
def __init__(
self,
url: str,
client_id: str,
client_secret: str,
verify_ssl: bool = True,
http503_attempts: int = 10,
http503_attempts_interval: int = 5,
result_extra_allow: bool = True,
):
self._config = config.Config(
base_api_path='/v1',
url=url,
verify_ssl=verify_ssl,
http503_attempts=http503_attempts,
http503_attempts_interval=http503_attempts_interval,
result_extra_allow=result_extra_allow,
)
self.client_id = client_id
self.client_secret = client_secret
@property
def api(self):
return DECS3OAPI(config=self._config)
def get_jwt(self):
return self.api.oauth__access_token(
client_id=self.client_id,
client_secret=self.client_secret,
grant_type='client_credentials',
response_type='id_token',
)
class DECS3OOauthAccesstokenResultStr(base.BaseAPIResultStr):
pass
class DECS3OOauthAccesstokenProtocol(base.BasePostAPIFunctionProtocol):
def oauth__access_token(
self,
*,
client_id: str,
client_secret: str,
grant_type: None | str = None,
response_type: None | str = None,
validity: None | int = None,
) -> DECS3OOauthAccesstokenResultStr:
...
class DECS3OAPI(
base.BaseAPI,
DECS3OOauthAccesstokenProtocol,
path_mapping_dict={'oauth__access_token': 'oauth/access_token'},
name_mapping_dict={
'client_id': 'client_id',
'client_secret': 'client_secret',
'grant_type': 'grant_type',
'response_type': 'response_type',
'validity': 'validity',
},
post_json=False,
):
pass

@ -0,0 +1,30 @@
from dynamix_sdk import config
from .api import API
class Dynamix:
_config: config.ConfigWithAuth
def __init__(
self,
*,
url: str,
auth: str | config.AuthenticatorProtocol,
verify_ssl: bool = True,
http503_attempts: int = 10,
http503_attempts_interval: int = 5,
result_extra_allow: bool = False,
):
self._config = config.ConfigWithAuth(
base_api_path='/restmachine',
url=url,
auth=auth,
verify_ssl=verify_ssl,
http503_attempts=http503_attempts,
http503_attempts_interval=http503_attempts_interval,
result_extra_allow=result_extra_allow,
)
@property
def api(self):
return API(config=self._config)

@ -0,0 +1,10 @@
from .base import (
BaseAPIParamsModel,
BaseAPIParamsNestedModel,
BaseAPIResultModel,
BaseAPIResultNestedModel,
BaseAPIResultStr,
BaseAPIResultInt,
BaseAPIResultBool,
)
from .api import *

@ -0,0 +1,21 @@
from enum import Enum, auto as enum_auto
class AutoNameEnum(Enum):
def _generate_next_value_(name, start, count, last_values):
return name
class EmptyStr(str, Enum):
EMPTY_STR = ''
class HTTPMethod(str, AutoNameEnum):
POST = enum_auto()
GET = enum_auto()
def gen_cls_name_from_url_path(url_path: str, postfix: str = ''):
parts = url_path.strip('/').replace('-', '').split('/')
base = ''.join(f'{p[0].upper()}{p[1:]}' for p in parts)
return f'{base}{postfix}'
Loading…
Cancel
Save