diff --git a/README.md b/README.md index cf597c5..3e35a04 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ print(account_status) > В планах добавить все методы API, но на текущий момент доступны только некоторые из них. - [x] Аккаунт - - [ ] Базы данных + - [x] Базы данных - [ ] Балансировщики - [ ] Выделенные серверы - [ ] Домены diff --git a/pyproject.toml b/pyproject.toml index 26dd0ab..21380f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "timeweb-cloud" -version = "0.2.0" +version = "0.3.0" description = "Timeweb Cloud API wrapper" authors = ["Maxim Mosin "] license = "MIT" @@ -38,7 +38,7 @@ requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.bumpver] -current_version = "0.2.0" +current_version = "0.3.0" version_pattern = "MAJOR.MINOR.PATCH" commit_message = "Bump version {old_version} -> {new_version}" commit = false diff --git a/src/timeweb/__meta.py b/src/timeweb/__meta.py index 4e00f20..c45f74d 100644 --- a/src/timeweb/__meta.py +++ b/src/timeweb/__meta.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- '''Timeweb Cloud package metadata''' -__version__ = '0.2.0' +__version__ = '0.3.0' __author__ = 'Maxim Mosin ' diff --git a/src/timeweb/async_api/api.py b/src/timeweb/async_api/api.py index 6371368..18475e7 100644 --- a/src/timeweb/async_api/api.py +++ b/src/timeweb/async_api/api.py @@ -5,6 +5,7 @@ from httpx import AsyncClient from .s3 import BucketsAPI +from .dbs import DatabasesAPI from .tokens import TokensAPI from .images import ImagesAPI from .account import AccountAPI @@ -20,6 +21,7 @@ class AsyncTimeweb: ssh_keys (SSHKeysAPI): API для работы с SSH ключами. images (ImagesAPI): API для работы с образами. s3 (BucketsAPI): API для работы с S3-хранилищами. + dbs (DatabasesAPI): API для работы с базами данных. ''' def __init__(self, token: str, client: AsyncClient | None = None): @@ -35,3 +37,4 @@ def __init__(self, token: str, client: AsyncClient | None = None): self.ssh_keys = SSHKeysAPI(token, client) self.images = ImagesAPI(token, client) self.s3 = BucketsAPI(token, client) + self.dbs = DatabasesAPI(token, client) diff --git a/src/timeweb/async_api/dbs.py b/src/timeweb/async_api/dbs.py new file mode 100644 index 0000000..9f58741 --- /dev/null +++ b/src/timeweb/async_api/dbs.py @@ -0,0 +1,240 @@ +# -*- coding: utf-8 -*- +'''Методы API для работы с API образов. + +Облачная база данных, или База данных как сервис (DBaaS) — +облачное решение для хранения структурированных данных и управления ими. +DBaaS обеспечивает полностью автоматизированную, гибкую и масштабируемую +платформу для работы с базами данных. + +Документация: https://timeweb.cloud/api-docs#tag/Bazy-dannyh''' +import logging + +from httpx import AsyncClient + +from .base import BaseAsyncClient +from ..schemas import dbs as schemas + + +class DatabasesAPI(BaseAsyncClient): + '''Клиент для работы с API базами данных Timeweb Cloud''' + + def __init__(self, token: str, client: AsyncClient | None = None): + '''Инициализация клиента. + Args: + token (str): API токен. + client (AsyncClient | None, optional): HTTPX клиент. Defaults to None. + ''' + super().__init__(token, client) + self.log = logging.getLogger('timeweb') + + async def get_databases(self) -> schemas.DBArray: + '''Получить список баз данных. + Returns: + schemas.DBArray: Список баз данных. + ''' + dbs = await self._request( + 'GET', '/dbs' + ) + return schemas.DBArray(**dbs.json()) + + async def create( + self, + password: str, + name: str, + type: schemas.DBType | str, + preset_id: int, + login: str | None = None, + hash_type: schemas.DBHashType | str | None = None, + config_parameters: schemas.DBConfigParameters | None = None + ) -> schemas.DatabaseResponse: + '''Создать базу данных. + Args: + password (str): Пароль для доступа к базе данных. + name (str): Название базы данных. + type (schemas.DBType | str): Тип базы данных. + preset_id (int): ID пресета. + login (str | None, optional): Логин для доступа к базе данных. + Defaults to None. + hash_type (schemas.DBHashType | str | None, optional): Тип хэша. + Defaults to None. + config_parameters (schemas.DBConfigParameters | None, optional): + Параметры конфигурации. Defaults to None. + Returns: + schemas.DatabaseResponse: Ответ от API. + ''' + data = { + 'password': password, + 'name': name, + 'type': type, + 'preset_id': preset_id + } + if login: + data['login'] = login + if hash_type: + if isinstance(hash_type, schemas.DBHashType): + data['hash_type'] = hash_type.value + else: + data['hash_type'] = hash_type + if config_parameters: + data['config_parameters'] = config_parameters.dict() + db = await self._request( + 'POST', '/dbs', json=data + ) + return schemas.DatabaseResponse(**db.json()) + + async def get(self, db_id: int) -> schemas.DatabaseResponse: + '''Получить информацию о базе данных. + + Args: + db_id (int): ID базы данных. + + Returns: + schemas.DatabaseResponse: Ответ от API. + ''' + db = await self._request( + 'GET', f'/dbs/{db_id}' + ) + return schemas.DatabaseResponse(**db.json()) + + async def update( + self, + db_id: int, + password: str | None = None, + name: str | None = None, + preset_id: int | None = None, + config_parameters: schemas.DBConfigParameters | None = None, + is_external_ip: bool | None = None + ) -> schemas.DatabaseResponse: + '''Обновить базу данных. + Args: + db_id (int): ID базы данных. + password (str | None, optional): Пароль для доступа к базе данных. + Defaults to None. + name (str | None, optional): Название базы данных. Defaults to None. + preset_id (int | None, optional): ID пресета. Defaults to None. + config_parameters (schemas.DBConfigParameters | None, optional): + Параметры конфигурации. Defaults to None. + is_external_ip (bool | None, optional): Внешний IP. Defaults to None. + Returns: + schemas.DatabaseResponse: Ответ от API. + ''' + data: dict[str, str | int | dict] = {} + if password: + data['password'] = password + if name: + data['name'] = name + if preset_id: + data['preset_id'] = preset_id + if config_parameters: + data['config_parameters'] = config_parameters.dict() + if is_external_ip is not None: + data['is_external_ip'] = str(is_external_ip).lower() + db = await self._request( + 'PATCH', f'/dbs/{db_id}', json=data + ) + return schemas.DatabaseResponse(**db.json()) + + async def delete(self, db_id: int) -> bool: + '''Удалить базу данных. + + Args: + db_id (int): ID базы данных. + + Returns: + bool: True, если база данных успешно удалена. + ''' + await self._request( + 'DELETE', f'/dbs/{db_id}' + ) + return True + + async def get_backups( + self, db_id: int, limit: int = 100, offset: int = 0 + ) -> schemas.BackupArray: + '''Получить список бэкапов базы данных. + + Args: + db_id (int): ID базы данных. + limit (int, optional): Лимит. Defaults to 100. + offset (int, optional): Смещение. Defaults to 0. + + Returns: + schemas.BackupArray: Ответ от API. + ''' + backups = await self._request( + 'GET', f'/dbs/{db_id}/backups', params={ + 'limit': limit, + 'offset': offset + } + ) + return schemas.BackupArray(**backups.json()) + + async def create_backup(self, db_id: int) -> schemas.BackupResponse: + '''Создать бэкап базы данных. + + Args: + db_id (int): ID базы данных. + + Returns: + schemas.BackupResponse: Ответ от API. + ''' + backup = await self._request( + 'POST', f'/dbs/{db_id}/backups' + ) + return schemas.BackupResponse(**backup.json()) + + async def delete_backup(self, db_id: int, backup_id: int) -> bool: + '''Удалить бэкап базы данных. + + Args: + db_id (int): ID базы данных. + backup_id (int): ID бэкапа. + + Returns: + bool: True, если бэкап успешно удален. + ''' + await self._request( + 'DELETE', f'/dbs/{db_id}/backups/{backup_id}' + ) + return True + + async def get_backup(self, db_id: int, backup_id: int) -> schemas.BackupResponse: + '''Получить информацию о бэкапе базы данных. + + Args: + db_id (int): ID базы данных. + backup_id (int): ID бэкапа. + + Returns: + schemas.BackupResponse: Ответ от API. + ''' + backup = await self._request( + 'GET', f'/dbs/{db_id}/backups/{backup_id}' + ) + return schemas.BackupResponse(**backup.json()) + + async def recover_from_backup(self, db_id: int, backup_id: int) -> bool: + '''Восстановить базу данных из бэкапа. + + Args: + db_id (int): ID базы данных. + backup_id (int): ID бэкапа. + + Returns: + bool: True, если база данных успешно восстановлена. + ''' + await self._request( + 'PUT', f'/dbs/{db_id}/backups/{backup_id}' + ) + return True + + async def get_presets(self) -> schemas.PresetArray: + '''Получить список пресетов. + + Returns: + schemas.PresetArray: Ответ от API. + ''' + presets = await self._request( + 'GET', '/presets/dbs' + ) + return schemas.PresetArray(**presets.json()) diff --git a/src/timeweb/schemas/dbs/__init__.py b/src/timeweb/schemas/dbs/__init__.py new file mode 100644 index 0000000..fa83426 --- /dev/null +++ b/src/timeweb/schemas/dbs/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# flake8: noqa +'''Модели для работы с Базами данных''' +from .dbs import * +from .backups import * +from .presets import * diff --git a/src/timeweb/schemas/dbs/backups.py b/src/timeweb/schemas/dbs/backups.py new file mode 100644 index 0000000..d0fa5e1 --- /dev/null +++ b/src/timeweb/schemas/dbs/backups.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +'''Модели для работы с Бэкапами баз данных''' +from enum import Enum +from datetime import datetime + +from pydantic import BaseModel, Field + +from ..base import ResponseWithMeta, BaseResponse + + +class BackupStatus(str, Enum): + '''Статусы бэкапов''' + PRECREATE = 'precreate' + DELETE = 'delete' + SHUTDOWN = 'shutdown' + RECOVER = 'recover' + CREATE = 'create' + FAIL = 'fail' + DONE = 'done' + + +class BackupType(str, Enum): + '''Типы бэкапов''' + MANUAL = 'manual' + AUTO = 'auto' + + +class Backup(BaseModel): + '''Бэкап базы данных''' + id: int = Field(..., description='ID бэкапа.') + name: str = Field(..., description='Имя бэкапа.') + comment: str | None = Field( + None, description='Комментарий к бэкапу.' + ) + created_at: datetime = Field( + ..., description='Дата создания бэкапа.' + ) + status: BackupStatus = Field( + ..., description='Статус бэкапа.' + ) + size: int = Field(..., description='Размер бэкапа (Мб).') + type: BackupType = Field(..., description='Тип бэкапа.') + + +class BackupArray(ResponseWithMeta): + '''Массив бэкапов''' + backups: list[Backup] = Field(..., description='Массив бэкапов.') + + +class BackupResponse(BaseResponse): + '''Бэкап''' + backup: Backup = Field(..., description='Бэкап.') diff --git a/src/timeweb/schemas/dbs/dbs.py b/src/timeweb/schemas/dbs/dbs.py new file mode 100644 index 0000000..6751fb8 --- /dev/null +++ b/src/timeweb/schemas/dbs/dbs.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- +'''Модели для работы с Базами данных''' +from enum import Enum +from datetime import datetime +from ipaddress import IPv4Address + +from pydantic import BaseModel, Field + +from ..base import ResponseWithMeta, BaseResponse + + +class DBType(str, Enum): + '''Типы Баз данных''' + MySQL = 'mysql' + MySQL5 = 'mysql5' + PostgreSQL = 'postgresql' + Redis = 'redis' + MondoDB = 'mongodb' + + +class DBHashType(str, Enum): + '''Типы хэшей Баз данных''' + CACHING_SHA2 = 'caching_sha2' + MySQL_Native = 'mysql_native' + + +class DBStatus(str, Enum): + '''Статусы Баз данных''' + STARTED = 'started' + STARTING = 'starting' + STOPED = 'stoped' + NO_PAID = 'no_paid' + + +class DBDiskStats(BaseModel): + '''Статистика диска''' + size: int = Field(..., description='Размер (в Кб) диска базы данных.') + used: int = Field( + ..., description='Размер(в Кб) использованного пространства диска базы данных.' + ) + + +class DBConfigParameters(BaseModel): + '''Параметры конфигурации Базы данных''' + auto_increment_increment: str | None = Field( + None, description='Интервал между значениями столбцов с атрибутом.' + ) + auto_increment_offset: str | None = Field( + None, description='Начальное значение для столбцов с атрибутом "AUTO_INCREMENT".' + ) + innodb_io_capacity: str | None = Field( + None, description='Количество потоков ввода-вывода, используемых для операций очистки.' + ) + innodb_read_io_threads: str | None = Field( + None, description='Максимальное число потоков, которые могут исполняться.' + ) + innodb_write_io_threads: str | None = Field( + None, description='Количество потоков ввода-вывода, используемых для операций записи.' + ) + join_buffer_size: str | None = Field( + None, description='Минимальный размер буфера.' + ) + max_allowed_packet: str | None = Field( + None, description='Максимальный размер одного пакета, строки или параметра, отправляемого функцией "mysql_stmt_send_long_data()".' + ) + max_heap_table_size: str | None = Field( + None, description='Максимальный размер пользовательских MEMORY-таблиц.' + ) + autovacuum_analyze_scale_factor: str | None = Field( + None, description='Доля измененных или удаленных записей в таблице, при которой процесс автоочистки выполнит команду "ANALYZE".' + ) + bgwriter_delay: str | None = Field( + None, description='Задержка между запусками процесса фоновой записи.' + ) + bgwriter_lru_maxpages: str | None = Field( + None, description='Максимальное число элементов буферного кеша.' + ) + deadlock_timeout: str | None = Field( + None, description='Время ожидания, по истечении которого будет выполняться проверка состояния перекрестной блокировки.' + ) + gin_pending_list_limit: str | None = Field( + None, description='Максимальный размер очереди записей индекса "GIN".' + ) + idle_in_transaction_session_timeout: str | None = Field( + None, description='Время простоя открытой транзакции, при превышении которого будет завершена сессия с этой транзакцией.' + ) + idle_session_timeout: str | None = Field( + None, description='Время простоя не открытой транзакции, при превышении которого будет завершена сессия с этой транзакцией.' + ) + join_collapse_limit: str | None = Field( + None, description='Значение количества элементов в списке "FROM" при превышении которого, планировщик будет переносить в список явные инструкции "JOIN".' + ) + lock_timeout: str | None = Field( + None, description='Время ожидания освобождения блокировки.' + ) + max_prepared_transactions: str | None = Field( + None, description='Максимальное число транзакций, которые могут одновременно находиться в подготовленном состоянии.' + ) + + +class Database(BaseModel): + """Database model.""" + id: int = Field( + ..., description='Уникальный идентификатор для каждого экземпляра базы данных' + ) + created_at: datetime = Field(..., description='Дата создания базы данных') + account_id: str = Field(..., description='Идентификатор пользователя') + login: str = Field(..., description='Логин для подключения к базе данных.') + password: str = Field(..., description='Пароль для подключения к базе данных.') + name: str = Field(..., description='Имя базы данных.') + host: str = Field(..., description='Хост базы данных.') + type: DBType = Field(..., description='Тип базы данных.') + hash_type: DBHashType = Field( + ..., description='Тип хеширования базы данных (mysql5 | mysql | postgres).' + ) + port: int = Field(..., description='Порт базы данных.') + ip: IPv4Address | None = Field( + None, description='IP-адрес сетевого интерфейса IPv4.') + local_ip: IPv4Address | None = Field( + None, description='IP-адрес локального сетевого интерфейса IPv4.') + status: DBStatus = Field(..., description='Статус базы данных.') + preset_id: int = Field(..., description='Идентификатор тарифа.') + dist_stats: DBDiskStats | None = Field( + None, description='Статистика использования диска базы данных.' + ) + config_parameters: DBConfigParameters = Field( + ..., description='Параметры конфигурации базы данных.' + ) + is_only_local_ip_access: bool = Field( + ..., description='Доступна ли база данных только по локальному IP адресу.' + ) + + +class DBArray(ResponseWithMeta): + """Response with array of databases.""" + dbs: list[Database] = Field(..., description='Массив баз данных.') + + +class DatabaseResponse(BaseResponse): + """Response with database.""" + db: Database = Field(..., description='База данных.') diff --git a/src/timeweb/schemas/dbs/presets.py b/src/timeweb/schemas/dbs/presets.py new file mode 100644 index 0000000..f0e5013 --- /dev/null +++ b/src/timeweb/schemas/dbs/presets.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +'''Модели для работы с тарифами баз данных''' +from pydantic import BaseModel, Field + +from .dbs import DBType +from ..base import ResponseWithMeta + + +class Preset(BaseModel): + '''Тариф базы данных''' + id: int = Field(..., description='ID тарифа.') + description: str = Field(..., description='Описание тарифа.') + description_short: str = Field( + ..., description='Краткое описание тарифа.' + ) + cpu: int = Field(..., description='Количество CPU.') + ram: int = Field(..., description='Количество RAM (Гб).') + disk: int = Field(..., description='Количество дискового пространства (Гб).') + type: DBType = Field(..., description='Тип базы данных.') + price: int = Field(..., description='Цена тарифа.') + location: str = Field(..., + description='Географическое расположение тарифа.') + + +class PresetArray(ResponseWithMeta): + '''Массив тарифов''' + databases_presets: list[Preset] = Field(..., description='Массив тарифов.') diff --git a/src/timeweb/sync_api/api.py b/src/timeweb/sync_api/api.py index 8557e32..b2a6577 100644 --- a/src/timeweb/sync_api/api.py +++ b/src/timeweb/sync_api/api.py @@ -5,6 +5,7 @@ from httpx import Client from .s3 import BucketsAPI +from .dbs import DatabasesAPI from .tokens import TokensAPI from .images import ImagesAPI from .account import AccountAPI @@ -20,6 +21,7 @@ class Timeweb: ssh_keys (SSHKeysAPI): API для работы с SSH ключами. images (ImagesAPI): API для работы с образами. s3 (BucketsAPI): API для работы с S3-хранилищами. + dbs (DatabasesAPI): API для работы с базами данных. ''' def __init__(self, token: str, client: Client | None = None): @@ -35,3 +37,4 @@ def __init__(self, token: str, client: Client | None = None): self.ssh_keys = SSHKeysAPI(token, client) self.images = ImagesAPI(token, client) self.s3 = BucketsAPI(token, client) + self.dbs = DatabasesAPI(token, client) diff --git a/src/timeweb/sync_api/dbs.py b/src/timeweb/sync_api/dbs.py new file mode 100644 index 0000000..1af4d46 --- /dev/null +++ b/src/timeweb/sync_api/dbs.py @@ -0,0 +1,240 @@ +# -*- coding: utf-8 -*- +'''Методы API для работы с API образов. + +Облачная база данных, или База данных как сервис (DBaaS) — +облачное решение для хранения структурированных данных и управления ими. +DBaaS обеспечивает полностью автоматизированную, гибкую и масштабируемую +платформу для работы с базами данных. + +Документация: https://timeweb.cloud/api-docs#tag/Bazy-dannyh''' +import logging + +from httpx import Client + +from .base import BaseClient +from ..schemas import dbs as schemas + + +class DatabasesAPI(BaseClient): + '''Клиент для работы с API базами данных Timeweb Cloud''' + + def __init__(self, token: str, client: Client | None = None): + '''Инициализация клиента. + Args: + token (str): API токен. + client (Client | None, optional): HTTPX клиент. Defaults to None. + ''' + super().__init__(token, client) + self.log = logging.getLogger('timeweb') + + def get_databases(self) -> schemas.DBArray: + '''Получить список баз данных. + Returns: + schemas.DBArray: Список баз данных. + ''' + dbs = self._request( + 'GET', '/dbs' + ) + return schemas.DBArray(**dbs.json()) + + def create( + self, + password: str, + name: str, + type: schemas.DBType | str, + preset_id: int, + login: str | None = None, + hash_type: schemas.DBHashType | str | None = None, + config_parameters: schemas.DBConfigParameters | None = None + ) -> schemas.DatabaseResponse: + '''Создать базу данных. + Args: + password (str): Пароль для доступа к базе данных. + name (str): Название базы данных. + type (schemas.DBType | str): Тип базы данных. + preset_id (int): ID пресета. + login (str | None, optional): Логин для доступа к базе данных. + Defaults to None. + hash_type (schemas.DBHashType | str | None, optional): Тип хэша. + Defaults to None. + config_parameters (schemas.DBConfigParameters | None, optional): + Параметры конфигурации. Defaults to None. + Returns: + schemas.DatabaseResponse: Ответ от API. + ''' + data = { + 'password': password, + 'name': name, + 'type': type, + 'preset_id': preset_id + } + if login: + data['login'] = login + if hash_type: + if isinstance(hash_type, schemas.DBHashType): + data['hash_type'] = hash_type.value + else: + data['hash_type'] = hash_type + if config_parameters: + data['config_parameters'] = config_parameters.dict() + db = self._request( + 'POST', '/dbs', json=data + ) + return schemas.DatabaseResponse(**db.json()) + + def get(self, db_id: int) -> schemas.DatabaseResponse: + '''Получить информацию о базе данных. + + Args: + db_id (int): ID базы данных. + + Returns: + schemas.DatabaseResponse: Ответ от API. + ''' + db = self._request( + 'GET', f'/dbs/{db_id}' + ) + return schemas.DatabaseResponse(**db.json()) + + def update( + self, + db_id: int, + password: str | None = None, + name: str | None = None, + preset_id: int | None = None, + config_parameters: schemas.DBConfigParameters | None = None, + is_external_ip: bool | None = None + ) -> schemas.DatabaseResponse: + '''Обновить базу данных. + Args: + db_id (int): ID базы данных. + password (str | None, optional): Пароль для доступа к базе данных. + Defaults to None. + name (str | None, optional): Название базы данных. Defaults to None. + preset_id (int | None, optional): ID пресета. Defaults to None. + config_parameters (schemas.DBConfigParameters | None, optional): + Параметры конфигурации. Defaults to None. + is_external_ip (bool | None, optional): Внешний IP. Defaults to None. + Returns: + schemas.DatabaseResponse: Ответ от API. + ''' + data: dict[str, str | int | dict] = {} + if password: + data['password'] = password + if name: + data['name'] = name + if preset_id: + data['preset_id'] = preset_id + if config_parameters: + data['config_parameters'] = config_parameters.dict() + if is_external_ip is not None: + data['is_external_ip'] = str(is_external_ip).lower() + db = self._request( + 'PATCH', f'/dbs/{db_id}', json=data + ) + return schemas.DatabaseResponse(**db.json()) + + def delete(self, db_id: int) -> bool: + '''Удалить базу данных. + + Args: + db_id (int): ID базы данных. + + Returns: + bool: True, если база данных успешно удалена. + ''' + self._request( + 'DELETE', f'/dbs/{db_id}' + ) + return True + + def get_backups( + self, db_id: int, limit: int = 100, offset: int = 0 + ) -> schemas.BackupArray: + '''Получить список бэкапов базы данных. + + Args: + db_id (int): ID базы данных. + limit (int, optional): Лимит. Defaults to 100. + offset (int, optional): Смещение. Defaults to 0. + + Returns: + schemas.BackupArray: Ответ от API. + ''' + backups = self._request( + 'GET', f'/dbs/{db_id}/backups', params={ + 'limit': limit, + 'offset': offset + } + ) + return schemas.BackupArray(**backups.json()) + + def create_backup(self, db_id: int) -> schemas.BackupResponse: + '''Создать бэкап базы данных. + + Args: + db_id (int): ID базы данных. + + Returns: + schemas.BackupResponse: Ответ от API. + ''' + backup = self._request( + 'POST', f'/dbs/{db_id}/backups' + ) + return schemas.BackupResponse(**backup.json()) + + def delete_backup(self, db_id: int, backup_id: int) -> bool: + '''Удалить бэкап базы данных. + + Args: + db_id (int): ID базы данных. + backup_id (int): ID бэкапа. + + Returns: + bool: True, если бэкап успешно удален. + ''' + self._request( + 'DELETE', f'/dbs/{db_id}/backups/{backup_id}' + ) + return True + + def get_backup(self, db_id: int, backup_id: int) -> schemas.BackupResponse: + '''Получить информацию о бэкапе базы данных. + + Args: + db_id (int): ID базы данных. + backup_id (int): ID бэкапа. + + Returns: + schemas.BackupResponse: Ответ от API. + ''' + backup = self._request( + 'GET', f'/dbs/{db_id}/backups/{backup_id}' + ) + return schemas.BackupResponse(**backup.json()) + + def recover_from_backup(self, db_id: int, backup_id: int) -> bool: + '''Восстановить базу данных из бэкапа. + + Args: + db_id (int): ID базы данных. + backup_id (int): ID бэкапа. + + Returns: + bool: True, если база данных успешно восстановлена. + ''' + self._request( + 'PUT', f'/dbs/{db_id}/backups/{backup_id}' + ) + return True + + def get_presets(self) -> schemas.PresetArray: + '''Получить список пресетов. + + Returns: + schemas.PresetArray: Ответ от API. + ''' + presets = self._request( + 'GET', '/presets/dbs' + ) + return schemas.PresetArray(**presets.json())