Commit 899eea71 authored by Guillaume Binet's avatar Guillaume Binet

Cross dependencies on plugin and repo managers

Also fixed a type error on the dependency check util.
parent 9230a832
......@@ -8,9 +8,11 @@ import sys
import traceback
from pathlib import Path
from typing import Tuple, Sequence, Dict, Union, Any, Type, Set, List
from typing import Tuple, Dict, Any, Type, Set, List, Optional
from errbot.flow import BotFlow, Flow
from errbot.repo_manager import BotRepoManager, check_dependencies
from errbot.storage.base import StoragePluginBase
from .botplugin import BotPlugin
from .plugin_info import PluginInfo
from .utils import version2tuple, collect_roots
......@@ -79,40 +81,6 @@ def install_packages(req_path: Path):
return sys.exc_info()
def check_dependencies(req_path: Path) -> Tuple[Union[str, None], Sequence[str]]:
""" This methods returns a pair of (message, packages missing).
Or None, [] if everything is OK.
"""
log.debug('check dependencies of %s', req_path)
# noinspection PyBroadException
try:
from pkg_resources import get_distribution
missing_pkg = []
if not os.path.isfile(req_path):
log.debug('%s has no requirements.txt file', req_path)
return None, missing_pkg
with open(req_path) as f:
for line in f:
stripped = line.strip()
# skip empty lines.
if not stripped:
continue
# noinspection PyBroadException
try:
get_distribution(stripped)
except Exception:
missing_pkg.append(stripped)
if missing_pkg:
return f'You need these dependencies for {req_path}: ' + ','.join(missing_pkg), missing_pkg
return None, missing_pkg
except Exception:
log.exception('Problem checking for dependencies.')
return 'You need to have setuptools installed for the dependency check of the plugins', []
def check_python_plug_section(plugin_info: PluginInfo) -> bool:
""" Checks if we have the correct version to run this plugin.
Returns true if the plugin is loadable """
......@@ -161,11 +129,17 @@ BL_PLUGINS = 'bl_plugins'
class BotPluginManager(StoreMixin):
def __init__(self, storage_plugin, repo_manager, extra, autoinstall_deps, core_plugins, plugins_callback_order):
def __init__(self,
storage_plugin: StoragePluginBase,
repo_manager: BotRepoManager,
extra_plugin_dir: Optional[str],
autoinstall_deps: bool,
core_plugins: Tuple[str, ...],
plugins_callback_order: Tuple[Optional[str], ...]):
super().__init__()
self.bot = None
self.autoinstall_deps = autoinstall_deps
self.extra = extra
self._extra_plugin_dir = extra_plugin_dir
self.core_plugins = core_plugins
# Make sure there is a 'None' entry in the callback order, to include
# any plugin not explicitly ordered.
......@@ -273,8 +247,8 @@ class BotPluginManager(StoreMixin):
self.flows, self.flow_infos, feedback)
return feedback
def _update_plugin_places(self, path_list, extra_plugin_dir) -> Dict[Path, str]:
repo_roots = (CORE_PLUGINS, extra_plugin_dir, path_list)
def _update_plugin_places(self, path_list) -> Dict[Path, str]:
repo_roots = (CORE_PLUGINS, self._extra_plugin_dir, path_list)
all_roots = collect_roots(repo_roots)
......@@ -356,7 +330,7 @@ class BotPluginManager(StoreMixin):
# this will load the plugins the admin has setup at runtime
def update_dynamic_plugins(self):
""" It returns a dictionary of path -> error strings."""
return self._update_plugin_places(self.repo_manager.get_all_repos_paths(), self.extra)
return self._update_plugin_places(self.repo_manager.get_all_repos_paths())
def activate_non_started_plugins(self):
"""
......
from typing import Tuple, Union, Sequence
import json
import re
import logging
import os
import shutil
......@@ -6,15 +10,11 @@ from collections import namedtuple
from datetime import timedelta, datetime
from os import path
import tarfile
from pathlib import Path
from urllib.error import HTTPError, URLError
from urllib.request import urlopen
from urllib.parse import urlparse
import json
import re
from errbot.plugin_manager import check_dependencies
from errbot.storage import StoreMixin
from .utils import ON_WINDOWS
......@@ -82,6 +82,40 @@ def which(program):
return None
def check_dependencies(req_path: Path) -> Tuple[Union[str, None], Sequence[str]]:
""" This methods returns a pair of (message, packages missing).
Or None, [] if everything is OK.
"""
log.debug('check dependencies of %s', req_path)
# noinspection PyBroadException
try:
from pkg_resources import get_distribution
missing_pkg = []
if not req_path.is_file():
log.debug('%s has no requirements.txt file', req_path)
return None, missing_pkg
with req_path.open() as f:
for line in f:
stripped = line.strip()
# skip empty lines.
if not stripped:
continue
# noinspection PyBroadException
try:
get_distribution(stripped)
except Exception:
missing_pkg.append(stripped)
if missing_pkg:
return f'You need these dependencies for {req_path}: ' + ','.join(missing_pkg), missing_pkg
return None, missing_pkg
except Exception:
log.exception('Problem checking for dependencies.')
return 'You need to have setuptools installed for the dependency check of the plugins', []
class BotRepoManager(StoreMixin):
"""
Manages the repo list, git clones/updates or the repos.
......@@ -252,7 +286,7 @@ class BotRepoManager(StoreMixin):
err = p.stderr.read().strip().decode('utf-8')
if err:
feedback += err + '\n' + '-' * 50 + '\n'
dep_err, missing_pkgs = check_dependencies(d)
dep_err, missing_pkgs = check_dependencies(Path(d) / 'requirements.txt')
if dep_err:
feedback += dep_err + '\n'
yield d, not p.wait(), feedback
......
......@@ -330,7 +330,8 @@ def test_callback_no_command(testbot):
expected_str = "Command fell through: {}".format(cmd)
testbot.exec_command('!plugin deactivate CommandNotFoundFilter')
testbot.bot.plugin_manager._update_plugin_places([], extra_plugin_dir)
testbot.bot.plugin_manager._extra_plugin_dir = extra_plugin_dir
testbot.bot.plugin_manager._update_plugin_places([])
testbot.exec_command('!plugin activate TestCommandNotFoundFilter')
assert expected_str == testbot.exec_command(cmd)
......
......@@ -2,7 +2,9 @@ import os
import pytest
import tempfile
from configparser import ConfigParser
from pathlib import Path
import errbot.repo_manager
from errbot import plugin_manager
from errbot.plugin_info import PluginInfo
from errbot.plugin_manager import IncompatiblePluginException
......@@ -17,25 +19,22 @@ def touch(name):
def test_check_dependencies():
response, deps = plugin_manager.check_dependencies(os.path.join(os.path.dirname(__file__),
'assets',
'requirements_never_there.txt'))
response, deps = errbot.repo_manager.check_dependencies(Path(__file__).parent / 'assets' /
'requirements_never_there.txt')
assert 'You need these dependencies for' in response
assert 'impossible_requirement' in response
assert ['impossible_requirement'] == deps
def test_check_dependencies_no_requirements_file():
response, deps = plugin_manager.check_dependencies(os.path.join(os.path.dirname(__file__),
'assets',
'requirements_non_existent.txt'))
response, deps = errbot.repo_manager.check_dependencies(Path(__file__).parent / 'assets' /
'requirements_non_existent.txt')
assert response is None
def test_check_dependencies_requirements_file_all_installed():
response, deps = plugin_manager.check_dependencies(os.path.join(os.path.dirname(__file__),
'assets',
'requirements_already_there.txt'))
response, deps = errbot.repo_manager.check_dependencies(Path(__file__).parent / 'assets' /
'requirements_already_there.txt')
assert response is None
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment