Skip to content
This repository was archived by the owner on Dec 16, 2025. It is now read-only.

Commit 3a7f91d

Browse files
committed
Integrate presentation of the current state into ModuleHandler.
This partly revers commit 3e9d54d. This reverts commit e15ce26. This reverts commit bff688c. Signed-off-by: Tobias Wolf <[email protected]>
1 parent 634d4d3 commit 3a7f91d

File tree

5 files changed

+128
-413
lines changed

5 files changed

+128
-413
lines changed

src/rookify/__main__.py

Lines changed: 31 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,15 @@
11
# -*- coding: utf-8 -*-
22

3-
import json
43
from pickle import Unpickler
54
import sys
65
import argparse
76
from argparse import ArgumentParser
8-
from typing import Any, Dict, Optional
7+
from typing import Any, Dict
98
from .modules import load_modules
109
from .modules.machine import Machine
10+
from .modules.module import ModuleHandler
1111
from .logger import configure_logging, get_logger
1212
from .yaml import load_config
13-
from structlog.typing import BindableLogger
14-
from pathlib import Path
15-
16-
17-
def parse_args(args: list[str]) -> argparse.Namespace:
18-
# Putting args-parser in seperate function to make this testable
19-
arg_parser = ArgumentParser("Rookify")
20-
21-
# --dry-run option
22-
arg_parser.add_argument("--dry-run", action="store_true", dest="dry_run_mode")
23-
24-
# --list-modules option
25-
arg_parser.add_argument(
26-
"--list-modules", action="store_true", help="List all modules"
27-
)
28-
29-
# Custom ReadAction to set 'all' if nothing is specified for --read-pickle
30-
class ReadAction(argparse.Action):
31-
def __call__(
32-
self,
33-
parser: ArgumentParser,
34-
namespace: argparse.Namespace,
35-
values: Optional[Any],
36-
option_string: Optional[str] = None,
37-
) -> None:
38-
setattr(namespace, self.dest, values if values is not None else "all")
39-
40-
# Custom ShowProgressAction to set 'all' if nothing is specified for --show-progress
41-
class ShowProgressAction(argparse.Action):
42-
def __call__(
43-
self,
44-
parser: ArgumentParser,
45-
namespace: argparse.Namespace,
46-
values: Optional[Any],
47-
option_string: Optional[str] = None,
48-
) -> None:
49-
setattr(namespace, self.dest, values if values is not None else "all")
50-
51-
arg_parser.add_argument(
52-
"--read-pickle",
53-
nargs="?",
54-
action=ReadAction,
55-
dest="read_pickle",
56-
metavar="<section>",
57-
help="Show the content of the pickle file. Default argument is 'all', you can also specify a section you want to look at.",
58-
required=False,
59-
)
60-
61-
arg_parser.add_argument(
62-
"--show-progress",
63-
nargs="?",
64-
action=ShowProgressAction,
65-
dest="show_progress",
66-
metavar="<module>",
67-
help="Show progress of the modules. Default argument is 'all', you can also specify a module you want to get the progress status from.",
68-
required=False,
69-
)
70-
return arg_parser.parse_args(args)
7113

7214

7315
def load_pickler(pickle_file_name: str) -> Any:
@@ -77,75 +19,26 @@ def load_pickler(pickle_file_name: str) -> Any:
7719
return states_data
7820

7921

80-
def get_all_modules() -> list[str]:
81-
base_path = Path(__file__).resolve().parent
82-
module_path = Path(base_path) / "modules"
83-
module_names = []
84-
for item in module_path.iterdir():
85-
if item.is_dir() and item.name != "__pycache__":
86-
module_names.append(item.name)
87-
return module_names
88-
89-
90-
def sort_pickle_file(unsorted_states_data: Dict[str, Any]) -> Dict[str, Any]:
91-
# sort the pickle-file alfabetically
92-
iterable_dict = iter(unsorted_states_data)
93-
first_key = next(iterable_dict)
94-
data_values = unsorted_states_data[first_key]["data"]
95-
sorted_data_by_keys = {k: data_values[k] for k in sorted(data_values)}
96-
return sorted_data_by_keys
97-
98-
99-
def read_pickle_file(
100-
args: argparse.Namespace, pickle_file_name: str, log: BindableLogger
101-
) -> None:
102-
states_data = load_pickler(pickle_file_name)
103-
sorted_states_data = sort_pickle_file(states_data)
22+
def parse_args(args: list[str]) -> argparse.Namespace:
23+
# Putting args-parser in seperate function to make this testable
24+
arg_parser = ArgumentParser("Rookify")
10425

105-
# Check if a specific section should be listed
106-
if args.read_pickle != "all":
107-
if args.read_pickle not in sorted_states_data.keys():
108-
log.error(f"The section {args.read_pickle} does not exist")
109-
else:
110-
sorted_states_data = sorted_states_data[args.read_pickle]
26+
# --dry-run option
27+
arg_parser.add_argument("--dry-run", action="store_true", dest="dry_run_mode")
11128

112-
log.info(
113-
'Current state as retrieved from pickle-file: \n "{0}": {1}'.format(
114-
args.read_pickle, json.dumps(sorted_states_data, indent=4)
115-
)
29+
arg_parser.add_argument(
30+
"--show-states",
31+
action="store_true",
32+
dest="show_states",
33+
help="Show states of the modules.",
11634
)
11735

118-
119-
def show_progress_from_state(
120-
args: argparse.Namespace, pickle_file_name: str, log: BindableLogger
121-
) -> bool:
122-
# states_data = load_pickler(pickle_file_name)
123-
modules = get_all_modules()
124-
125-
# Check if a specific module should be targeted
126-
if args.show_progress != "all":
127-
module = args.show_progress
128-
if args.show_progress not in modules:
129-
log.error(f"The module {module} does not exist")
130-
return False
131-
log.info("Show progress of the {0} module".format(args.show_progress))
132-
return True
133-
else:
134-
log.info("Show progress of {0} modules".format(args.show_progress))
135-
return True
36+
return arg_parser.parse_args(args)
13637

13738

13839
def main() -> None:
13940
args = parse_args(sys.argv[1:])
14041

141-
# Handle --list-modules
142-
if args.list_modules:
143-
modules = get_all_modules()
144-
print("Available modules:\n")
145-
for module in modules:
146-
print(f"- {module}")
147-
return
148-
14942
# Load configuration file
15043
try:
15144
config: Dict[str, Any] = load_config("config.yaml")
@@ -154,49 +47,29 @@ def main() -> None:
15447

15548
# Configure logging
15649
try:
157-
configure_logging(config["logging"])
50+
if args.show_states is True:
51+
configure_logging(
52+
{"level": "ERROR", "format": {"renderer": "console", "time": "iso"}}
53+
)
54+
else:
55+
configure_logging(config["logging"])
15856
except Exception as e:
15957
raise SystemExit(f"Error configuring logging: {e}")
58+
16059
# Get Logger
16160
log = get_logger()
16261

163-
# Get Pickle File if configured in config.yaml
164-
pickle_file_name = config["general"].get("machine_pickle_file")
165-
if pickle_file_name is None:
166-
log.info("No pickle file was set in the configuration.")
167-
else:
168-
log.info(f"Pickle file set: {pickle_file_name}")
62+
log.info("Executing Rookify ...")
16963

170-
# Get Pickle File if configured in config.yaml
171-
pickle_file_name = config["general"].get("machine_pickle_file")
172-
if pickle_file_name is None:
173-
log.info("No pickle file was set in the configuration.")
174-
else:
175-
log.info(f"Pickle file set: {pickle_file_name}")
176-
177-
# If read_pickle is run and there is a picklefile, show the picklefiles contents.
178-
# NOTE: preflight mode (--dry-run) has no effect here, because no module actions are required.
179-
if args.read_pickle is not None and pickle_file_name is not None:
180-
read_pickle_file(args, pickle_file_name, log)
181-
return
182-
elif args.read_pickle is not None and pickle_file_name is None:
183-
log.info(
184-
"No pickle file configured to read from. Check if the pickle file exists and is configured in config.yaml"
185-
)
186-
return
64+
machine = Machine(config["general"].get("machine_pickle_file"))
65+
66+
load_modules(machine, config)
18767

68+
if args.show_states is True:
69+
ModuleHandler.show_states(machine, config)
18870
else:
189-
machine = Machine(config["general"].get("machine_pickle_file"))
190-
191-
if args.show_progress is not None:
192-
if show_progress_from_state(args, pickle_file_name, log) is True:
193-
load_modules(machine, config, show_progress=True)
194-
# NOTE: this is always run in preflight-mode (migration should not be executed)
195-
machine.execute(dry_run_mode=True)
196-
return
197-
else:
198-
return
199-
else:
200-
load_modules(machine, config)
201-
log.debug("Executing Rookify")
202-
machine.execute(dry_run_mode=args.dry_run_mode)
71+
machine.execute(dry_run_mode=args.dry_run_mode)
72+
73+
74+
if __name__ == "__main__":
75+
main()

src/rookify/modules/__init__.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# -*- coding: utf-8 -*-
22

33
import importlib
4-
from typing import Any, Dict, Optional
4+
from typing import Any, Dict, List
55
from ..logger import get_logger
66
from .machine import Machine
77

@@ -22,11 +22,18 @@ def __init__(self, module_name: str, message: str):
2222
self.message = message
2323

2424

25+
_modules_loaded: List[Any] = []
26+
27+
28+
def get_modules() -> List[Any]:
29+
global _modules_loaded
30+
return _modules_loaded.copy()
31+
32+
2533
def _load_module(
2634
machine: Machine,
2735
config: Dict[str, Any],
2836
module_name: str,
29-
show_progress: Optional[bool] = False,
3037
) -> None:
3138
"""
3239
Dynamically loads a module from the 'rookify.modules' package.
@@ -35,8 +42,10 @@ def _load_module(
3542
:return: returns tuple of preflight_modules, modules
3643
"""
3744

45+
global _modules_loaded
46+
3847
module = importlib.import_module("rookify.modules.{0}".format(module_name))
39-
additional_modules = []
48+
additional_module_names = []
4049

4150
if not hasattr(module, "ModuleHandler") or not callable(
4251
getattr(module.ModuleHandler, "register_states")
@@ -45,17 +54,17 @@ def _load_module(
4554

4655
if hasattr(module.ModuleHandler, "REQUIRES"):
4756
assert isinstance(module.ModuleHandler.REQUIRES, list)
48-
additional_modules = module.ModuleHandler.REQUIRES
57+
additional_module_names = module.ModuleHandler.REQUIRES
4958

50-
for module_name in additional_modules:
51-
_load_module(machine, config, module_name, show_progress)
59+
for additional_module_name in additional_module_names:
60+
_load_module(machine, config, additional_module_name)
5261

53-
module.ModuleHandler.register_states(machine, config, show_progress)
62+
if module not in _modules_loaded:
63+
_modules_loaded.append(module)
64+
module.ModuleHandler.register_states(machine, config)
5465

5566

56-
def load_modules(
57-
machine: Machine, config: Dict[str, Any], show_progress: Optional[bool] = False
58-
) -> None:
67+
def load_modules(machine: Machine, config: Dict[str, Any]) -> None:
5968
"""
6069
Dynamically loads modules from the 'modules' package.
6170
@@ -68,7 +77,7 @@ def load_modules(
6877
for entry in importlib.resources.files("rookify.modules").iterdir():
6978
if entry.is_dir() and entry.name in config["migration_modules"]:
7079
migration_modules.remove(entry.name)
71-
_load_module(machine, config, entry.name, show_progress)
80+
_load_module(machine, config, entry.name)
7281

7382
if len(migration_modules) > 0 or len(config["migration_modules"]) < 1:
7483
logger = get_logger()

src/rookify/modules/machine.py

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,14 @@ def add_execution_state(self, name: str, **kwargs: Any) -> None:
2626
def add_preflight_state(self, name: str, **kwargs: Any) -> None:
2727
self._preflight_states.append(self.__class__.state_cls(name, **kwargs))
2828

29-
def execute(
30-
self, dry_run_mode: bool = False, show_progress: Optional[bool] = False
31-
) -> None:
29+
def execute(self, dry_run_mode: bool = False) -> None:
3230
states = self._preflight_states
3331
if not dry_run_mode:
3432
states = states + self._execution_states
3533

36-
logger = get_logger()
34+
self._register_states(states)
3735

38-
for state in states:
39-
if state.name not in self.states:
40-
logger.debug("Registering state '{0}'".format(state.name))
41-
self.add_state(state)
42-
43-
self.add_state("migrated")
44-
self.add_ordered_transitions(loop=False)
36+
logger = get_logger()
4537

4638
if self._machine_pickle_file is None:
4739
logger.info("Execution started without machine pickle file")
@@ -54,12 +46,8 @@ def execute(
5446
def _execute(self, pickle_file: Optional[IO[Any]] = None) -> None:
5547
states_data = {}
5648

57-
# Read pickle file if it exists, to continue from the stored state
58-
if pickle_file is not None and pickle_file.tell() > 0:
59-
pickle_file.seek(0)
60-
61-
states_data = Unpickler(pickle_file).load()
62-
self._restore_state_data(states_data)
49+
if pickle_file is not None:
50+
self._restore_state_data(pickle_file)
6351

6452
try:
6553
while True:
@@ -110,7 +98,39 @@ def get_preflight_state_data(
11098
) -> Any:
11199
return getattr(self.get_preflight_state(name), tag, default_value)
112100

113-
def _restore_state_data(self, data: Dict[str, Any]) -> None:
101+
def register_states(self) -> None:
102+
self._register_states(self._preflight_states + self._execution_states)
103+
104+
if self._machine_pickle_file is not None:
105+
with open(self._machine_pickle_file, "rb") as file:
106+
file.seek(1)
107+
self._restore_state_data(file)
108+
109+
def _register_states(self, states: List[State]) -> None:
110+
logger = get_logger()
111+
112+
for state in states:
113+
if state.name not in self.states:
114+
logger.debug("Registering state '{0}'".format(state.name))
115+
self.add_state(state)
116+
117+
self.add_state("migrated")
118+
self.add_ordered_transitions(loop=False)
119+
120+
"""
121+
Read pickle file if it exists, to continue from the stored state. It is
122+
required that the position of the pointer of the pickle file given is not
123+
at the start.
124+
"""
125+
126+
def _restore_state_data(self, pickle_file: IO[Any]) -> None:
127+
if pickle_file.tell() == 0:
128+
return None
129+
130+
pickle_file.seek(0)
131+
132+
data = Unpickler(pickle_file).load()
133+
114134
for state_name in data:
115135
try:
116136
state = self.get_state(state_name)

0 commit comments

Comments
 (0)