From 301a9c87bee1e145141c0cd251730f8ef9192fd9 Mon Sep 17 00:00:00 2001 From: raj_mathe Date: Sat, 11 Jun 2022 14:00:45 +0200 Subject: [PATCH] master > master: code py - refactored code mit Endpunkten --- code/python/main.py | 61 +++---- .../src/algorithms/hirschberg/__init__.py | 1 + code/python/src/api.py | 41 +++++ code/python/src/core/calls.py | 149 ++++++++++++++++++ code/python/src/core/log.py | 103 ++++++++---- code/python/src/endpoints/__init__.py | 20 +++ .../src/endpoints/ep_algorithm_hirschberg.py | 42 +++++ .../src/endpoints/ep_algorithm_tarjan.py | 37 +++++ code/python/src/endpoints/ep_algorithm_tsp.py | 35 ++++ code/python/src/models/config/__init__.py | 19 +++ code/python/src/models/config/app.py | 31 ++++ code/python/src/models/config/commands.py | 45 ++++++ code/python/src/models/hirschberg/paths.py | 8 +- .../python/src/models/hirschberg/penalties.py | 6 +- code/python/src/setup/config.py | 25 ++- code/python/src/thirdparty/log.py | 18 +++ code/python/src/thirdparty/misc.py | 6 + code/python/src/thirdparty/run.py | 41 +++++ code/python/src/thirdparty/types.py | 2 + 19 files changed, 593 insertions(+), 97 deletions(-) create mode 100644 code/python/src/api.py create mode 100644 code/python/src/core/calls.py create mode 100644 code/python/src/endpoints/__init__.py create mode 100644 code/python/src/endpoints/ep_algorithm_hirschberg.py create mode 100644 code/python/src/endpoints/ep_algorithm_tarjan.py create mode 100644 code/python/src/endpoints/ep_algorithm_tsp.py create mode 100644 code/python/src/models/config/__init__.py create mode 100644 code/python/src/models/config/app.py create mode 100644 code/python/src/models/config/commands.py create mode 100644 code/python/src/thirdparty/log.py create mode 100644 code/python/src/thirdparty/run.py diff --git a/code/python/main.py b/code/python/main.py index f606716..cc084b1 100644 --- a/code/python/main.py +++ b/code/python/main.py @@ -11,51 +11,29 @@ import sys os.chdir(os.path.join(os.path.dirname(__file__))); sys.path.insert(0, os.getcwd()); -from src.thirdparty.maths import *; - -from models.generated.config import *; -from models.generated.commands import *; -from src.core.log import *; -from src.setup.config import *; -from src.models.graphs import *; -from src.algorithms.tarjan import *; -from src.algorithms.tsp import *; -from src.algorithms.hirschberg import *; - -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# GLOBAL CONSTANTS/VARIABLES -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -# +from src.models.config import *; +from src.core import log; +from src.setup import config; +from src import api; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # MAIN METHOD # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -def enter(): - for command in COMMANDS: - if isinstance(command, CommandTarjan): - tarjan_algorithm( - G = Graph( - nodes=command.nodes, - edges=list(map(tuple, command.edges)), - ), - verbose = OPTIONS.tarjan.verbose - ); - if isinstance(command, CommandTsp): - tsp_algorithm( - dist = np.asarray(command.dist, dtype=float), - optimise = min if command.optimise == EnumTSPOptimise.min else max, - verbose = OPTIONS.tsp.verbose, - ); - elif isinstance(command, CommandHirschberg): - hirschberg_algorithm( - X = command.word1, - Y = command.word2, - once = command.once, - verbose = OPTIONS.hirschberg.verbose, - show = OPTIONS.hirschberg.show, - ); +def enter(*args: str): + # set logging level: + log.configure_logging(config.LOG_LEVEL); + + # process inputs: + if len(args) == 0: + # Führe befehle in Assets aus: + for command in config.COMMANDS: + result = api.run_command(command); + log.log_result(result, debug=True); # ignored if log-level >> DEBUG + else: + # Führe CLI-Befehl aus: + result = api.run_command_from_json(args[0]); + log.log_result(result, debug=True); # ignored if log-level >> DEBUG return; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -63,4 +41,5 @@ def enter(): # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if __name__ == '__main__': - enter(); + sys.tracebacklimit = 0; + enter(*sys.argv[1:]); diff --git a/code/python/src/algorithms/hirschberg/__init__.py b/code/python/src/algorithms/hirschberg/__init__.py index 65f215b..997e89c 100644 --- a/code/python/src/algorithms/hirschberg/__init__.py +++ b/code/python/src/algorithms/hirschberg/__init__.py @@ -14,5 +14,6 @@ from src.algorithms.hirschberg.display import *; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ __all__ = [ + 'simple_algorithm', 'hirschberg_algorithm', ]; diff --git a/code/python/src/api.py b/code/python/src/api.py new file mode 100644 index 0000000..64d1920 --- /dev/null +++ b/code/python/src/api.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from src.thirdparty.code import *; + +from models.generated.commands import *; +from src.models.config import * +from src.endpoints import *; +from src.core.calls import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'run_command', + 'run_command_from_json', +]; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# API METHODS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +@run_safely(tag='api-from-json') +def run_command_from_json(command_json: str) -> Result[CallResult, CallError]: + command = command_from_json(command_json); + return run_command(command); + +@run_safely(tag='api-from-command') +def run_command(command: Command) -> Result[CallResult, CallError]: + if isinstance(command, CommandTarjan): + return endpoint_tarjan(command); + if isinstance(command, CommandTsp): + return endpoint_tsp(command); + elif isinstance(command, CommandHirschberg): + return endpoint_hirschberg(command); + raise Exception(f'No endpoint set for `{command.name.value}`-command type.'); diff --git a/code/python/src/core/calls.py b/code/python/src/core/calls.py new file mode 100644 index 0000000..8a17c46 --- /dev/null +++ b/code/python/src/core/calls.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from __future__ import annotations; + +from src.thirdparty.code import *; +from src.thirdparty.misc import *; +from src.thirdparty.run import *; +from src.thirdparty.types import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'CallResult', + 'CallError', + 'run_safely', +]; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# CONSTANTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# local usage only +T = TypeVar('T'); +V = TypeVar('V'); +E = TypeVar('E', bound=list); +ARGS = ParamSpec('ARGS'); + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# CLASS Trace for debugging only! +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +@dataclass +class CallResult(): # pragma: no cover + ''' + An auxiliary class which keeps track of the latest return value during calls. + ''' + action_taken: bool = field(default=False); + message: Optional[Any] = field(default=None); + +@dataclass +class CallErrorRaw(): # pragma: no cover + timestamp: str = field(); + tag: str = field(); + errors: List[str] = field(default_factory=list); + +class CallError(CallErrorRaw): + ''' + An auxiliary class which keeps track of potentially multiple errors during calls. + ''' + timestamp: str; + tag: str; + errors: List[str]; + + def __init__(self, tag: str, err: Any = Nothing()): + self.timestamp = str(datetime.now()); + self.tag = tag; + self.errors = []; + if isinstance(err, list): + for e in err: + self.append(e); + else: + self.append(err); + + def __len__(self) -> int: + return len(self.errors); + + def append(self, e: Any): + if isinstance(e, Nothing): + return; + if isinstance(e, Some): + e = e.unwrap(); + self.errors.append(str(e)); + + def extend(self, E: CallError): + self.errors.extend(E.errors); + + def __repr__(self) -> str: + return f'CallError(tag=\'{self.tag}\', errors={self.errors})'; + + def __str__(self) -> str: + return self.__repr__(); + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# DECORATOR - forces methods to run safely +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +def run_safely(tag: Union[str, None] = None, error_message: Union[str, None] = None): + ''' + Creates a decorator for an action to perform it safely. + + @inputs (parameters) + - `tag` - optional string to aid error tracking. + - `error_message` - optional string for an error message. + + ### Example usage ### + ```py + @run_safely(tag='recognise int', error_message='unrecognise string') + def action1(x: str) -> Result[int, CallError]: + return Ok(int(x)); + + assert action1('5') == Ok(5); + result = action1('not a number'); + assert isinstance(result, Err); + err = result.unwrap_err(); + assert isinstance(err, CallError); + assert err.tag == 'recognise int'; + assert err.errors == ['unrecognise string']; + + @run_safely('recognise int') + def action2(x: str) -> Result[int, CallError]: + return Ok(int(x)); + + assert action2('5') == Ok(5); + result = action2('not a number'); + assert isinstance(result, Err); + err = result.unwrap_err(); + assert isinstance(err, CallError); + assert err.tag == 'recognise int'; + assert len(err.errors) == 1; + ``` + NOTE: in the second example, err.errors is a list containing + the stringified Exception generated when calling `int('not a number')`. + ''' + def dec(action: Callable[ARGS, Result[V, CallError]]) -> Callable[ARGS, Result[V, CallError]]: + ''' + Wraps action with return type Result[..., CallError], + so that it is performed safely a promise, + catching any internal exceptions as an Err(...)-component of the Result. + ''' + @wraps(action) + def wrapped_action(*_, **__) -> Result[V, CallError]: + # NOTE: intercept Exceptions first, then flatten: + return Result.of(lambda: action(*_, **__)) \ + .or_else( + lambda err: Err(CallError( + tag = tag or action.__name__, + err = error_message or err + )) + ) \ + .and_then(lambda V: V); + return wrapped_action; + return dec; diff --git a/code/python/src/core/log.py b/code/python/src/core/log.py index 41912cf..94b1559 100644 --- a/code/python/src/core/log.py +++ b/code/python/src/core/log.py @@ -5,60 +5,93 @@ # IMPORTS # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# +from src.thirdparty.code import *; +from src.thirdparty.log import *; +from src.thirdparty.types import *; + +from src.core.calls import *; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # EXPORTS # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ __all__ = [ + 'LOG_LEVELS', + 'configure_logging', 'log_info', 'log_debug', 'log_warn', 'log_error', 'log_fatal', + 'log_result', + 'log_dev', ]; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# METHODS logging +# CONSTANTS # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -def log_info(*text: str): - ''' - Prints an info message - ''' - for line in text: - print("[\x1b[94;1mINFO\x1b[0m] {}".format(line)); +_LOGGING_DEBUG_FILE: str = 'logs/debug.log'; + +class LOG_LEVELS(Enum): # pragma: no cover + INFO = logging.INFO; + DEBUG = logging.DEBUG; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# METHODS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +def configure_logging(level: LOG_LEVELS): # pragma: no cover + logging.basicConfig( + format = '[\x1b[1m%(levelname)s\x1b[0m] %(message)s', + level = level.value, + ); return; -def log_debug(*text: str): - ''' - Prints a debug message - ''' - for line in text: - print("[\x1b[96;1mDEBUG\x1b[95;0m] {}".format(line)); - return; +def log_debug(*messages: Any): + logging.debug(*messages); -def log_warn(*text: str): - ''' - Prints a warning message - ''' - for line in text: - print("[\x1b[93;1mWARNING\x1b[0m] {}".format(line)); - return; +def log_info(*messages: Any): + logging.info(*messages); -def log_error(*text: str): - ''' - Prints an error message - ''' - for line in text: - print("[\x1b[91;1mERROR\x1b[0m] {}".format(line)); - return; +def log_warn(*messages: Any): + logging.warning(*messages); -def log_fatal(*text: str): - ''' - Prints a fatal error message + crashes - ''' - for line in text: - print("[\x1b[91;1mFATAL\x1b[0m] {}".format(line)); +def log_error(*messages: Any): + logging.error(*messages); + +def log_fatal(*messages: Any): + logging.fatal(*messages); exit(1); + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Special Methods +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +def log_result(result: Result[CallResult, CallError], debug: bool = False): + ''' + Logs safely encapsulated result of call as either debug/info or error. + + @inputs + - `result` - the result of the call. + - `debug = False` (default) - if the result is okay, will be logged as an INFO message. + - `debug = True` - if the result is okay, will be logged as a DEBUG message. + ''' + if isinstance(result, Ok): + value = result.unwrap(); + if debug: + log_debug(asdict(value)); + else: + log_info(asdict(value)); + else: + err = result.unwrap_err(); + log_error(asdict(err)); + return; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# DEBUG LOGGING FOR DEVELOPMENT +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +def log_dev(*messages: Any): # pragma: no cover + with open(_LOGGING_DEBUG_FILE, 'a') as fp: + print(*messages, file=fp); diff --git a/code/python/src/endpoints/__init__.py b/code/python/src/endpoints/__init__.py new file mode 100644 index 0000000..4c93022 --- /dev/null +++ b/code/python/src/endpoints/__init__.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from src.endpoints.ep_algorithm_hirschberg import *; +from src.endpoints.ep_algorithm_tarjan import *; +from src.endpoints.ep_algorithm_tsp import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'endpoint_hirschberg', + 'endpoint_tarjan', + 'endpoint_tsp', +]; diff --git a/code/python/src/endpoints/ep_algorithm_hirschberg.py b/code/python/src/endpoints/ep_algorithm_hirschberg.py new file mode 100644 index 0000000..c44b2d0 --- /dev/null +++ b/code/python/src/endpoints/ep_algorithm_hirschberg.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from src.thirdparty.code import *; + +from models.generated.commands import *; +from src.core.calls import *; +from src.setup import config; +from src.algorithms.hirschberg import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'endpoint_hirschberg', +]; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ENDPOINT +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +@run_safely() +def endpoint_hirschberg(command: CommandHirschberg) -> Result[CallResult, CallError]: + if command.once: + result = simple_algorithm( + X = command.word1, + Y = command.word2, + verbose = config.OPTIONS.hirschberg.verbose, + ); + else: + result = hirschberg_algorithm( + X = command.word1, + Y = command.word2, + verbose = config.OPTIONS.hirschberg.verbose, + show = config.OPTIONS.hirschberg.show, + ); + return Ok(CallResult(action_taken=True, message=result)); diff --git a/code/python/src/endpoints/ep_algorithm_tarjan.py b/code/python/src/endpoints/ep_algorithm_tarjan.py new file mode 100644 index 0000000..79cb440 --- /dev/null +++ b/code/python/src/endpoints/ep_algorithm_tarjan.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from src.thirdparty.code import *; + +from models.generated.commands import *; +from src.core.calls import *; +from src.setup import config; +from src.models.graphs import *; +from src.algorithms.tarjan import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'endpoint_tarjan', +]; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ENDPOINT +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +@run_safely() +def endpoint_tarjan(command: CommandTarjan) -> Result[CallResult, CallError]: + result = tarjan_algorithm( + G = Graph( + nodes=command.nodes, + edges=list(map(tuple, command.edges)), + ), + verbose = config.OPTIONS.tarjan.verbose + ); + return Ok(CallResult(action_taken=True, message=result)); diff --git a/code/python/src/endpoints/ep_algorithm_tsp.py b/code/python/src/endpoints/ep_algorithm_tsp.py new file mode 100644 index 0000000..7dd772a --- /dev/null +++ b/code/python/src/endpoints/ep_algorithm_tsp.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from src.thirdparty.code import *; +from src.thirdparty.maths import *; + +from models.generated.commands import *; +from src.core.calls import *; +from src.setup import config; +from src.algorithms.tsp import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'endpoint_tsp', +]; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ENDPOINT +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +@run_safely() +def endpoint_tsp(command: CommandTsp) -> Result[CallResult, CallError]: + result = tsp_algorithm( + dist = np.asarray(command.dist, dtype=float), + optimise = min if command.optimise == EnumTSPOptimise.min else max, + verbose = config.OPTIONS.tsp.verbose, + ); + return Ok(CallResult(action_taken=True, message=result)); diff --git a/code/python/src/models/config/__init__.py b/code/python/src/models/config/__init__.py new file mode 100644 index 0000000..812e448 --- /dev/null +++ b/code/python/src/models/config/__init__.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from src.models.config.app import *; +from src.models.config.commands import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'log_level', + 'command_from_json', + 'interpret_command', +]; diff --git a/code/python/src/models/config/app.py b/code/python/src/models/config/app.py new file mode 100644 index 0000000..2d64b01 --- /dev/null +++ b/code/python/src/models/config/app.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from models.generated.config import AppOptions; +from models.generated.config import EnumLogLevel; +from src.core.log import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'log_level', +]; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# METHODS log level +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +def log_level(options: AppOptions) -> LOG_LEVELS: + match options.log_level: + case EnumLogLevel.debug: + return LOG_LEVELS.DEBUG; + case EnumLogLevel.info: + return LOG_LEVELS.INFO; + case _: + return LOG_LEVELS.INFO; diff --git a/code/python/src/models/config/commands.py b/code/python/src/models/config/commands.py new file mode 100644 index 0000000..e4fd444 --- /dev/null +++ b/code/python/src/models/config/commands.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from src.thirdparty.config import *; + +from models.generated.commands import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'command_from_json', + 'interpret_command', +]; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# METHODS Convert to appropriate command type +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +def command_from_json(command_json: str) -> Command: + try: + instructions = json.loads(command_json); + except: + raise Exception('Invalid json!'); + try: + command = Command(**instructions); + except: + raise Exception('Invalid instruction format - consult schema!'); + command = interpret_command(command); + return command; + +def interpret_command(command: Command) -> Command: + match command.name: + case EnumAlgorithmNames.tarjan: + return CommandTarjan(**command.dict()); + case EnumAlgorithmNames.tsp: + return CommandTsp(**command.dict()); + case EnumAlgorithmNames.hirschberg: + return CommandHirschberg(**command.dict()); + raise command; diff --git a/code/python/src/models/hirschberg/paths.py b/code/python/src/models/hirschberg/paths.py index 234751f..eef07eb 100644 --- a/code/python/src/models/hirschberg/paths.py +++ b/code/python/src/models/hirschberg/paths.py @@ -6,7 +6,7 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ from src.thirdparty.types import *; -from src.setup.config import *; +from src.setup import config; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # EXPORTS @@ -23,6 +23,6 @@ __all__ = [ class Directions(Enum): UNSET = -1; # Prioritäten hier setzen - DIAGONAL = OPTIONS.hirschberg.move_priorities.diagonal; - HORIZONTAL = OPTIONS.hirschberg.move_priorities.horizontal; - VERTICAL = OPTIONS.hirschberg.move_priorities.vertical; + DIAGONAL = config.OPTIONS.hirschberg.move_priorities.diagonal; + HORIZONTAL = config.OPTIONS.hirschberg.move_priorities.horizontal; + VERTICAL = config.OPTIONS.hirschberg.move_priorities.vertical; diff --git a/code/python/src/models/hirschberg/penalties.py b/code/python/src/models/hirschberg/penalties.py index a00a7e6..c94ff07 100644 --- a/code/python/src/models/hirschberg/penalties.py +++ b/code/python/src/models/hirschberg/penalties.py @@ -6,7 +6,7 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ from src.thirdparty.types import *; -from src.setup.config import *; +from src.setup import config; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # EXPORTS @@ -22,7 +22,7 @@ __all__ = [ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def gap_penalty(x: str): - return OPTIONS.hirschberg.penality_gap; + return config.OPTIONS.hirschberg.penality_gap; def missmatch_penalty(x: str, y: str): - return 0 if x == y else OPTIONS.hirschberg.penality_mismatch; + return 0 if x == y else config.OPTIONS.hirschberg.penality_mismatch; diff --git a/code/python/src/setup/config.py b/code/python/src/setup/config.py index 6b053f4..7cee18f 100644 --- a/code/python/src/setup/config.py +++ b/code/python/src/setup/config.py @@ -12,6 +12,8 @@ from src.thirdparty.types import *; from models.generated.config import *; from models.generated.commands import *; +from src.core.log import *; +from src.models.config import *; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # EXPORTS @@ -41,21 +43,16 @@ def load_assets_config(path: str) -> Config: # pragma: no cover return Config(**assets); def create_commands(path: str) -> List[Command]: # pragma: no cover - commands = []; with open(path, 'r') as fp: assets = yaml_load(fp, Loader=yaml_FullLoader); - for command in assets: - match Command(**command).name: - case EnumAlgorithmNames.tarjan: - commands.append(CommandTarjan(**command)); - case EnumAlgorithmNames.tsp: - commands.append(CommandTsp(**command)); - case EnumAlgorithmNames.hirschberg: - commands.append(CommandHirschberg(**command)); - return commands; + return [ + interpret_command(Command(**instruction)) + for instruction in assets + ]; # use lazy loaing to ensure that values only loaded (once) when used -CONFIG: Config = lazy(load_assets_config, path=PATH_ASSETS_CONFIG); -INFO: Info = lazy(lambda x: x.info, CONFIG); -OPTIONS: AppOptions = lazy(lambda x: x.options, CONFIG); -COMMANDS: List[Command] = lazy(create_commands, path=PATH_ASSETS_COMMANDS); +CONFIG: Config = lazy(load_assets_config, path=PATH_ASSETS_CONFIG); +INFO: Info = lazy(lambda x: x.info, CONFIG); +OPTIONS: AppOptions = lazy(lambda x: x.options, CONFIG); +LOG_LEVEL: LOG_LEVELS = lazy(log_level, OPTIONS); +COMMANDS: List[Command] = lazy(create_commands, path=PATH_ASSETS_COMMANDS); diff --git a/code/python/src/thirdparty/log.py b/code/python/src/thirdparty/log.py new file mode 100644 index 0000000..03d159d --- /dev/null +++ b/code/python/src/thirdparty/log.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +import logging; +from logging import LogRecord; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'logging', + 'LogRecord', +]; diff --git a/code/python/src/thirdparty/misc.py b/code/python/src/thirdparty/misc.py index b485ace..8476b32 100644 --- a/code/python/src/thirdparty/misc.py +++ b/code/python/src/thirdparty/misc.py @@ -5,6 +5,9 @@ # IMPORTS # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +from datetime import datetime; +from datetime import timedelta; +import lorem; import re; from textwrap import dedent; @@ -13,6 +16,9 @@ from textwrap import dedent; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ __all__ = [ + 'lorem', + 'datetime', + 'timedelta', 're', 'dedent', ]; diff --git a/code/python/src/thirdparty/run.py b/code/python/src/thirdparty/run.py new file mode 100644 index 0000000..eda298d --- /dev/null +++ b/code/python/src/thirdparty/run.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +import aiohttp; +from asyncio import Future; +from asyncio import gather as asyncio_gather; +from asyncio import get_event_loop as asyncio_get_event_loop; +from asyncio import new_event_loop as asyncio_new_event_loop; +from asyncio import set_event_loop as asyncio_set_event_loop; +from asyncio import ensure_future as asyncio_ensure_future; +from asyncio import sleep as asyncio_sleep; +from asyncio import AbstractEventLoop; +from codetiming import Timer; + +from signal import Signals; +from signal import SIGINT; +from signal import SIGTERM; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'aiohttp', + 'Future', + 'asyncio_gather', + 'asyncio_ensure_future', + 'asyncio_get_event_loop', + 'asyncio_new_event_loop', + 'asyncio_set_event_loop', + 'asyncio_sleep', + 'AbstractEventLoop', + 'Timer', + 'Signals', + 'SIGINT', + 'SIGTERM', +]; diff --git a/code/python/src/thirdparty/types.py b/code/python/src/thirdparty/types.py index e51b94c..ef217d7 100644 --- a/code/python/src/thirdparty/types.py +++ b/code/python/src/thirdparty/types.py @@ -16,6 +16,7 @@ from typing import Generator; from typing import Generic; from typing import List; from typing import Optional; +from typing import ParamSpec; from typing import Tuple; from typing import Type; from typing import TypeVar; @@ -38,6 +39,7 @@ __all__ = [ 'Generic', 'List', 'Optional', + 'ParamSpec', 'Tuple', 'Type', 'TypeVar',