Skip to content

Commit d0f4647

Browse files
author
DUYN
committed
Refactor BotContext
Now the BotContext decides which state to run
1 parent 145c486 commit d0f4647

File tree

6 files changed

+102
-84
lines changed

6 files changed

+102
-84
lines changed

investing_bot_framework/core/context/bot_context.py

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,37 +33,51 @@ def transition_to(self, bot_state: Type[BotState]) -> None:
3333

3434
self._state = bot_state(context=self)
3535

36-
def _check_state(self) -> None:
36+
def _check_state(self, raise_exception: bool = False) -> bool:
3737
"""
3838
Function that wil check if the state is set
3939
"""
4040

4141
if self._state is None:
42-
raise OperationalException(
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."
45-
)
4642

47-
def run(self) -> None:
43+
if raise_exception:
44+
raise OperationalException(
45+
"Bot context doesn't have a state, Make sure that you set the state of bot either "
46+
"by initializing it or making sure that you transition to a new valid state."
47+
)
48+
else:
49+
return False
50+
51+
return True
52+
53+
def start(self) -> None:
4854
"""
4955
Run the current state of the investing_bot_framework
5056
"""
5157

52-
self._check_state()
58+
self._check_state(raise_exception=True)
59+
self._run_state()
60+
61+
while self._check_state():
62+
self._run_state()
63+
64+
def _run_state(self) -> None:
5365
self._state.start()
66+
transition_state = self._state.get_transition_state_class()
67+
self.transition_to(transition_state)
5468

5569
def stop(self) -> None:
5670
"""
5771
Stop the current state of the investing_bot_framework
5872
"""
5973

60-
self._check_state()
74+
self._check_state(raise_exception=True)
6175
self._state.stop()
6276

6377
def reconfigure(self) -> None:
6478
"""
6579
Reconfigure the current state of the investing_bot_framework
6680
"""
6781

68-
self._check_state()
82+
self._check_state(raise_exception=True)
6983
self._state.reconfigure()

investing_bot_framework/core/context/states/bot_state.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,10 @@ def start(self):
2525
while True:
2626
self.run()
2727

28-
# Will run unit state has a positive validation and can transition to next state
28+
# Will run state again if validation is negative
2929
if self.validate_state():
3030
break
3131

32-
self.transition()
33-
3432
@abstractmethod
3533
def run(self) -> None:
3634
pass
@@ -64,12 +62,7 @@ def validate_state(self) -> bool:
6462

6563
return True
6664

67-
def transition(self) -> None:
68-
bot_state_class = self.get_transition_state_class()
69-
self.context.transition_to(bot_state_class)
70-
self.context.run()
71-
72-
def get_transition_state_class(self) -> Type:
65+
def get_transition_state_class(self):
7366

