Skip to content

Commit a1ea41e

Browse files
author
DUYN
committed
Finish merge with old setup
1 parent 4b24ce8 commit a1ea41e

File tree

11 files changed

+146
-49
lines changed

11 files changed

+146
-49
lines changed

investing_bot_framework/core/configuration/setup/default_template_creators.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ def create(self) -> None:
2525
for root, dirs, files in os.walk(template_dir):
2626

2727
# Get the last part of the path
28-
# /home/test_user/bots/investing-investing_bot_framework/investing_bot_framework/templates/configuration -> configuration
2928
# This is used as the basis for the copying
3029
path_rest = root[len(template_dir) + 1:]
3130

investing_bot_framework/core/context/bot_context.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
1-
from typing import Type, List
1+
from typing import Type
22

33
from investing_bot_framework.core.configuration import settings
44
from investing_bot_framework.core.exceptions import OperationalException
55
from investing_bot_framework.core.utils import Singleton
66
from investing_bot_framework.core.context.states import BotState
7-
from investing_bot_framework.core.data.data_providers import DataProvider
87

98

109
class BotContext(metaclass=Singleton):
1110
"""
12-
The BotContext defines the current state of the running investing_bot_framework. It also maintains a reference to an instance of a
11+
The BotContext defines the current state of the running bot. It also maintains a reference to an instance of a
1312
BotState subclass, which represents the current state of the BotContext.
1413
"""
1514

1615
# A reference to the current state of the Bot Context.
1716
_state: BotState = None
1817

19-
# List of data providers
20-
_data_providers: List[DataProvider] = []
21-
2218
# Settings reference
2319
settings = settings
2420

@@ -32,7 +28,7 @@ def initialize(self, bot_state: Type[BotState]) -> None:
3228

3329
def transition_to(self, bot_state: Type[BotState]) -> None:
3430
"""
35-
The Context allows changing the State object at runtime.
31+
Function to change the running BotState at runtime.
3632
"""
3733

3834
self._state = bot_state(context=self)
@@ -44,8 +40,8 @@ def _check_state(self) -> None:
4440

4541
if self._state is None:
4642
raise OperationalException(
47-
"Bot context doesn't have a state, Make sure that you set the state of investing_bot_framework either by initializing it "
48-
"or making sure that you transition to a new valid state."
43+
"Bot context doesn't have a state, Make sure that you set the state of bot either "
44+
"by initializing it or making sure that you transition to a new valid state."
4945
)
5046

5147
def run(self) -> None:

investing_bot_framework/core/context/state_validator.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
from abc import abstractmethod, ABC
22

3+
34
class StateValidator(ABC):
5+
"""
6+
Class StateValidator: validates the given state. Use this class to change the transition process of a state.
7+
Use it as a hook to decide if a state must transition, e.g. only change to a strategy state if all the
8+
provided data meets a certain threshold.
9+
"""
410

511
@abstractmethod
612
def validate_state(self, state) -> bool:

investing_bot_framework/core/context/states/bot_state.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from abc import ABC, abstractmethod
22
from typing import Type, List
3-
from collections import Iterable
43

54
from investing_bot_framework.core.context.state_validator import StateValidator
65

@@ -11,7 +10,10 @@ class BotState(ABC):
1110
mode for the investing_bot_framework.
1211
"""
1312

13+
# Transition state for the next BotState
1414
transition_state_class = None
15+
16+
# Validator for the current state
1517
state_validators = None
1618

1719
def __init__(self, context, state_validator: StateValidator = None) -> None:
@@ -62,10 +64,6 @@ def validate_state(self) -> bool:
6264

6365
return True
6466

65-
@property
66-
def state_validator(self) -> StateValidator:
67-
return self._state_validator
68-
6967
def transition(self) -> None:
7068
bot_state_class = self.get_transition_state_class()
7169
self.context.transition_to(bot_state_class)

investing_bot_framework/core/context/states/data_state.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,21 @@
88
from investing_bot_framework.core.data.data_providers import DataProvider, DataProviderExecutor
99
from investing_bot_framework.core.resolvers import ClassCollector
1010
from investing_bot_framework.core.exceptions import OperationalException
11-
from investing_bot_framework.core.configuration.config_constants import DEFAULT_MAX_WORKERS, SETTINGS_DATA_PROVIDER_REGISTERED_APPS, \
12-
SETTINGS_MAX_WORKERS
11+
from investing_bot_framework.core.configuration.config_constants import DEFAULT_MAX_WORKERS, SETTINGS_MAX_WORKERS, \
12+
SETTINGS_DATA_PROVIDER_REGISTERED_APPS
1313

1414

1515
class DataState(BotState, Observer):
1616
"""
17-
Represent the data state of a investing_bot_framework. This state will load all the defined data providers and will
17+
Represent the data state of a bot. This state will load all the defined data providers and will
1818
run them.
19+
20+
If you want to validate the state before transitioning, provide a state validator.
1921
"""
2022

2123
from investing_bot_framework.core.context.states.strategy_state import StrategyState
2224
transition_state_class = StrategyState
25+
2326
data_providers: List[DataProvider] = []
2427

2528
_data_provider_executor: DataProviderExecutor

investing_bot_framework/core/context/states/setup_state.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from pydoc import locate
2+
13
from investing_bot_framework.core.exceptions import ImproperlyConfigured
24
from investing_bot_framework.core.context.states import BotState
35
from investing_bot_framework.core.resolvers import ClassCollector
@@ -49,15 +51,17 @@ def _validate_data_providers(self) -> None:
4951

5052
# Try to load all the specified data provider modules
5153
for data_provider_app in data_provider_apps_config:
52-
class_collector = ClassCollector(package_path=data_provider_app, class_type=DataProvider)
53-
54-
if len(class_collector.instances) == 0:
55-
raise ImproperlyConfigured(
56-
"Could not load data providers from package {}, are they implemented correctly?. Please make sure "
57-
"that you defined the right package or module. In the case of referring to your own defined data "
58-
"providers make sure that they can be imported. If you have difficulties configuring data "
59-
"providers, consider looking at the documentation.".format(data_provider_app)
60-
)
54+
instance = locate(data_provider_app)
55+
print(instance)
56+
# class_collector = ClassCollector(package_path=data_provider_app, class_type=DataProvider)
57+
#
58+
# if len(class_collector.instances) == 0:
59+
# raise ImproperlyConfigured(
60+
# "Could not load data providers from package {}, are they implemented correctly?. Please make sure "
61+
# "that you defined the right package or module. In the case of referring to your own defined data "
62+
# "providers make sure that they can be imported. If you have difficulties configuring data "
63+
# "providers, consider looking at the documentation.".format(data_provider_app)
64+
# )
6165

6266
def stop(self) -> None:
6367
# Stopping all services

investing_bot_framework/core/data/data_providers/data_provider.py

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,15 @@ def __init__(self, message: str) -> None:
1818
def __str__(self) -> str:
1919
return self.message
2020

21-
def __json__(self):
22-
return {
23-
'msg': self.message
24-
}
25-
2621

2722
class DataProvider(Worker):
2823
"""
2924
Class DataProvider: An entity which responsibility is to provide data from an external data source. Where a data
30-
source is defined as any third party service that provides data, e.g cloud storage, REST API, or website
31-
"""
25+
source is defined as any third party service that provides data, e.g cloud storage, REST API, or website.
3226
33-
id = None
27+
A data provider must always be run with the start function from it´s super class. Otherwise depend observers will
28+
not be updated.
29+
"""
3430

3531
def __init__(self):
3632
super(DataProvider, self).__init__()
@@ -54,14 +50,6 @@ def data(self) -> Any:
5450
def clean_up(self) -> None:
5551
self._data = None
5652

57-
def get_id(self) -> str:
58-
assert getattr(self, 'id', None) is not None, (
59-
"{} should either include a id attribute, or override the "
60-
"`get_id()`, method.".format(self.__class__.__name__)
61-
)
62-
63-
return getattr(self, 'id')
64-
6553

6654
class RestApiDataProvider(RestApiClientMixin, DataProvider, ABC):
6755
pass

investing_bot_framework/core/data/data_providers/data_provider_executor.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88

99
class DataProviderExecutor(Executor):
10+
"""
11+
Class DataProviderExecutor: is an executor for DataProvider instances.
12+
"""
1013

1114
def __init__(self, data_providers: List[DataProvider] = None, max_workers: int = DEFAULT_MAX_WORKERS):
1215
super(DataProviderExecutor, self).__init__(max_workers=max_workers)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
from datetime import datetime
2+
from collections import namedtuple
3+
from typing import Dict, List
4+
5+
from investing_bot_framework.core.utils import TimeUnit
6+
from investing_bot_framework.core.exceptions import OperationalException
7+
8+
ExecutionTask = namedtuple('ExecutionTask', 'time_unit interval last_run')
9+
10+
11+
class ExecutionScheduler:
12+
"""
13+
Class ExecutionScheduler: This is a lazy scheduler. It only runs it's scheduling algorithm when it is asked to.
14+
It will then evaluate all the time units and intervals and decide which executions it needs to return.
15+
16+
At the initial run, it will schedule all the executions.
17+
"""
18+
19+
def __init__(self):
20+
self._planning: Dict[str, ExecutionTask] = {}
21+
22+
def add_execution_task(self, execution_id: str, time_unit: TimeUnit, interval: int = None) -> None:
23+
"""
24+
Function that will add an appointment to the scheduler
25+
"""
26+
27+
if execution_id not in self._planning:
28+
29+
if time_unit is not TimeUnit.ALWAYS:
30+
31+
if interval is None:
32+
raise OperationalException("Appoint must set an interval with the corresponding time unit")
33+
elif interval < 1:
34+
raise OperationalException("Interval for task time unit is smaller then 1")
35+
36+
self._planning[execution_id] = ExecutionTask(time_unit=time_unit, interval=interval, last_run=None)
37+
38+
else:
39+
raise OperationalException("Can't add execution task, execution id is already taken")
40+
41+
def schedule_executions(self) -> List[str]:
42+
"""
43+
Function that will return all appointments that have hit their time threshold
44+
"""
45+
46+
appointments: List[str] = []
47+
48+
for appointment_id in self._planning:
49+
50+
# Run at the first call
51+
if self._planning[appointment_id].last_run is None:
52+
appointments.append(appointment_id)
53+
54+
# Executions that always need to be scheduled
55+
elif self._planning[appointment_id].time_unit is TimeUnit.ALWAYS:
56+
appointments.append(appointment_id)
57+
58+
else:
59+
60+
# Get the current time
61+
now = datetime.now()
62+
63+
# More seconds passed then the given interval
64+
if self._planning[appointment_id].time_unit is TimeUnit.SECOND:
65+
last_run = self._planning[appointment_id].last_run
66+
elapsed_time = now - last_run
67+
seconds = elapsed_time.total_seconds()
68+
69+
if seconds >= self._planning[appointment_id].interval:
70+
appointments.append(appointment_id)
71+
72+
# More minutes passed then the given interval
73+
if self._planning[appointment_id].time_unit is TimeUnit.MINUTE:
74+
last_run = self._planning[appointment_id].last_run
75+
elapsed_time = now - last_run
76+
minutes = divmod(elapsed_time.total_seconds(), 60)
77+
78+
if minutes[0] >= self._planning[appointment_id].interval:
79+
appointments.append(appointment_id)
80+
81+
# More hours run then the given interval
82+
elif self._planning[appointment_id].time_unit is TimeUnit.HOUR:
83+
last_run = self._planning[appointment_id].last_run
84+
elapsed_time = now - last_run
85+
hours = divmod(elapsed_time.total_seconds(), 3600)
86+
87+
if hours[0] >= self._planning[appointment_id].interval:
88+
appointments.append(appointment_id)
89+
90+
for appointment in appointments:
91+
self._planning[appointment] = ExecutionTask(
92+
self._planning[appointment].time_unit,
93+
self._planning[appointment].interval,
94+
datetime.now()
95+
)
96+
97+
return appointments

investing_bot_framework/core/resolvers/class_collector.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ def _find_classes(self, package_path: str, module_name: str = None) -> None:
4646
cls_members = inspect.getmembers(module, inspect.isclass)
4747

4848
for (name, class_definition) in cls_members:
49-
print(name)
5049

5150
# Only add classes that are a sub class of defined Plugin class, but NOT Plugin itself
5251
if issubclass(class_definition, self.class_type) \

0 commit comments

Comments
 (0)