diff --git a/perxis/extensions/actions.py b/perxis/extensions/actions.py index a84976a736d20c585bc3882eb42b07d532b00d46..580ebf5174770a447dd3c159e54c8a90c2c6d293 100644 --- a/perxis/extensions/actions.py +++ b/perxis/extensions/actions.py @@ -1,3 +1,8 @@ +""" + Модуль содержит вспомогательные функции для работы СЃ экшнами +""" + + import copy from google.protobuf.struct_pb2 import Struct @@ -8,6 +13,18 @@ ACTIONS_COLLECTION_ID = "space_actions" def process_action_id(action_id: str) -> str: + """ + Функция для преобразования идентификатора действия чтобы РІ дальнейшем его РјРѕР¶РЅРѕ было + использовать РІ Servicer. Примеры преобразований: + - some-action -> some_action + - some other action -> some_other_action + + Arguments: + action_id (str): идентификатор действия + + Returns: + str: обработанный идентификатор действия + """ action_id = action_id.lower().replace("-", "_").replace(" ", "_") return action_id @@ -20,6 +37,18 @@ def make_action_dict( kind: int, **kwargs, ) -> dict: + """ + Функция для создания dict РЅСѓР¶РЅРѕР№ структуры. Р’ дальнейшем РѕРЅ идёт РІ свойство data item'Р° + + Arguments: + extension_id (str): идентификатор расширения + action_id (str): идентификатор действия + name (str): название action'Р° + kind (str): область действия action'Р° согласно [документации](https://docs.perxis.ru/docs/api/extension/#actionkind) + kwargs: любые РґРѕРї. параметры согласно [документации](https://docs.perxis.ru/docs/api/extension/#action) + Returns: + dict + """ action_id = process_action_id(action_id) return { @@ -32,6 +61,14 @@ def make_action_dict( def make_action_struct(data: dict) -> Struct: + """ + Вспомогательная функция для создания объекта Struct РЅР° основании данных РёР· dict + + Arguments: + data (dict) + Returns: + google.protobuf.struct_pb2.Struct + """ struct = Struct() struct.update(data) @@ -39,6 +76,18 @@ def make_action_struct(data: dict) -> Struct: def make_action_item(space_id: str, env_id: str, data: dict) -> items_pb2.Item: + """ + Функция для создания items_pb2.Item СЃ данными action'Р° + + Arguments: + space_id (str): идентификатор пространства РІ котором будет создан action + env_id (str): идентификатор окружения РІ котором будет создан action + data (dict): данные action'Р° + + Returns: + items_pb2.Item + """ + copied_data = copy.deepcopy(data) action_id = copied_data.pop("id") diff --git a/perxis/extensions/bootstrap.py b/perxis/extensions/bootstrap.py index 4e3eb6634a70f09ecb24c6d30953f630838ed13e..d62cb02f7e3a8fa5ad40c31a6a6ed284e7d2d471 100644 --- a/perxis/extensions/bootstrap.py +++ b/perxis/extensions/bootstrap.py @@ -1,3 +1,8 @@ +""" + Модуль содержит функцию bootstrap для упрощения запуска сервисов расширений +""" + + import asyncio import aiocron @@ -140,7 +145,33 @@ def bootstrap( files_host: str = "files:8003", images_host: str = "images:8005" ): + """ + Функция для инициализации Рё запуска сервиса расширения. + + Пример использования РІ расширении: + ``` + def main(): + ext_descriptor = utils.get_extension_descriptor( + ext_host="demo-ext-backend:50051", + ext_id=extension.ID, + ext_name=extension.NAME, + ext_version=extension.VERSION, + ext_description=extension.DESCRIPTION, + ext_version_description=extension.VERSION_DESCRIPTION + ) + bootstrap(ext_descriptor, Servicer, ext_manager_host, content_host) + ``` + + Arguments: + ext_descriptor (manager_service_pb2.ExtensionDescriptor): Дескриптор расширения + servicer_cls (extension_service_pb2_grpc.ExtensionServiceServicer): класс (РЅРµ экземпляр!) сервиса + ext_manager_host (str): адрес сервиса менеджера расширений + content_host (str): адрес сервиса контента + account_host (str): адрес сервиса аккаунтов + files_host (str): адрес сервиса файлов + images_host (str): адрес сервиса работы СЃ изображениями + """ logger.info(f"Рнициализация сервиса расширения {ext_descriptor.extension}") loop = asyncio.get_event_loop() diff --git a/perxis/extensions/extension_service.py b/perxis/extensions/extension_service.py index 3799b340c03214128f30ce680362764c27c17cde..c902ce878818c3b0e52282fbf8e3023345f407e3 100644 --- a/perxis/extensions/extension_service.py +++ b/perxis/extensions/extension_service.py @@ -1,3 +1,8 @@ +""" + Модуль содержит базовый класс сервисов расширений - ExtensionService +""" + + import grpc import uuid import typing @@ -31,10 +36,26 @@ from perxis.extensions.item_models import AbstractItem def generate_operation_id() -> str: + """ + Функция для генерации идентификатора операции + + Returns: + str + """ + return str(uuid.uuid4()) def datetime_to_timestamp(dt: datetime) -> timestamp_pb2.Timestamp: + """ + Функция для преобразования объекта datetime РІ объект timestamp_pb2.Timestamp + + Arguments: + dt (datetime) + Returns: + timestamp_pb2.Timestamp + """ + timestamp = dt.timestamp() seconds = int(timestamp) nanos = int(timestamp % 1 * 1e9) @@ -44,6 +65,21 @@ def datetime_to_timestamp(dt: datetime) -> timestamp_pb2.Timestamp: @dataclasses.dataclass class OperationMeta: + """ + Класс для хранения метаданных операции + + Attributes: + task (asyncio.Task): ссылка РЅР° task операции + operation_id (str): идентификатор операции + description (str): описание операции + created_at (datetime.datetime): дата создания операции + modified_at (datetime.datetime): дата изменения метаданных операции + response (str | None): результат операции + errors (list[str]): возникшие РІРѕ время выполнения операции ошибки + was_finished (bool): флаг завершения операции + metadata (dict): любые прочие метаданные операции + """ + task: asyncio.Task operation_id: str @@ -59,16 +95,34 @@ class OperationMeta: metadata: dict[str, typing.Any] = dataclasses.field(default_factory=dict) def mark_cancelled(self): + """ + Функция для отмены операции + """ + self.was_finished = True self.response = "Отменено" def mark_finished(self, errors: typing.Optional[list[str]] = None): + """ + Функция для завершения операции + + Arguments: + errors (list[str] | None): СЃРїРёСЃРѕРє ошибок + """ + self.modified_at = datetime.datetime.now() self.was_finished = True self.errors = errors self.response = None if errors else "OK" def to_operation(self): + """ + Функция для преобразования объекта операции РІ формат perxis + + Returns: + operation_pb2.Operation + """ + packed_response = any_pb2.Any() packed_response.Pack(wrappers_pb2.StringValue(value=self.response or "PENDING")) @@ -89,6 +143,38 @@ class OperationMeta: class ExtensionService( extension_service_pb2_grpc.ExtensionServiceServicer, operation_service_pb2_grpc.OperationServiceServicer ): + """ + Базовый класс для сервисов расширений + + Attributes: + extension_id (str): идентификатор расширения + collections (list[collections_pb2.Collection]): СЃРїРёСЃРѕРє коллекций расширения + roles (list[roles_pb2.Role]): СЃРїРёСЃРѕРє ролей расширения + clients (list[clients_pb2.Client]): СЃРїРёСЃРѕРє клиентов расширения + actions (list[dict]): СЃРїРёСЃРѕРє действий расширения + items (list[AbstractItem]): СЃРїРёСЃРѕРє управляемых расширением item'РѕРІ + __operations (dict[str, OperationMeta]): маппинг СЃ данными операций + logger (logging.Logger): логгер расширения + ext_manager_service (manager_service_pb2_grpc.ExtensionManagerServiceStub): ссылка РЅР° сервис менеджера расширений + collections_service (collections_pb2_grpc.CollectionsStub): ссылка РЅР° сервис коллекций + environments_service (environments_pb2_grpc.EnvironmentsStub): ссылка РЅР° сервис окружений + roles_service (roles_pb2_grpc.RolesStub): ссылка РЅР° сервис ролей + clients_service (clients_pb2_grpc.ClientsStub): ссылка РЅР° сервис клиентов + items_service (items_pb2_grpc.ItemsStub): ссылка РЅР° сервис item'РѕРІ + spaces_service (spaces_pb2_grpc.SpacesStub): ссылка РЅР° сервис пространств + files_service (files_pb2_grpc.FilesStub): ссылка РЅР° сервис файлов + images_service (images_pb2_grpc.ImagesStub): ссылка РЅР° сервис изображений + references_service (references_pb2_grpc.ReferencesStub): ссылка РЅР° сервис для работы СЃ объектами Reference + users_service (users_pb2_grpc.UsersStub): ссылка РЅР° сервис пользователей + organizations_service (organizations_pb2_grpc.OrganizationsStub): ссылка РЅР° сервис организаций + members_service (members_pb2_grpc.MembersStub): ссылка РЅР° сервис работы СЃ пользователями организаций + locales_service (locales_pb2_grpc.LocalesStub): ссылка РЅР° сервис локалей Рё переводов + invitations_service (invitations_pb2_grpc.InvitationsStub): ссылка РЅР° сервис приглашений + collaborators_service (collaborators_pb2_grpc.CollaboratorsStub): ссылка РЅР° сервис коллабораторов + channel (grpc.Channel): ссылка РЅР° grpc канал + extension_setup (ExtensionSetup): объект класса ExtensionSetup для управления данными расширения + """ + extension_id: str collections: list[collections_pb2.Collection] = [] roles: list[roles_pb2.Role] = [] @@ -178,6 +264,11 @@ class ExtensionService( return self.__operations def remove_old_operations(self): + """ + Функция для удаления stale операций. Запускается раз РІ час. Удаляет РІСЃРµ операции + которые были завершены больше часа назад + """ + self.logger.info("Удаление старых операций") ids = [] @@ -224,6 +315,16 @@ class ExtensionService( self.logger.info("Удаление старых операций завершено") def result_log(self, operation, operation_id: str, request, errors: list[str]): + """ + Вспомогательная функция для логгирования резултата выполнения операции + + Arguments: + operation (str): название операции + operation_id (str): идентификатор операции + request: объект запроса + errors (list[str]): ошибки выполнения операции + """ + log_func = self.logger.error if errors else self.logger.info log_func( @@ -239,6 +340,15 @@ class ExtensionService( ) def ext_request_results_from_exception(self, e: Exception): + """ + Вспомогательная функция для преобразования объектов исключений РІ объект результата запроса + + Arguments: + e (Exception): любое исключение + Returns: + extension_service_pb2.ExtensionRequestResult + """ + return [ extension_service_pb2.ExtensionRequestResult( extension=self.extension_id, @@ -249,9 +359,27 @@ class ExtensionService( ] def get_operation_meta(self, operation_id: str) -> typing.Optional[OperationMeta]: + """ + Получение объекта операции РїРѕ идентификатору + + Arguments: + operation_id (str): идентификатор операции + + Returns: + OperationMeta | None + """ + return self.__operations.get(operation_id) def set_operation_meta(self, operation_id: str, description: str, task: asyncio.Task): + """ + Функция для сохранения данных РѕР± операции + Arguments: + operation_id (str): идентификатор операции + description (str): описание операции + task (asyncio.Task): объект task операции + """ + self.__operations[operation_id] = OperationMeta( operation_id=operation_id, task=task, @@ -259,12 +387,30 @@ class ExtensionService( ) def mark_operation_as_finished(self, operation_id: str, errors: list[str] | None = None): + """ + Функция для завершения операции + + Arguments: + operation_id (str): идентификатор операции + errors (list[str] | None): ошибки выполнения операции + """ + if operation_id in self.operations: self.operations[operation_id].mark_finished(errors) else: self.logger.error(f"Операция {operation_id} РЅРµ найдена!") async def _Install(self, operation_id: str, request: extension_service_pb2.InstallRequest, context): + """ + Действия метода Install сервиса расширений. Вызывается после всех необходимых действий СЃ + формированием данных РѕР± операции. Р’ случае необходимости РјРѕР¶РЅРѕ переопределить + + Arguments: + operation_id (str): идентификатор операции + request (extension_service_pb2.InstallRequest): объект запроса + context: контекст + """ + errors_list = await self.extension_setup.install( request.space_id, request.env_id, request.force ) @@ -277,12 +423,28 @@ class ExtensionService( async def additional_install_operations(self, operation_id: str, request: extension_service_pb2.InstallRequest, context) -> list[str]: """ - Для РґРѕРї. логики установки + Функция для возможности добавления любой дополнительной логики после завершения всех стандартных + процедур установки. РњРѕР¶РЅРѕ переопределять. Возвращает массив СЃ ошибками. + + Arguments: + operation_id (str): идентификатор операции + request (extension_service_pb2.InstallRequest): объект запроса + context: контекст + + Returns: + list[str] """ return [] async def Install(self, request: extension_service_pb2.InstallRequest, context): + """ + Реализация метода Install класса extension_service_pb2_grpc.ExtensionServiceServicer. Содержит + РІ себе формирование данных РѕР± операции. Переопределять РЅРµ следует, для изменения логики Install + лучше работать СЃ методами _Install если РЅСѓР¶РЅРѕ РІСЃС‘ радикально изменить или СЃ методом + additional_install_operations для добавления чего-то дополнительного + """ + operation_id = generate_operation_id() operation_description = "Установка расширения %s для окружения %s пространства %s. %s force" % ( self.extension_id, @@ -300,6 +462,16 @@ class ExtensionService( return self.get_operation_meta(operation_id).to_operation() async def _Uninstall(self, operation_id: str, request: extension_service_pb2.UninstallRequest, context): + """ + Действия метода _Uninstall сервиса расширений. Вызывается после всех необходимых действий СЃ + формированием данных РѕР± операции. Р’ случае необходимости РјРѕР¶РЅРѕ переопределить + + Arguments: + operation_id (str): идентификатор операции + request (extension_service_pb2.UninstallRequest): объект запроса + context: контекст + """ + errors_list: list[str] = await self.extension_setup.uninstall(request.space_id, request.env_id, request.remove) errors_list += await self.additional_uninstall_operations(operation_id, request, context) @@ -309,12 +481,28 @@ class ExtensionService( async def additional_uninstall_operations(self, operation_id: str, request: extension_service_pb2.UninstallRequest, context) -> list[str]: """ - Для РґРѕРї. логики удаления + Функция для возможности добавления любой дополнительной логики после завершения всех стандартных + процедур удаления. РњРѕР¶РЅРѕ переопределять. Возвращает массив СЃ ошибками. + + Arguments: + operation_id (str): идентификатор операции + request (extension_service_pb2.UninstallRequest): объект запроса + context: контекст + + Returns: + list[str] """ return [] async def Uninstall(self, request: extension_service_pb2.UninstallRequest, context): + """ + Реализация метода Uninstall класса extension_service_pb2_grpc.ExtensionServiceServicer. Содержит + РІ себе формирование данных РѕР± операции. Переопределять РЅРµ следует, для изменения логики Uninstall + лучше работать СЃ методами _Uninstall если РЅСѓР¶РЅРѕ РІСЃС‘ радикально изменить или СЃ методом + additional_uninstall_operations для добавления чего-то дополнительного + """ + operation_id = generate_operation_id() operation_description = "Удаление расширения %s для окружения %s пространства %s. %s remove" % ( self.extension_id, @@ -332,6 +520,16 @@ class ExtensionService( return self.get_operation_meta(operation_id).to_operation() async def _Check(self, operation_id: str, request: extension_service_pb2.CheckRequest, context): + """ + Действия метода _Check сервиса расширений. Вызывается после всех необходимых действий СЃ + формированием данных РѕР± операции. Р’ случае необходимости РјРѕР¶РЅРѕ переопределить + + Arguments: + operation_id (str): идентификатор операции + request (extension_service_pb2.CheckRequest): объект запроса + context: контекст + """ + errors_list: list[str] = await self.extension_setup.check(request.space_id, request.env_id) self.result_log("проверки", operation_id, request, errors_list) @@ -339,13 +537,29 @@ class ExtensionService( self.__operations[operation_id].mark_finished(errors_list) async def additional_check_operations(self, operation_id: str, request: extension_service_pb2.CheckRequest, context) -> list[str]: - """" - Для РґРѕРї. логики проверки + """ + Функция для возможности добавления любой дополнительной логики после завершения всех стандартных + процедур проверки. РњРѕР¶РЅРѕ переопределять. Возвращает массив СЃ ошибками. + + Arguments: + operation_id (str): идентификатор операции + request (extension_service_pb2.CheckRequest): объект запроса + context: контекст + + Returns: + list[str] """ return [] async def Check(self, request: extension_service_pb2.CheckRequest, context): + """ + Реализация метода Check класса extension_service_pb2_grpc.ExtensionServiceServicer. Содержит + РІ себе формирование данных РѕР± операции. Переопределять РЅРµ следует, для изменения логики Check + лучше работать СЃ методами _Check если РЅСѓР¶РЅРѕ РІСЃС‘ радикально изменить или СЃ методом + additional_check_operations для добавления чего-то дополнительного + """ + operation_id = generate_operation_id() operation_description = "Проверка расширения %s для окружения %s пространства %s" % ( self.extension_id, @@ -362,6 +576,17 @@ class ExtensionService( async def _dispatch_action( self, request: extension_pb2.ActionRequest, context ) -> extension_service_pb2.ActionResponse: + """ + Метод для получение РЅСѓР¶РЅРѕР№ С„-ции для действия Рё её вызова + + Arguments: + request (extension_pb2.ActionRequest): объект запроса + context: контекст + + Returns: + extension_service_pb2.ActionResponse + """ + action_id = request.action.split("/")[-1] func_name = f"action_{action_id}" @@ -380,6 +605,14 @@ class ExtensionService( return response async def Action(self, request: extension_pb2.ActionRequest, context): + """ + Реализация метода Action класса extension_service_pb2_grpc.ExtensionServiceServicer + + Arguments: + request (extension_pb2.ActionRequest): объект запроса + context: контекст + """ + operation_description = "Действие %s для окружения %s пространства %s" % ( request.action, request.env_id, @@ -402,6 +635,15 @@ class ExtensionService( return response async def Get(self, request: operation_service_pb2.GetOperationRequest, context): + """ + Реализация метода Get класса operation_service_pb2_grpc.OperationServiceServicer. Рспользуется + для получения данных Рѕ выполняемой операции + + Arguments: + request (operation_service_pb2.GetOperationRequest): объект запроса + context: контекст + """ + operations_meta = self.get_operation_meta(request.operation_id) if not operations_meta: @@ -427,6 +669,15 @@ class ExtensionService( return operations_meta.to_operation() def Cancel(self, request: operation_service_pb2.CancelOperationRequest, context): + """ + Реализация метода Cancel класса operation_service_pb2_grpc.OperationServiceServicer. Рспользуется + для отмены выполнения операции + + Arguments: + request (operation_service_pb2.CancelOperationRequest): объект запроса + context: контекст + """ + operations_meta = self.get_operation_meta(request.operation_id) if not operations_meta: diff --git a/perxis/extensions/extension_setup.py b/perxis/extensions/extension_setup.py index 3bfa04ec060501c855bf05400cf38cc5f2c08489..99be10baf5f02f0cbcfcf8ee56b660cb286941e6 100644 --- a/perxis/extensions/extension_setup.py +++ b/perxis/extensions/extension_setup.py @@ -1,3 +1,8 @@ +""" +Модуль содержит класс ExtensionSetup. РћРЅ используется для управления всеми данными расширения +""" + + import logging import grpc @@ -24,6 +29,22 @@ logger = logging.getLogger(__name__) class ExtensionSetup: + """ + Attributes: + collections_service (collections_pb2_grpc.CollectionsStub): ссылка РЅР° сервис коллекций + environments_service (environments_pb2_grpc.EnvironmentsStub): ссылка РЅР° сервис окружений + roles_service (roles_pb2_grpc.RolesStub): ссылка РЅР° сервис ролей + clients_service (clients_pb2_grpc.ClientsStub): ссылка РЅР° сервис клиентов + items_service (items_pb2_grpc.ItemsStub): ссылка РЅР° сервис item'РѕРІ + collections (list[collections_pb2.Collection]): СЃРїРёСЃРѕРє коллекций расширения + clients (list[clients_pb2.Client]): СЃРїРёСЃРѕРє клиентов расширения + roles (list[roles_pb2.Role]): СЃРїРёСЃРѕРє ролей расширения + items (list[AbstractItem]): СЃРїРёСЃРѕРє item'РѕРІ расширения + __max_attempts_count (int): макс. РєРѕР»-РІРѕ попыток записи + __sleep_time (int): время ожидания перед попыткой + + """ + def __init__( self, collections_service: collections_pb2_grpc.CollectionsStub, @@ -76,6 +97,16 @@ class ExtensionSetup: # Работа СЃ ролями async def __remove_roles(self, space_id: str) -> list[str]: + """ + Метод для удаления ролей РёР· пространства. Возвращает массив СЃ ошибками + + Arguments: + space_id (str): идентификатор пространства + + Returns: + list[str] + """ + errors_list: list[str] = [] for role in self.roles: @@ -93,6 +124,16 @@ class ExtensionSetup: return errors_list async def __check_roles(self, space_id: str) -> list[str]: + """ + Метод для проверки ролей РёР· пространства. Возвращает массив СЃ ошибками + + Arguments: + space_id (str): идентификатор пространства + + Returns: + list[str] + """ + errors_list = [] for role in self.roles: @@ -106,6 +147,18 @@ class ExtensionSetup: return errors_list async def __update_roles(self, space_id: str, env_id: str) -> list[str]: + """ + Метод для установки ролей РІ пространство. Возвращает массив СЃ ошибками. Права доступа + которые были установлены РІ perxis вручную или РґСЂСѓРіРёРј расширением РЅРµ затираются. + + Arguments: + space_id (str): идентификатор пространства + env_id (str): идентификатор окружения + + Returns: + list[str] + """ + errors_list = [] for local_role in self.roles: @@ -186,6 +239,16 @@ class ExtensionSetup: # Работа СЃ клиентами async def __check_clients(self, space_id: str) -> list[str]: + """ + Метод для проверки клиентов РІ пространстве. Возвращает массив СЃ ошибками + + Arguments: + space_id (str): идентификатор пространства + + Returns: + list[str] + """ + errors_list = [] for client in self.clients: @@ -199,6 +262,16 @@ class ExtensionSetup: return errors_list async def __remove_clients(self, space_id: str) -> list[str]: + """ + Метод для удаления клиентов РёР· пространства. Возвращает массив СЃ ошибками + + Arguments: + space_id (str): идентификатор пространства + + Returns: + list[str] + """ + errors_list: list[str] = [] for client in self.clients: @@ -216,6 +289,17 @@ class ExtensionSetup: return errors_list async def __update_clients(self, space_id: str) -> list[str]: + """ + Метод для создания / обновления клиентов РІ пространстве. Возвращает массив СЃ ошибками. + Свойства oauth Рё api_key РЅРµ затираются РІ случае если клиент СѓР¶Рµ был создан + + Arguments: + space_id (str): идентификатор пространства + + Returns: + list[str] + """ + errors_list = [] for local_client in self.clients: @@ -278,6 +362,17 @@ class ExtensionSetup: # Работа СЃ коллекциями async def __remove_collections(self, space_id: str, env_id: str) -> list[str]: + """ + Метод для удаления коллекций РёР· определённого окружения пространства. Возвращает массив СЃ ошибками + + Arguments: + space_id (str): идентификатор пространства + env_id (str): идентификатор окружения + + Returns: + list[str] + """ + errors_list: list[str] = [] for collection in self.collections: @@ -295,6 +390,17 @@ class ExtensionSetup: return errors_list async def __check_collections(self, space_id: str, env_id: str) -> list[str]: + """ + Метод для проверки коллекций РёР· определённого окружения пространства. Возвращает массив СЃ ошибками + + Arguments: + space_id (str): идентификатор пространства + env_id (str): идентификатор окружения + + Returns: + list[str] + """ + errors_list = [] for collection in self.collections: @@ -316,6 +422,13 @@ class ExtensionSetup: 3. Сравнить схему коллекции РІ расширении Рё РІ perxis 4. Если схема изменена - обновить схему РІ perxis 5. Если обновлялась хотя Р±С‹ РѕРґРЅР° схема РІ perxis - запустить миграцию окружения + + Arguments: + space_id (str): идентификатор пространства + env_id (str): идентификатор окружения + + Returns: + list[str] """ need_to_migrate_environment = False @@ -388,8 +501,17 @@ class ExtensionSetup: return errors_list async def __migrate_environment(self, space_id: str, env_id: str) -> typing.Optional[str]: - # Так как perxis может РЅРµ сразу выставить коллекции / окружению статус ready операцию необходимо выполнять - # СЃ попытками + """ + Метод для миграции окружения. + Так как perxis может РЅРµ сразу выставить коллекции / окружению статус ready операцию необходимо выполнять + СЃ попытками + Arguments: + space_id (str): идентификатор пространства + env_id (str): идентификатор окружения + + Returns: + str | None + """ attempt = 0 is_ok = False @@ -421,8 +543,19 @@ class ExtensionSetup: return error_message async def __set_collection_schema(self, space_id: str, env_id: str, collection_id: str, schema: str) -> typing.Optional[str]: - # Так как perxis может РЅРµ сразу выставить коллекции / окружению статус ready операцию необходимо выполнять - # СЃ попытками + """ + Метод для установки схемы коллекции РІ определённом окружении пространства + Так как perxis может РЅРµ сразу выставить коллекции / окружению статус ready операцию необходимо выполнять + СЃ попытками + Arguments: + space_id (str): идентификатор пространства + env_id (str): идентификатор окружения + collection_id (str): идентификатор коллекции + schema (str): схема коллекции РІ формате json + + Returns: + str | None + """ attempt = 0 is_ok = False @@ -456,6 +589,17 @@ class ExtensionSetup: # Работа СЃ действиями async def __check_actions(self, space_id: str, env_id: str) -> list[str]: + """ + Метод для проверки действий РёР· определённого окружения пространства. Возвращает массив СЃ ошибками + + Arguments: + space_id (str): идентификатор пространства + env_id (str): идентификатор окружения + + Returns: + list[str] + """ + errors_list = [] ids = [data["id"] for data in self.actions if data.get("id")] @@ -490,6 +634,17 @@ class ExtensionSetup: return errors_list async def __update_actions(self, space_id: str, env_id: str) -> list[str]: + """ + Метод для создания / обновления действий РёР· определённого окружения пространства. Возвращает массив СЃ ошибками + + Arguments: + space_id (str): идентификатор пространства + env_id (str): идентификатор окружения + + Returns: + list[str] + """ + errors_list = [] not_found = False @@ -549,6 +704,17 @@ class ExtensionSetup: return errors_list async def __remove_actions(self, space_id: str, env_id: str) -> list[str]: + """ + Метод для удаления действий РёР· определённого окружения пространства. Возвращает массив СЃ ошибками + + Arguments: + space_id (str): идентификатор пространства + env_id (str): идентификатор окружения + + Returns: + list[str] + """ + errors_list = [] for action in self.actions: action_item = make_action_item(space_id, env_id, action) @@ -578,6 +744,17 @@ class ExtensionSetup: return errors_list async def __check_items(self, space_id: str, env_id: str) -> list[str]: + """ + Метод для item'РѕРІ действий РёР· определённого окружения пространства. Возвращает массив СЃ ошибками + + Arguments: + space_id (str): идентификатор пространства + env_id (str): идентификатор окружения + + Returns: + list[str] + """ + errors_list: list[str] = [] wrapper = PerxisItemsWrapper( self.items_service, @@ -611,6 +788,17 @@ class ExtensionSetup: return errors_list async def __update_items(self, space_id: str, env_id: str) -> list[str]: + """ + Метод для создания / обновления item'РѕРІ РёР· определённого окружения пространства. Возвращает массив СЃ ошибками + + Arguments: + space_id (str): идентификатор пространства + env_id (str): идентификатор окружения + + Returns: + list[str] + """ + errors_list: list[str] = [] wrapper = PerxisItemsWrapper( self.items_service, @@ -694,6 +882,17 @@ class ExtensionSetup: return errors_list async def __remove_items(self, space_id: str, env_id: str) -> list[str]: + """ + Метод для удаления item'РѕРІ РёР· определённого окружения пространства. Возвращает массив СЃ ошибками + + Arguments: + space_id (str): идентификатор пространства + env_id (str): идентификатор окружения + + Returns: + list[str] + """ + errors_list: list[str] = [] wrapper = PerxisItemsWrapper( self.items_service, @@ -742,6 +941,17 @@ class ExtensionSetup: return errors_list async def __update_view_role(self, space_id: str, env_id: str, mode: str = "add") -> list[str]: + """ + Метод для создания / обновления роли view РёР· определённого окружения пространства. Возвращает массив СЃ ошибками + + Arguments: + space_id (str): идентификатор пространства + env_id (str): идентификатор окружения + + Returns: + list[str] + """ + errors = [] # РќСѓР¶РЅС‹ только относящиеся Рє синхронизации элементы @@ -850,6 +1060,20 @@ class ExtensionSetup: async def install(self, space_id: str, env_id: str, use_force: bool) -> list[str]: + """ + Метод установки расширения. + TODO разобраться РЅСѓР¶РЅС‹ ли отдельные методы `install` Рё `update` + TODO разобраться СЃ аргументом `use_force` + + Arguments: + space_id (str): идентификатор пространства + env_id (str): идентификатор окружения + use_force (bool): флаг РЅРµ используется + + Returns: + list[str] + """ + errors = [] try: @@ -866,6 +1090,20 @@ class ExtensionSetup: return errors async def update(self, space_id: str, env_id: str, use_force: bool) -> list[str]: + """ + Метод установки расширения. + TODO разобраться РЅСѓР¶РЅС‹ ли отдельные методы `install` Рё `update` + TODO разобраться СЃ аргументом `use_force` + + Arguments: + space_id (str): идентификатор пространства + env_id (str): идентификатор окружения + use_force (bool): флаг РЅРµ используется + + Returns: + list[str] + """ + errors = [] try: @@ -882,6 +1120,17 @@ class ExtensionSetup: return errors async def check(self, space_id: str, env_id: str) -> list[str]: + """ + Метод проверки расширения. + + Arguments: + space_id (str): идентификатор пространства + env_id (str): идентификатор окружения + + Returns: + list[str] + """ + errors = [] try: @@ -897,6 +1146,18 @@ class ExtensionSetup: return errors async def uninstall(self, space_id: str, env_id: str, use_remove: bool) -> list[str]: + """ + Метод удаления расширения. + + Arguments: + space_id (str): идентификатор пространства + env_id (str): идентификатор окружения + use_remove (str): удалять данные расширения + + Returns: + list[str] + """ + errors = [] if use_remove: try: diff --git a/perxis/extensions/item_models.py b/perxis/extensions/item_models.py index 02acd8608eebaa6f06717d4e39a65b024257e8a6..120e59faf967ea4f028df845831a5d07e805acd4 100644 --- a/perxis/extensions/item_models.py +++ b/perxis/extensions/item_models.py @@ -1,3 +1,8 @@ +""" +Модуль содержит классы для работы СЃ item'ами РІ расширениях +""" + + import abc from typing import TypedDict, NotRequired @@ -7,6 +12,9 @@ from perxis.extensions.item_rules import AbstractRule, IfCollectionExists, IfExt class DataSourceData(TypedDict): + """ + Маппинг для объекта data элементов коллекции web_datasources + """ query: NotRequired[str] content_type: NotRequired[str] exclude: NotRequired[bool] @@ -15,6 +23,10 @@ class DataSourceData(TypedDict): class SyncPolicyData(TypedDict): + """ + Маппинг для объекта data элементов коллекции hoop_item_sync_policies + """ + key: NotRequired[str] export_view: NotRequired[bool] remove_collection: NotRequired[bool] @@ -31,6 +43,16 @@ class AbstractItem(metaclass=abc.ABCMeta): """ Абстрактный класс для item'Р°. Нужен для определения общих свойств без реализации какого то конкретного конструктора. + + Attributes: + collection_id (str): идентификатор коллекции + data (dict): данные item'Р° + rules (list[AbstractRule]): СЃРїРёСЃРѕРє правил применения item'Р° + identifier_field (str): поле РїРѕ которому будет производиться РїРѕРёСЃРє элемента РІ perxis + with_update (bool): указание возможности обновления элемента РїСЂРё установке или удалении расширения + РІ случае если РѕРЅ СѓР¶Рµ существует РІ perxis + with_delete (bool): указание возможности удаления элемента РІ случае если РѕРЅ существует РІ perxis РїСЂРё + удалении расширения """ collection_id: str @@ -43,16 +65,40 @@ class AbstractItem(metaclass=abc.ABCMeta): @property def identifier(self): + """ + Метод для получения значения идентификатора элемента РёР· данных + + Returns: + str + """ return self.data[self.identifier_field] @property def struct(self) -> Struct: + """ + Метод для преобразования данных элемента РёР· dict РІ Struct + + Returns: + Struct + """ + s = Struct() s.update(self.data) return s async def all_rules_is_satisfied(self, space_id: str, env_id: str) -> bool: + """ + Метод для проверки применимости всех правил item'Р° для указанных пространства Рё окружения. + Возвращает True РІ случае если РІСЃРµ указанные для item'Р° правила вернули True + + Arguments: + space_id (str): идентификатор пространства + env_id (str): идентификатор окружения + Returns: + bool + """ + return all( [ await rule(item=self, space_id=space_id, env_id=env_id) @@ -63,8 +109,32 @@ class AbstractItem(metaclass=abc.ABCMeta): def merge_data(self, data_from_perxis: dict) -> Struct: """ - Мерж данных РёР· перксиса Рё расширения. Р’ приоритете данные расширения, если что-то + Метод для слияния данных РёР· perxis Рё расширения. Нужен для того чтобы РЅРµ затирать те поля + что были заполнены РІ базе данных РЅРѕ отсутствуют РІ item'Рµ расширения. Р’ приоритете данные расширения, если что-то РёР· РЅРёС… было вручную изменено РІ perxis - значение будет затёрто + + Пример. + ``` + # Данные РІ perxis + { + "a": 1, + "b": 2, + "c": 3, + } + + # Данные РІ расширении + { + "b": 1, + "c": 3, + } + + # Ртоговый результат + { + "a": 1, + "b": 1, + "c": 3, + } + ``` """ s = Struct() @@ -90,6 +160,18 @@ class Item(AbstractItem): identifier_field: str = "id", rules: list[AbstractRule] | None = None ): + """ + Arguments: + collection_id (str): идентификатор коллекции + data (dict): данные item'Р° + with_update (bool): указание возможности обновления элемента РїСЂРё установке или удалении расширения + РІ случае если РѕРЅ СѓР¶Рµ существует РІ perxis + with_delete (bool): указание возможности удаления элемента РІ случае если РѕРЅ существует РІ perxis РїСЂРё + удалении расширения + rules (list[AbstractRule]): СЃРїРёСЃРѕРє правил применения item'Р° + identifier_field (str): поле РїРѕ которому будет производиться РїРѕРёСЃРє элемента РІ perxis + """ + self.collection_id = collection_id self.data = data self.identifier_field = identifier_field @@ -100,7 +182,8 @@ class Item(AbstractItem): class DataSourceItem(AbstractItem): """ - Класс для системной коллекции web_datasources + Класс для системной коллекции web_datasources. РџРѕ умолчанию имеет указание проверки + наличия коллекции РІ rules """ def __init__( @@ -113,6 +196,19 @@ class DataSourceItem(AbstractItem): with_update: bool = True, with_delete: bool = True, ): + """ + Arguments: + collection_id (str): Рдентификатор коллекции для выгрузки данных + query (str): Фильтр РїРѕ данным + content_type (str): Настройки типа данных + exclude (bool): РќРµ выгружать источник РІ каталог данных + with_update (bool): указание возможности обновления элемента РїСЂРё установке или удалении расширения + РІ случае если РѕРЅ СѓР¶Рµ существует РІ perxis + with_delete (bool): указание возможности удаления элемента РІ случае если РѕРЅ существует РІ perxis РїСЂРё + удалении расширения + rules (list[AbstractRule]): СЃРїРёСЃРѕРє правил применения item'Р° + """ + self.collection_id = "web_datasources" self.rules = rules or [IfCollectionExists()] self.data = { @@ -129,7 +225,9 @@ class DataSourceItem(AbstractItem): class SyncPolicyItem(AbstractItem): """ - Класс для коллекции hoop_item_sync_policies расширения perxishoop + Класс для коллекции hoop_item_sync_policies расширения perxishoop. РџРѕ умолчанию item имеет + правило для проверки установленного расширения perxishoop. Р’ качестве идентификатора записи + используется поле `collection` """ identifier_field = "collection" @@ -149,6 +247,26 @@ class SyncPolicyItem(AbstractItem): with_update: bool = False, with_delete: bool = False, ): + """ + Arguments: + collection_id (str): Рдентификатор коллекции для выгрузки данных + name (str): Название + key (str): Ключ для синхронизации + export_view (bool): Создать отображение коллекции + remove_collection (bool): Удалить коллекцию + deny_publish (bool): Запретить операции СЃ публикацией элементов ведущего пространства + deny_delete (bool): Запретить удаление элементов ведущего пространства + deny_create (bool): Запретить создавать новые элементы + deny_read (bool): Запретить чтение элементов + hidden (bool): Скрыть коллекцию + + with_update (bool): указание возможности обновления элемента РїСЂРё установке или удалении расширения + РІ случае если РѕРЅ СѓР¶Рµ существует РІ perxis + with_delete (bool): указание возможности удаления элемента РІ случае если РѕРЅ существует РІ perxis РїСЂРё + удалении расширения + rules (list[AbstractRule]): СЃРїРёСЃРѕРє правил применения item'Р° + """ + self.collection_id = "hoop_item_sync_policies" self.rules = rules or [IfExtensionInstalled("perxishoop")] diff --git a/perxis/extensions/item_rules.py b/perxis/extensions/item_rules.py index 106a98e28a942b7b4da215ec468227e30d7a8e01..f08641fdfd84fc2e51f8586f983c1341f78880be 100644 --- a/perxis/extensions/item_rules.py +++ b/perxis/extensions/item_rules.py @@ -25,20 +25,39 @@ class AbstractRule(metaclass=abc.ABCMeta): РЅРµ был излишне длинным - это делается неявным РїСЂРѕР±СЂРѕСЃРѕРј свойств РёР· ExtensionService. Таким образом Рё РєРѕРґ короче получается, Рё РІ случае если Р±СѓРґСѓС‚ добавлены какие то новые сервисы РѕРЅРё автоматически Р±СѓРґСѓС‚ проброшены СЃСЋРґР°. + + Attributes: + channel (grpc.Channel): ссылка РЅР° grpc канал + *_service: ссылки РЅР° сервисы. Рменование аналогично тому как РѕРЅРё указаны РІ классе ExtensionService """ @property def rule_name(self): + """ + Название правила + + Returns: + str + """ return self.__class__.__name__ @property def logger(self): + """ + Логгер для правила + + Returns: + logging.Logger + """ return logging.getLogger(self.rule_name) def bind_services(self, service_list: list[tuple[str, object]]): """ РќСѓР¶РЅРѕ для автоматического подключения сервисов РёР· ExtensionService. Проблема РІ том что РёС… очень РјРЅРѕРіРѕ Рё если руками СЏРІРЅРѕ указывать - будет большой boilerplate + + Arguments: + service_list (list[tuple[str, object]]): СЃРїРёСЃРѕРє доступных сервисов Рё РёС… идентифиакторы """ for service_name, service in service_list: @@ -46,6 +65,16 @@ class AbstractRule(metaclass=abc.ABCMeta): @abc.abstractmethod async def act(self, item: "AbstractItem", space_id: str, env_id: str): + """ + Абстрактный метод для указания логики правила. Обработка исключений ведётся РІ методе __call__, + уровнем выше. Если метод `act` отработал без исключений считается что правило успешно применено + + Arguments: + item (AbstractItem): item СЃ которым будет вестись работа + space_id (str): идентификатор пространства + env_id (str): идентификатор окружения + """ + raise NotImplementedError() async def __call__(self, item: "AbstractItem", space_id: str, env_id: str) -> bool: @@ -71,6 +100,15 @@ class IfCollectionExists(AbstractRule): """ async def act(self, item: "AbstractItem", space_id: str, env_id: str): + """ + Проверка наличия коллекции РІ perxis для указанного пространства Рё окружения + + Arguments: + item (AbstractItem): item СЃ которым будет вестись работа + space_id (str): идентификатор пространства + env_id (str): идентификатор окружения + """ + message = await self.collections_service.Get( collections_pb2.GetRequest( space_id=space_id, @@ -92,9 +130,22 @@ class IfExtensionInstalled(AbstractRule): required_extension: str def __init__(self, required_extension: str): + """ + Arguments: + required_extension (str): Рдентификатор требуемоого расширения + """ self.required_extension = required_extension async def act(self, item: "AbstractItem", space_id: str, env_id: str): + """ + Проверка наличия установленного расширения РІ perxis для указанного пространства Рё окружения + + Arguments: + item (AbstractItem): item СЃ которым будет вестись работа + space_id (str): идентификатор пространства + env_id (str): идентификатор окружения + """ + registered_extensions = await self.ext_manager_service.ListRegisteredExtensions( manager_service_pb2.ListRegisteredExtensionsRequest() ) diff --git a/perxis/extensions/utils.py b/perxis/extensions/utils.py index 167302d4b0b409d09d80e55ec4e46a7a2f465e3b..40b8bdd669ed2a03d0ecd9aadadef76e386f342e 100644 --- a/perxis/extensions/utils.py +++ b/perxis/extensions/utils.py @@ -1,3 +1,7 @@ +""" +Модуль содержит вспомогательные функции для сервисов расширений +""" + from typing import Optional from perxis.extensions import manager_service_pb2 @@ -14,7 +18,7 @@ def datasource_items_from_collections( """ Создание записей источников данных РЅР° базе маппинга коллекций. - Args: + Arguments: collections_map: Dict РІРёРґР° [collection_id, collection_name] datasource_data: Dict СЃРѕ структурой DataSourceData для item'РѕРІ коллекций override_for_collections: Dict для переопределения значений для отдельных коллекций @@ -58,7 +62,7 @@ def sync_policies_from_collections( """ Создание записей синхронизации коллекций РЅР° базе маппинга коллекций - Args: + Arguments: collections_map: Dict РІРёРґР° [collection_id, collection_name] sync_policies_data: Dict СЃРѕ структурой SyncPolicyData для item'РѕРІ коллекций override_for_collections: Dict для переопределения значений для отдельных коллекций @@ -98,6 +102,21 @@ def get_extension_descriptor( ext_version: str, ext_version_description: str, ext_deps: Optional[list[str]] = None ) -> manager_service_pb2.ExtensionDescriptor: + """ + Функция для получения дескриптора расширения РЅР° основании его данных + + Arguments: + ext_host (str): адрес расширения + ext_id (str): идентификатор расширения + ext_name (str): название расширения + ext_version (str): версия расширения + ext_version_description (str): описание расширения + ext_deps (list[str] | None): зависимости расширения + + Returns: + manager_service_pb2.ExtensionDescriptor + """ + if not ext_deps: ext_deps = []