7467
assert getattr(self, 'transition_state_class', None) is not None, (
7568
"{} should either include a transition_state_class attribute, or override the "

investing_bot_framework/core/context/states/data_state.py

Lines changed: 75 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,48 @@
22
from typing import List
33
from wrapt import synchronized
44

5+
from investing_bot_framework.core.exceptions import ImproperlyConfigured, OperationalException
56
from investing_bot_framework.core.events import Observer
67
from investing_bot_framework.core.context.bot_context import BotContext
78
from investing_bot_framework.core.context.states import BotState
9+
from investing_bot_framework.core.executors import ExecutionScheduler
810
from investing_bot_framework.core.data.data_providers import DataProvider, DataProviderExecutor
9-
from investing_bot_framework.core.resolvers import ClassCollector
10-
from investing_bot_framework.core.exceptions import OperationalException
11+
from investing_bot_framework.core.utils import Singleton, TimeUnit
1112
from investing_bot_framework.core.configuration.config_constants import DEFAULT_MAX_WORKERS, SETTINGS_MAX_WORKERS, \
1213
SETTINGS_DATA_PROVIDER_REGISTERED_APPS
1314

1415

16+
def import_class(cl):
17+
d = cl.rfind(".")
18+
classname = cl[d+1:len(cl)]
19+
m = __import__(cl[0:d], globals(), locals(), [classname])
20+
return getattr(m, classname)
21+
22+
23+
class DataProviderScheduler(ExecutionScheduler, metaclass=Singleton):
24+
"""
25+
Data Provider scheduler that will function as a scheduler to make sure it keeps it state across multiple states,
26+
it is defined as a Singleton.
27+
"""
28+
29+
def __init__(self):
30+
self._configured = False
31+
32+
super(DataProviderScheduler, self).__init__()
33+
34+
def configure(self, data_providers: List[DataProvider]) -> None:
35+
self._planning = {}
36+
37+
for data_provider in data_providers:
38+
self.add_execution_task(execution_id=data_provider.get_id(), time_unit=TimeUnit.ALWAYS)
39+
40+
self._configured = True
41+
42+
@property
43+
def configured(self) -> bool:
44+
return self._configured
45+
46+
1547
class DataState(BotState, Observer):
1648
"""
1749
Represent the data state of a bot. This state will load all the defined data providers and will
@@ -24,7 +56,6 @@ class DataState(BotState, Observer):
2456
transition_state_class = StrategyState
2557

2658
data_providers: List[DataProvider] = []
27-
2859
_data_provider_executor: DataProviderExecutor
2960

3061
def __init__(self, context: BotContext) -> None:
@@ -40,17 +71,17 @@ def _initialize(self) -> None:
4071
"""
4172
self._clean_up()
4273

43-
for data_provider_app_conf in self.context.settings[SETTINGS_DATA_PROVIDER_REGISTERED_APPS]:
44-
class_collector = ClassCollector(package_path=data_provider_app_conf, class_type=DataProvider)
45-
self.data_providers += class_collector.instances
74+
for data_provider_app_class in self.context.settings[SETTINGS_DATA_PROVIDER_REGISTERED_APPS]:
4675

47-
self._data_provider_executor = DataProviderExecutor(
48-
data_providers=self.data_providers,
49-
max_workers=self.context.settings.get(SETTINGS_MAX_WORKERS, DEFAULT_MAX_WORKERS)
50-
)
76+
instance = import_class(data_provider_app_class)()
77+
78+
if not isinstance(instance, DataProvider):
79+
raise ImproperlyConfigured(
80+
"Specified data provider {} is not a instance of DataProvider".format(data_provider_app_class)
81+
)
82+
83+
self.data_providers.append(instance)
5184

52-
# Adding self as an observer
53-
self._data_provider_executor.add_observer(self)
5485
self._configured = True
5586

5687
def _clean_up(self) -> None:
@@ -61,25 +92,52 @@ def _clean_up(self) -> None:
6192
self._data_provider_executor = None
6293
self.data_providers = []
6394

95+
def _schedule_data_providers(self) -> List[DataProvider]:
96+
data_provider_scheduler = DataProviderScheduler()
97+
98+
if not data_provider_scheduler.configured:
99+
data_provider_scheduler.configure(self.data_providers)
100+
101+
planning = data_provider_scheduler.schedule_executions()
102+
planned_data_providers = []
103+
104+
for data_provider in self.data_providers:
105+
106+
if data_provider.get_id() in planning:
107+
planned_data_providers.append(data_provider)
108+
109+
return planned_data_providers
110+
111+
def _start_data_providers(self, data_providers: List[DataProvider]) -> None:
112+
113+
self._data_provider_executor = DataProviderExecutor(
114+
data_providers=data_providers,
115+
max_workers=self.context.settings.get(SETTINGS_MAX_WORKERS, DEFAULT_MAX_WORKERS)
116+
)
117+
118+
self._data_provider_executor.add_observer(self)
119+
self._data_provider_executor.start()
120+
64121
def run(self) -> None:
65122

66123
if self._configured:
67-
# Start the data providers
68-
self._data_provider_executor.start()
124+
# Schedule the data providers
125+
planned_data_providers = self._schedule_data_providers()
126+
127+
# Execute all the data providers
128+
self._start_data_providers(planned_data_providers)
69129

70130
# Sleep till updated
71131
while not self._updated:
72132
time.sleep(1)
73133

74134
# Collect all data from the data providers
75135
for data_provider in self._data_provider_executor.registered_data_providers:
76-
print(data_provider.get_id())
136+
print("Data provider: {} finished running".format(data_provider.get_id()))
77137

78138
else:
79139
raise OperationalException("Data state started without any configuration")
80140

81-
print('stopped running')
82-
83141
def stop(self) -> None:
84142
"""
85143
Stop all data providers
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
from investing_bot_framework.core.executors.executor import Executor
2+
from investing_bot_framework.core.executors.execution_scheduler import ExecutionScheduler

investing_bot_framework/core/management/commands/runbot.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def handle(self, *args, **options) -> Any:
2828
# Create an investing_bot_framework context of the investing_bot_framework and run it
2929
context = BotContext()
3030
context.initialize(SetupState)
31-
context.run()
31+
context.start()
3232

3333
@staticmethod
3434
def validate_cycles(self, cycles: int) -> None:

investing_bot_framework/main.py

Lines changed: 0 additions & 48 deletions
This file was deleted.

0 commit comments

Comments
 (0)