0
0
mirror of https://gitflic.ru/project/maks1ms/ocab.git synced 2024-12-24 16:44:44 +03:00

добавлена проверка зависимостей

This commit is contained in:
Maxim Slipenko 2024-07-10 12:36:56 +03:00
parent 89fe6a3520
commit 3b849417c3
12 changed files with 71 additions and 28 deletions

View File

@ -17,10 +17,11 @@
"name": "Info", "name": "Info",
"description": "Модуль с информацией", "description": "Модуль с информацией",
"author": "OCAB Team", "author": "OCAB Team",
"version": "1.0", "version": "1.0.0",
"privileged": false, "privileged": false,
"dependencies": { "dependencies": {
"standard.roles": "^1.0.0" "standard.roles": "^1.0.0",
"standard.database": "^1.0.0"
} }
} }
``` ```
@ -54,7 +55,6 @@
## Взаимодействие между модулями ## Взаимодействие между модулями
Модули могут взаимодействовать друг с другом через API, предоставляемое системой управления модулями. Модули могут взаимодействовать друг с другом через [API](../src/ocab_core/modules_system/public_api/__init__.py), предоставляемое системой управления модулями.
Например, есть `ocab_core.modules_system.public_api.get_module`. Например, есть функция `get_module`. Она позволяет получить модуль или предоставляемые им объекты по его идентификатору.
Функция, которая получает модуль или предоставляемые им объекты по его идентификатору.

13
poetry.lock generated
View File

@ -1124,6 +1124,17 @@ pygments = ">=2.13.0,<3.0.0"
[package.extras] [package.extras]
jupyter = ["ipywidgets (>=7.5.1,<9)"] jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "semver"
version = "3.0.2"
description = "Python helper for Semantic Versioning (https://semver.org)"
optional = false
python-versions = ">=3.7"
files = [
{file = "semver-3.0.2-py3-none-any.whl", hash = "sha256:b1ea4686fe70b981f85359eda33199d60c53964284e0cfb4977d243e37cf4bf4"},
{file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"},
]
[[package]] [[package]]
name = "stevedore" name = "stevedore"
version = "5.2.0" version = "5.2.0"
@ -1307,4 +1318,4 @@ multidict = ">=4.0"
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = ">=3.11.6,<3.13" python-versions = ">=3.11.6,<3.13"
content-hash = "ea35c6a1b64b487fba434fe79cdbf6b78e21941e28d7cda433fd4093c3a0923d" content-hash = "5df07f0efc29d67f3ef2952b7d26a58098c16d5a391f469258f91ba47ebb972f"

View File

@ -30,6 +30,7 @@ pyyaml = "^6.0.1"
requests = "^2.32.3" requests = "^2.32.3"
restrictedpython = "^7.1" restrictedpython = "^7.1"
dataclasses-json = "^0.6.7" dataclasses-json = "^0.6.7"
semver = "^3.0.2"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
flake8 = "^7.1.0" flake8 = "^7.1.0"
@ -37,6 +38,7 @@ black = "^24.4.2"
isort = "^5.13.2" isort = "^5.13.2"
bandit = "^1.7.9" bandit = "^1.7.9"
pre-commit = "^3.7.1" pre-commit = "^3.7.1"
semver = "^3.0.2"
[tool.black] [tool.black]
line-length = 88 line-length = 88

View File

@ -12,6 +12,8 @@ class ModuleInfo:
description: str description: str
version: str version: str
author: str author: str
privileged: bool
dependencies: dict
class AbstractLoader: class AbstractLoader:

View File

@ -18,13 +18,9 @@ class FSLoader(UnsafeFSLoader):
self.builtins["__import__"] = self._hook_import self.builtins["__import__"] = self._hook_import
def load(self): def load(self):
# TODO handle dependencies from info info = self.info()
if info.privileged:
self.info() raise Exception("Only non privileged modules are allowed to be imported")
# with open(os.path.join(self.path, "__init__.py"), "r") as f:
# source = f.read()
return self._hook_import(".") return self._hook_import(".")
def _resolve_module_from_path(self, module_name: str): def _resolve_module_from_path(self, module_name: str):
@ -52,8 +48,7 @@ class FSLoader(UnsafeFSLoader):
if name == allowed or name.startswith(f"{allowed}."): if name == allowed or name.startswith(f"{allowed}."):
return __import__(name, *args, **kwargs) return __import__(name, *args, **kwargs)
# TODO: allow only public api for modules if name == "ocab_core.modules_system.public_api":
if name.startswith("ocab_core."):
return __import__(name, *args, **kwargs) return __import__(name, *args, **kwargs)
module_file_path = self._resolve_module_from_path(name) module_file_path = self._resolve_module_from_path(name)

View File

@ -1,12 +1,48 @@
import semver
from ocab_core.modules_system.loaders.base import AbstractLoader from ocab_core.modules_system.loaders.base import AbstractLoader
def is_version_compatible(version, requirement):
def parse_requirement(req):
if req.startswith("^"):
base_version = req[1:]
base_version_info = semver.VersionInfo.parse(base_version)
range_start = base_version_info
range_end = base_version_info.bump_major()
return [f">={range_start}", f"<{range_end}"]
else:
return [req]
for r in parse_requirement(requirement):
if not semver.Version.parse(version).match(r):
return False
return True
class ModulesManager: class ModulesManager:
def __init__(self): def __init__(self):
self.modules = {} self.modules = {}
def load(self, loader: AbstractLoader): def load(self, loader: AbstractLoader):
info = loader.info() info = loader.info()
if info.id in self.modules:
return
for dependency, version in info.dependencies.items():
if dependency not in self.modules:
raise Exception(
f"Module {info.id} depends on {dependency}, but it is not loaded"
)
loaded_dependency_info = self.modules[dependency]["info"]
if not is_version_compatible(loaded_dependency_info.version, version):
raise Exception(
f"Module {info.id} depends on {dependency}, "
f"but version {version} is not compatible"
)
module = loader.load() module = loader.load()
self.modules[info.id] = { self.modules[info.id] = {

View File

@ -1 +1,3 @@
from ocab_core.logger import log # noqa
from .public_api import Storage, get_module, register_router from .public_api import Storage, get_module, register_router

View File

@ -3,7 +3,7 @@
"name": "Config YAML", "name": "Config YAML",
"description": "Модуль для работы с конфигурационным файлом бота (YAML)", "description": "Модуль для работы с конфигурационным файлом бота (YAML)",
"author": "OCAB Team", "author": "OCAB Team",
"version": "1.0", "version": "1.0.0",
"privileged": true, "privileged": true,
"dependencies": {} "dependencies": {}
} }

View File

@ -3,7 +3,7 @@
"name": "Database", "name": "Database",
"description": "Модуль для работы с БД", "description": "Модуль для работы с БД",
"author": "OCAB Team", "author": "OCAB Team",
"version": "1.0", "version": "1.0.0",
"privileged": true, "privileged": true,
"dependencies": {} "dependencies": {}
} }

View File

@ -1,24 +1,18 @@
# flake8: noqa # flake8: noqa
from typing import Any, Type from typing import Type
from aiogram import Bot from aiogram import Bot
from aiogram.types import Message from aiogram.types import Message
from ocab_core.logger import log from ocab_core.modules_system.public_api import get_module, log
from ocab_core.modules_system.public_api import get_module
from .interfaces import IDbApi, IRoles from .interfaces import IDbApi, IRoles
# from src.modules.standard.database import db_api
db_api: Type[IDbApi] = get_module( db_api: Type[IDbApi] = get_module(
"standard.database", "standard.database",
"db_api", "db_api",
) )
# db_api.init_db_connection()
Roles: Type[IRoles] = get_module("standard.roles", "Roles") Roles: Type[IRoles] = get_module("standard.roles", "Roles")

View File

@ -3,9 +3,10 @@
"name": "Info", "name": "Info",
"description": "Модуль с информацией", "description": "Модуль с информацией",
"author": "OCAB Team", "author": "OCAB Team",
"version": "1.0", "version": "1.0.0",
"privileged": false, "privileged": false,
"dependencies": { "dependencies": {
"standard.roles": "^1.0.0" "standard.roles": "^1.0.0",
"standard.database": "^1.0.0"
} }
} }

View File

@ -3,7 +3,7 @@
"name": "Roles", "name": "Roles",
"description": "Модуль для работы с ролями", "description": "Модуль для работы с ролями",
"author": "OCAB Team", "author": "OCAB Team",
"version": "1.0", "version": "1.0.0",
"privileged": true, "privileged": true,
"dependencies": { "dependencies": {
"standard.config": "^1.0.0", "standard.config": "^1.0.0",