Skip to content

Commit 7157113

Browse files
author
DUYN
committed
Initialize remote loaders
1 parent 78f293e commit 7157113

File tree

4 files changed

+146
-25
lines changed

4 files changed

+146
-25
lines changed

bot/remote_loaders/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from bot.remote_loaders.strategy_remote_loader import StrategyRemoteLoader
2+
from bot.remote_loaders.data_provider_remote_loader import DataProviderRemoteLoader
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import logging
2+
from pathlib import Path
3+
4+
from bot.data import DataProvider
5+
from bot import OperationalException
6+
from bot.constants import PLUGIN_STRATEGIES_DIR, PLUGIN_DATA_PROVIDERS_DIR
7+
from bot.remote_loaders.remote_loader import RemoteLoader
8+
9+
logger = logging.getLogger(__name__)
10+
11+
12+
class DataProviderRemoteLoader(RemoteLoader):
13+
14+
def load_data_provider(self, data_provider_class_name: str) -> DataProvider:
15+
16+
logger.info("Loading remote data providers ...")
17+
18+
if not data_provider_class_name:
19+
raise OperationalException("Provided data provider has it search class name not set")
20+
21+
data_providers_dir = Path(PLUGIN_DATA_PROVIDERS_DIR)
22+
modules = self.locate_python_modules(data_providers_dir)
23+
location = self.locate_class(modules, data_provider_class_name)
24+
generator = self.create_class_generators(location, data_provider_class_name, DataProvider)
25+
data_provider: DataProvider = next(generator, None)
26+
27+
if data_provider and issubclass(data_provider, DataProvider):
28+
return data_provider
29+
30+
raise OperationalException(
31+
f"Impossible to load Strategy '{data_provider_class_name}'. This strategy does not exist "
32+
"or contains Python code errors."
33+
)
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import logging
2+
from abc import ABC
3+
from pathlib import Path
4+
from inspect import getmembers, isclass
5+
from typing import List, Type, Generator, Any
6+
from importlib.util import spec_from_file_location, module_from_spec
7+
8+
from bot import OperationalException, DependencyException
9+
10+
11+
logger = logging.getLogger(__name__)
12+
13+
14+
class RemoteLoader(ABC):
15+
"""
16+
RemoteLoader class: abstract class with some util functions to handle searching for modules,
17+
classes and instantiating classes
18+
"""
19+
20+
@staticmethod
21+
def locate_python_modules(dir_path: Path) -> List[Path]:
22+
"""
23+
Functions that will search through all the files in a directory to locate the class matching the
24+
given class name.
25+
"""
26+
27+
if not dir_path.is_dir():
28+
raise OperationalException("Given directory path is not a directory")
29+
30+
modules: List[Path] = []
31+
32+
logger.info("Searching in directory {} ...".format(dir_path))
33+
34+
for entry in dir_path.iterdir():
35+
36+
if not str(entry).endswith('.py'):
37+
continue
38+
39+
logger.info("Found module: {}, appending it to search paths".format(str(entry)))
40+
modules.append(entry)
41+
42+
return modules
43+
44+
@staticmethod
45+
def locate_class(modules: List[Path], class_name: str) -> Path:
46+
"""
47+
Function that will search all the given modules and will return the corresponding path where
48+
the class is located.
49+
"""
50+
51+
logger.info("Searching for class: {}".format(class_name))
52+
53+
for module_path in modules:
54+
spec = spec_from_file_location(module_path.stem, str(module_path))
55+
module = module_from_spec(spec)
56+
57+
try:
58+
spec.loader.exec_module(module)
59+
except (ModuleNotFoundError, SyntaxError) as err:
60+
# Catch errors in case a specific module is not installed
61+
logger.warning(f"Could not import {module_path} due to '{err}'")
62+
63+
if hasattr(module, class_name):
64+
logger.info("Found class {} in module {}".format(class_name, str(module_path)))
65+
return module_path
66+
67+
raise DependencyException("Could not find given class in selection of modules")
68+
69+
@staticmethod
70+
def create_class_generators(module_path: Path, class_name: str,
71+
class_type: Type[Any]) -> Generator[Any, None, None]:
72+
"""
73+
Function that creates a generator for a given module path and class name
74+
"""
75+
spec = spec_from_file_location(module_path.stem, str(module_path))
76+
module = module_from_spec(spec)
77+
78+
try:
79+
spec.loader.exec_module(module)
80+
except (ModuleNotFoundError, SyntaxError) as err:
81+
# Catch errors in case a specific module is not installed
82+
logger.warning(f"Could not import {module_path} due to '{err}'")
83+
84+
object_generators = (
85+
obj for name, obj in getmembers(module, isclass) if (class_name is None or class_name == name)
86+
and class_type in obj.__bases__
87+
)
88+
89+
return object_generators
Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,34 @@
1-
import os
2-
from typing import Dict, Any, List
1+
import logging
2+
from pathlib import Path
33

4+
from bot.strategies import Strategy
45
from bot import OperationalException
5-
from bot.strategy.strategy import Strategy
6+
from bot.constants import PLUGIN_STRATEGIES_DIR
7+
from bot.remote_loaders.remote_loader import RemoteLoader
68

9+
logger = logging.getLogger(__name__)
710

8-
class StrategyRemoteLoader:
911

10-
def __init__(self, config: Dict[str, Any]):
12+
class StrategyRemoteLoader(RemoteLoader):
1113

12-
self._strategies: List[Strategy] = None
14+
def load_strategy(self, strategy_class_name: str) -> Strategy:
1315

14-
if not config.get('strategies'):
15-
raise OperationalException(
16-
"Could not resolve strategies, please provide the strategy "
17-
"paths in your config file. You could also use de default strategies, that can be found in the "
18-
"strategies directory. If you have difficulties creating your own strategies, please see the "
19-
"documentation"
20-
)
16+
logger.info("Loading remote strategies ...")
2117

22-
strategies = config.get('strategies')
18+
if not strategy_class_name:
19+
raise OperationalException("Provided strategies has it search class name not set")
2320

24-
for strategy in strategies:
25-
path = strategy.get('path')
26-
self._strategies.append(self.load_strategy(path))
27-
28-
@staticmethod
29-
def load_strategy(path: str) -> Strategy:
30-
31-
if not path:
32-
raise OperationalException("Provided strategies has it file path not set")
33-
34-
current_path = os.path.dirname(os.path.abspath(__file__))
21+
strategies_dir = Path(PLUGIN_STRATEGIES_DIR)
22+
modules = self.locate_python_modules(strategies_dir)
23+
location = self.locate_class(modules, strategy_class_name)
24+
generator = self.create_class_generators(location, strategy_class_name, Strategy)
25+
strategy: Strategy = next(generator, None)
3526

27+
if strategy and issubclass(strategy, Strategy):
28+
return strategy
3629

30+
raise OperationalException(
31+
f"Impossible to load Strategy '{strategy_class_name}'. This strategy does not exist "
32+
"or contains Python code errors."
33+
)
3734

0 commit comments

Comments
 (0)