Added cpl-mail
Some checks failed
Build on push / prepare (push) Successful in 9s
Build on push / core (push) Successful in 18s
Build on push / query (push) Successful in 25s
Build on push / translation (push) Failing after 8s
Build on push / mail (push) Successful in 14s

This commit is contained in:
2025-09-15 20:56:07 +02:00
parent 3b120370b8
commit 25b4ca0696
344 changed files with 4567 additions and 4946 deletions

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,6 @@
from .application_abc import ApplicationABC
from .application_builder import ApplicationBuilder
from .application_builder_abc import ApplicationBuilderABC
from .application_extension_abc import ApplicationExtensionABC
from .startup_abc import StartupABC
from .startup_extension_abc import StartupExtensionABC

View File

@@ -0,0 +1,60 @@
from abc import ABC, abstractmethod
from typing import Optional
from cpl.core.configuration.configuration_abc import ConfigurationABC
from cpl.core.console.console import Console
from cpl.core.dependency_injection.service_provider_abc import ServiceProviderABC
from cpl.core.environment.application_environment_abc import ApplicationEnvironmentABC
class ApplicationABC(ABC):
r"""ABC for the Application class
Parameters:
config: :class:`cpl.core.configuration.configuration_abc.ConfigurationABC`
Contains object loaded from appsettings
services: :class:`cpl.core.dependency_injection.service_provider_abc.ServiceProviderABC`
Contains instances of prepared objects
"""
@abstractmethod
def __init__(self, config: ConfigurationABC, services: ServiceProviderABC):
self._configuration: Optional[ConfigurationABC] = config
self._environment: Optional[ApplicationEnvironmentABC] = self._configuration.environment
self._services: Optional[ServiceProviderABC] = services
def run(self):
r"""Entry point
Called by custom Application.main
"""
try:
self.configure()
self.main()
except KeyboardInterrupt:
Console.close()
async def run_async(self):
r"""Entry point
Called by custom Application.main
"""
try:
await self.configure()
await self.main()
except KeyboardInterrupt:
Console.close()
@abstractmethod
def configure(self):
r"""Configure the application
Called by :class:`cpl.core.application.application_abc.ApplicationABC.run`
"""
@abstractmethod
def main(self):
r"""Custom entry point
Called by :class:`cpl.core.application.application_abc.ApplicationABC.run`
"""

View File

@@ -0,0 +1,78 @@
from typing import Type, Optional, Callable, Union
from cpl.core.application.application_abc import ApplicationABC
from cpl.core.application.application_builder_abc import ApplicationBuilderABC
from cpl.core.application.application_extension_abc import ApplicationExtensionABC
from cpl.core.application.startup_abc import StartupABC
from cpl.core.application.startup_extension_abc import StartupExtensionABC
from cpl.core.configuration.configuration import Configuration
from cpl.core.dependency_injection.service_collection import ServiceCollection
class ApplicationBuilder(ApplicationBuilderABC):
r"""This is class is used to build an object of :class:`cpl.core.application.application_abc.ApplicationABC`
Parameter:
app: Type[:class:`cpl.core.application.application_abc.ApplicationABC`]
Application to build
"""
def __init__(self, app: Type[ApplicationABC]):
ApplicationBuilderABC.__init__(self)
self._app = app
self._startup: Optional[StartupABC] = None
self._configuration = Configuration()
self._environment = self._configuration.environment
self._services = ServiceCollection(self._configuration)
self._app_extensions: list[Callable] = []
self._startup_extensions: list[Callable] = []
def use_startup(self, startup: Type[StartupABC]) -> "ApplicationBuilder":
self._startup = startup()
return self
def use_extension(
self, extension: Type[Union[ApplicationExtensionABC, StartupExtensionABC]]
) -> "ApplicationBuilder":
if issubclass(extension, ApplicationExtensionABC) and extension not in self._app_extensions:
self._app_extensions.append(extension)
elif issubclass(extension, StartupExtensionABC) and extension not in self._startup_extensions:
self._startup_extensions.append(extension)
return self
def _build_startup(self):
for ex in self._startup_extensions:
extension = ex()
extension.configure_configuration(self._configuration, self._environment)
extension.configure_services(self._services, self._environment)
if self._startup is not None:
self._startup.configure_configuration(self._configuration, self._environment)
self._startup.configure_services(self._services, self._environment)
def build(self) -> ApplicationABC:
self._build_startup()
config = self._configuration
services = self._services.build_service_provider()
for ex in self._app_extensions:
extension = ex()
extension.run(config, services)
return self._app(config, services)
async def build_async(self) -> ApplicationABC:
self._build_startup()
config = self._configuration
services = self._services.build_service_provider()
for ex in self._app_extensions:
extension = ex()
await extension.run(config, services)
return self._app(config, services)

View File

@@ -0,0 +1,47 @@
from abc import ABC, abstractmethod
from typing import Type
from cpl.core.application.application_abc import ApplicationABC
from cpl.core.application.startup_abc import StartupABC
class ApplicationBuilderABC(ABC):
r"""ABC for the :class:`cpl.core.application.application_builder.ApplicationBuilder`"""
@abstractmethod
def __init__(self, *args):
pass
@abstractmethod
def use_startup(self, startup: Type[StartupABC]):
r"""Sets the custom startup class to use
Parameter:
startup: Type[:class:`cpl.core.application.startup_abc.StartupABC`]
Startup class to use
"""
@abstractmethod
async def use_startup(self, startup: Type[StartupABC]):
r"""Sets the custom startup class to use async
Parameter:
startup: Type[:class:`cpl.core.application.startup_abc.StartupABC`]
Startup class to use
"""
@abstractmethod
def build(self) -> ApplicationABC:
r"""Creates custom application object
Returns:
Object of :class:`cpl.core.application.application_abc.ApplicationABC`
"""
@abstractmethod
async def build_async(self) -> ApplicationABC:
r"""Creates custom application object async
Returns:
Object of :class:`cpl.core.application.application_abc.ApplicationABC`
"""

View File

@@ -0,0 +1,18 @@
from abc import ABC, abstractmethod
from cpl.core.configuration import ConfigurationABC
from cpl.core.dependency_injection import ServiceProviderABC
class ApplicationExtensionABC(ABC):
@abstractmethod
def __init__(self):
pass
@abstractmethod
def run(self, config: ConfigurationABC, services: ServiceProviderABC):
pass
@abstractmethod
async def run(self, config: ConfigurationABC, services: ServiceProviderABC):
pass

View File

@@ -0,0 +1,38 @@
from abc import ABC, abstractmethod
from cpl.core.configuration.configuration_abc import ConfigurationABC
from cpl.core.dependency_injection.service_collection_abc import ServiceCollectionABC
from cpl.core.dependency_injection.service_provider_abc import ServiceProviderABC
from cpl.core.environment.application_environment_abc import ApplicationEnvironmentABC
class StartupABC(ABC):
r"""ABC for the startup class"""
@abstractmethod
def __init__(self):
pass
@abstractmethod
def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC) -> ConfigurationABC:
r"""Creates configuration of application
Parameter:
config: :class:`cpl.core.configuration.configuration_abc.ConfigurationABC`
env: :class:`cpl.core.environment.application_environment_abc`
Returns:
Object of :class:`cpl.core.configuration.configuration_abc.ConfigurationABC`
"""
@abstractmethod
def configure_services(self, service: ServiceCollectionABC, env: ApplicationEnvironmentABC) -> ServiceProviderABC:
r"""Creates service provider
Parameter:
services: :class:`cpl.core.dependency_injection.service_collection_abc`
env: :class:`cpl.core.environment.application_environment_abc`
Returns:
Object of :class:`cpl.core.dependency_injection.service_provider_abc.ServiceProviderABC`
"""

View File

@@ -0,0 +1,31 @@
from abc import ABC, abstractmethod
from cpl.core.configuration.configuration_abc import ConfigurationABC
from cpl.core.dependency_injection.service_collection_abc import ServiceCollectionABC
from cpl.core.environment.application_environment_abc import ApplicationEnvironmentABC
class StartupExtensionABC(ABC):
r"""ABC for startup extension classes"""
@abstractmethod
def __init__(self):
pass
@abstractmethod
def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC):
r"""Creates configuration of application
Parameter:
config: :class:`cpl.core.configuration.configuration_abc.ConfigurationABC`
env: :class:`cpl.core.environment.application_environment_abc`
"""
@abstractmethod
def configure_services(self, service: ServiceCollectionABC, env: ApplicationEnvironmentABC):
r"""Creates service provider
Parameter:
services: :class:`cpl.core.dependency_injection.service_collection_abc`
env: :class:`cpl.core.environment.application_environment_abc`
"""

View File

@@ -0,0 +1,12 @@
from .argument_abc import ArgumentABC
from .argument_builder import ArgumentBuilder
from .argument_executable_abc import ArgumentExecutableABC
from .argument_type_enum import ArgumentTypeEnum
from .configuration import Configuration
from .configuration_abc import ConfigurationABC
from .configuration_model_abc import ConfigurationModelABC
from .configuration_variable_name_enum import ConfigurationVariableNameEnum
from .executable_argument import ExecutableArgument
from .flag_argument import FlagArgument
from .validator_abc import ValidatorABC
from .variable_argument import VariableArgument

View File

@@ -0,0 +1,64 @@
from abc import ABC, abstractmethod
from cpl.core.configuration.argument_type_enum import ArgumentTypeEnum
class ArgumentABC(ABC):
@abstractmethod
def __init__(
self,
token: str,
name: str,
aliases: list[str],
prevent_next_executable: bool = False,
console_arguments: list["ArgumentABC"] = None,
):
r"""Representation of an console argument
Parameter:
token: :class:`str`
name: :class:`str`
aliases: list[:class:`str`]
console_arguments: List[:class:`cpl.core.configuration.console_argument.ConsoleArgument`]
"""
self._token = token
self._name = name
self._aliases = aliases
self._prevent_next_executable = prevent_next_executable
self._console_arguments = console_arguments if console_arguments is not None else []
@property
def token(self) -> str:
return self._token
@property
def name(self) -> str:
return self._name
@property
def aliases(self) -> list[str]:
return self._aliases
@property
def prevent_next_executable(self) -> bool:
return self._prevent_next_executable
@property
def console_arguments(self) -> list["ArgumentABC"]:
return self._console_arguments
def add_console_argument(self, arg_type: ArgumentTypeEnum, *args, **kwargs) -> "ArgumentABC":
r"""Creates and adds a console argument to known console arguments
Parameter:
arg_type: :class:`str`
Specifies the specific type of the argument
Returns:
self :class:`cpl.core.configuration.console_argument.ConsoleArgument` not created argument!
"""
from cpl.core.configuration.argument_builder import ArgumentBuilder
argument = ArgumentBuilder.build_argument(arg_type, *args, *kwargs)
self._console_arguments.append(argument)
return self

View File

@@ -0,0 +1,30 @@
from typing import Union
from cpl.core.configuration.argument_type_enum import ArgumentTypeEnum
from cpl.core.configuration.executable_argument import ExecutableArgument
from cpl.core.configuration.flag_argument import FlagArgument
from cpl.core.configuration.variable_argument import VariableArgument
from cpl.core.console import Console
class ArgumentBuilder:
@staticmethod
def build_argument(
arg_type: ArgumentTypeEnum, *args, **kwargs
) -> Union[ExecutableArgument, FlagArgument, VariableArgument]:
argument = None
try:
match arg_type:
case ArgumentTypeEnum.Flag:
argument = FlagArgument(*args, **kwargs)
case ArgumentTypeEnum.Executable:
argument = ExecutableArgument(*args, **kwargs)
case ArgumentTypeEnum.Variable:
argument = VariableArgument(*args, **kwargs)
case _:
Console.error("Invalid argument type")
Console.close()
except TypeError as e:
Console.error(str(e))
Console.close()
return argument

View File

@@ -0,0 +1,11 @@
from abc import ABC, abstractmethod
class ArgumentExecutableABC(ABC):
@abstractmethod
def __init__(self):
pass
@abstractmethod
def run(self, args: list[str]):
pass

View File

@@ -0,0 +1,7 @@
from enum import Enum
class ArgumentTypeEnum(Enum):
Flag = 0
Executable = 1
Variable = 3

View File

@@ -0,0 +1,385 @@
import json
import os
import sys
import traceback
from collections.abc import Callable
from typing import Union, Optional
from cpl.core.configuration.argument_abc import ArgumentABC
from cpl.core.configuration.argument_builder import ArgumentBuilder
from cpl.core.configuration.argument_type_enum import ArgumentTypeEnum
from cpl.core.configuration.configuration_abc import ConfigurationABC
from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl.core.configuration.configuration_variable_name_enum import (
ConfigurationVariableNameEnum,
)
from cpl.core.configuration.executable_argument import ExecutableArgument
from cpl.core.configuration.flag_argument import FlagArgument
from cpl.core.configuration.variable_argument import VariableArgument
from cpl.core.console.console import Console
from cpl.core.console.foreground_color_enum import ForegroundColorEnum
from cpl.core.dependency_injection.service_provider_abc import ServiceProviderABC
from cpl.core.environment.application_environment import ApplicationEnvironment
from cpl.core.environment.application_environment_abc import ApplicationEnvironmentABC
from cpl.core.environment.environment_name_enum import EnvironmentNameEnum
from cpl.core.type import T, R
from cpl.core.utils.json_processor import JSONProcessor
class Configuration(ConfigurationABC):
def __init__(self):
r"""Representation of configuration"""
ConfigurationABC.__init__(self)
self._application_environment = ApplicationEnvironment()
self._config: dict[Union[type, str], Union[ConfigurationModelABC, str]] = {}
self._argument_types: list[ArgumentABC] = []
self._additional_arguments: list[str] = []
self._argument_error_function: Optional[Callable] = None
self._handled_args = []
@property
def environment(self) -> ApplicationEnvironmentABC:
return self._application_environment
@property
def additional_arguments(self) -> list[str]:
return self._additional_arguments
@property
def argument_error_function(self) -> Optional[Callable]:
return self._argument_error_function
@argument_error_function.setter
def argument_error_function(self, argument_error_function: Callable):
self._argument_error_function = argument_error_function
@property
def arguments(self) -> list[ArgumentABC]:
return self._argument_types
@staticmethod
def _print_info(name: str, message: str):
r"""Prints an info message
Parameter:
name: :class:`str`
Info name
message: :class:`str`
Info message
"""
Console.set_foreground_color(ForegroundColorEnum.green)
Console.write_line(f"[{name}] {message}")
Console.set_foreground_color(ForegroundColorEnum.default)
@staticmethod
def _print_warn(name: str, message: str):
r"""Prints a warning
Parameter:
name: :class:`str`
Warning name
message: :class:`str`
Warning message
"""
Console.set_foreground_color(ForegroundColorEnum.yellow)
Console.write_line(f"[{name}] {message}")
Console.set_foreground_color(ForegroundColorEnum.default)
@staticmethod
def _print_error(name: str, message: str):
r"""Prints an error
Parameter:
name: :class:`str`
Error name
message: :class:`str`
Error message
"""
Console.set_foreground_color(ForegroundColorEnum.red)
Console.write_line(f"[{name}] {message}")
Console.set_foreground_color(ForegroundColorEnum.default)
def _set_variable(self, name: str, value: any):
r"""Sets variable to given value
Parameter:
name: :class:`str`
Name of the variable
value: :class:`any`
Value of the variable
"""
if name == ConfigurationVariableNameEnum.environment.value:
self._application_environment.environment_name = EnvironmentNameEnum(value)
elif name == ConfigurationVariableNameEnum.name.value:
self._application_environment.application_name = value
elif name == ConfigurationVariableNameEnum.customer.value:
self._application_environment.customer = value
else:
self._config[name] = value
def _load_json_file(self, file: str, output: bool) -> dict:
r"""Reads the json file
Parameter:
file: :class:`str`
Name of the file
output: :class:`bool`
Specifies whether an output should take place
Returns:
Object of :class:`dict`
"""
try:
# open config file, create if not exists
with open(file, encoding="utf-8") as cfg:
# load json
json_cfg = json.load(cfg)
if output:
self._print_info(__name__, f"Loaded config file: {file}")
return json_cfg
except Exception as e:
self._print_error(__name__, f"Cannot load config file: {file}! -> {e}")
return {}
def _handle_pre_or_post_executables(self, pre: bool, argument: ExecutableArgument, services: ServiceProviderABC):
script_type = "pre-" if pre else "post-"
from cpl_cli.configuration.workspace_settings import WorkspaceSettings
workspace: Optional[WorkspaceSettings] = self.get_configuration(WorkspaceSettings)
if workspace is None or len(workspace.scripts) == 0:
return
for script in workspace.scripts:
if script_type not in script and not script.startswith(script_type):
continue
# split in two ifs to prevent exception
if script.split(script_type)[1] != argument.name:
continue
from cpl_cli.command.custom_script_service import CustomScriptService
css: CustomScriptService = services.get_service(CustomScriptService)
if css is None:
continue
Console.write_line()
self._set_variable("ACTIVE_EXECUTABLE", script)
css.run(self._additional_arguments)
def _parse_arguments(
self,
executables: list[ArgumentABC],
arg_list: list[str],
args_types: list[ArgumentABC],
):
for i in range(0, len(arg_list)):
arg_str = arg_list[i]
for n in range(0, len(args_types)):
arg = args_types[n]
arg_str_without_token = arg_str
if arg.token != "" and arg.token in arg_str:
arg_str_without_token = arg_str.split(arg.token)[1]
# executable
if isinstance(arg, ExecutableArgument):
if (
arg_str.startswith(arg.token)
and arg_str_without_token == arg.name
or arg_str_without_token in arg.aliases
):
executables.append(arg)
self._handled_args.append(arg_str)
self._parse_arguments(executables, arg_list[i + 1 :], arg.console_arguments)
# variables
elif isinstance(arg, VariableArgument):
arg_str_without_value = arg_str_without_token
if arg.value_token in arg_str_without_value:
arg_str_without_value = arg_str_without_token.split(arg.value_token)[0]
if (
arg_str.startswith(arg.token)
and arg_str_without_value == arg.name
or arg_str_without_value in arg.aliases
):
if arg.value_token != " ":
value = arg_str_without_token.split(arg.value_token)[1]
else:
value = arg_list[i + 1]
self._set_variable(arg.name, value)
self._handled_args.append(arg_str)
self._handled_args.append(value)
self._parse_arguments(executables, arg_list[i + 1 :], arg.console_arguments)
# flags
elif isinstance(arg, FlagArgument):
if (
arg_str.startswith(arg.token)
and arg_str_without_token == arg.name
or arg_str_without_token in arg.aliases
):
if arg_str in self._additional_arguments:
self._additional_arguments.remove(arg_str)
self._additional_arguments.append(arg.name)
self._handled_args.append(arg_str)
self._parse_arguments(executables, arg_list[i + 1 :], arg.console_arguments)
# add left over values to args
if arg_str not in self._additional_arguments and arg_str not in self._handled_args:
self._additional_arguments.append(arg_str)
def add_environment_variables(self, prefix: str):
for env_var in os.environ.keys():
if not env_var.startswith(prefix):
continue
self._set_variable(env_var.replace(prefix, ""), os.environ[env_var])
def add_console_argument(self, argument: ArgumentABC):
self._argument_types.append(argument)
def add_json_file(self, name: str, optional: bool = None, output: bool = True, path: str = None):
if os.path.isabs(name):
file_path = name
else:
path_root = self._application_environment.working_directory
if path is not None:
path_root = path
if str(path_root).endswith("/") and not name.startswith("/"):
file_path = f"{path_root}{name}"
else:
file_path = f"{path_root}/{name}"
if not os.path.isfile(file_path):
if optional is not True:
if output:
self._print_error(__name__, f"File not found: {file_path}")
sys.exit()
if output:
self._print_warn(__name__, f"Not Loaded config file: {file_path}")
return None
config_from_file = self._load_json_file(file_path, output)
for sub in ConfigurationModelABC.__subclasses__():
for key, value in config_from_file.items():
if sub.__name__ != key and sub.__name__.replace("Settings", "") != key:
continue
configuration = sub()
from_dict = getattr(configuration, "from_dict", None)
if from_dict is not None and not hasattr(from_dict, "is_base_func"):
Console.set_foreground_color(ForegroundColorEnum.yellow)
Console.write_line(
f"{sub.__name__}.from_dict is deprecated. Instead, set attributes as typed arguments in __init__. They can be None by default!"
)
Console.color_reset()
configuration.from_dict(value)
else:
configuration = JSONProcessor.process(sub, value)
self.add_configuration(sub, configuration)
def add_configuration(self, key_type: T, value: any):
self._config[key_type] = value
def create_console_argument(
self,
arg_type: ArgumentTypeEnum,
token: str,
name: str,
aliases: list[str],
*args,
**kwargs,
) -> ArgumentABC:
argument = ArgumentBuilder.build_argument(arg_type, token, name, aliases, *args, **kwargs)
self._argument_types.append(argument)
return argument
def for_each_argument(self, call: Callable):
for arg in self._argument_types:
call(arg)
def get_configuration(self, search_type: T) -> Optional[R]:
if type(search_type) is str:
if search_type == ConfigurationVariableNameEnum.environment.value:
return self._application_environment.environment_name
elif search_type == ConfigurationVariableNameEnum.name.value:
return self._application_environment.application_name
elif search_type == ConfigurationVariableNameEnum.customer.value:
return self._application_environment.customer
if search_type not in self._config:
return None
for config_model in self._config:
if config_model == search_type:
return self._config[config_model]
def parse_console_arguments(self, services: ServiceProviderABC, error: bool = None) -> bool:
# sets environment variables as possible arguments as: --VAR=VALUE
for arg_name in ConfigurationVariableNameEnum.to_list():
self.add_console_argument(VariableArgument("--", str(arg_name).upper(), [str(arg_name).lower()], "="))
success = False
try:
arg_list = sys.argv[1:]
executables: list[ExecutableArgument] = []
self._parse_arguments(executables, arg_list, self._argument_types)
except Exception:
Console.error("An error occurred while parsing arguments.", traceback.format_exc())
sys.exit()
try:
prevent = False
for exe in executables:
if prevent:
continue
if exe.validators is not None:
abort = False
for validator_type in exe.validators:
validator = services.get_service(validator_type)
result = validator.validate()
abort = not result
if abort:
break
if abort:
sys.exit()
cmd = services.get_service(exe.executable_type)
self._handle_pre_or_post_executables(True, exe, services)
self._set_variable("ACTIVE_EXECUTABLE", exe.name)
args = self.get_configuration("ARGS")
if args is not None:
for arg in args.split(" "):
if arg == "":
continue
self._additional_arguments.append(arg)
cmd.run(self._additional_arguments)
self._handle_pre_or_post_executables(False, exe, services)
prevent = exe.prevent_next_executable
success = True
except Exception:
Console.error("An error occurred while executing arguments.", traceback.format_exc())
sys.exit()
return success

View File

@@ -0,0 +1,140 @@
from abc import abstractmethod, ABC
from collections.abc import Callable
from typing import Optional
from cpl.core.configuration.argument_abc import ArgumentABC
from cpl.core.configuration.argument_type_enum import ArgumentTypeEnum
from cpl.core.environment.application_environment_abc import ApplicationEnvironmentABC
from cpl.core.type import T, R
class ConfigurationABC(ABC):
@abstractmethod
def __init__(self):
r"""ABC for the :class:`cpl.core.configuration.configuration.Configuration`"""
@property
@abstractmethod
def environment(self) -> ApplicationEnvironmentABC:
pass
@property
@abstractmethod
def additional_arguments(self) -> list[str]:
pass
@property
@abstractmethod
def argument_error_function(self) -> Optional[Callable]:
pass
@argument_error_function.setter
@abstractmethod
def argument_error_function(self, argument_error_function: Callable):
pass
@property
@abstractmethod
def arguments(self) -> list[ArgumentABC]:
pass
@abstractmethod
def add_environment_variables(self, prefix: str):
r"""Reads the environment variables
Parameter:
prefix: :class:`str`
Prefix of the variables
"""
@abstractmethod
def add_console_argument(self, argument: ArgumentABC):
r"""Adds console argument to known console arguments
Parameter:
argument: :class:`cpl.core.configuration.console_argument.ConsoleArgumentABC`
Specifies the console argument
"""
@abstractmethod
def add_json_file(self, name: str, optional: bool = None, output: bool = True, path: str = None):
r"""Reads and saves settings from given json file
Parameter:
name: :class:`str`
Name of the file
optional: :class:`str`
Specifies whether an error should occur if the file was not found
output: :class:`bool`
Specifies whether an output should take place
path: :class:`str`
Path in which the file should be stored
"""
@abstractmethod
def add_configuration(self, key_type: T, value: any):
r"""Add configuration object
Parameter:
key_type: :class:`cpl.core.type.T`
Type of the value
value: any
Object of the value
"""
@abstractmethod
def create_console_argument(
self, arg_type: ArgumentTypeEnum, token: str, name: str, aliases: list[str], *args, **kwargs
) -> ArgumentABC:
r"""Creates and adds a console argument to known console arguments
Parameter:
token: :class:`str`
Specifies optional beginning of argument
name :class:`str`
Specifies name of argument
aliases list[:class:`str`]
Specifies possible aliases of name
value_token :class:`str`
Specifies were the value begins
is_value_token_optional :class:`bool`
Specifies if values are optional
runnable: :class:`cpl.core.configuration.console_argument.ConsoleArgumentABC`
Specifies class to run when called if value is not None
Returns:
Object of :class:`cpl.core.configuration.console_argument.ConsoleArgumentABC`
"""
@abstractmethod
def for_each_argument(self, call: Callable):
r"""Iterates through all arguments and calls the call function
Parameter:
call: :class:`Callable`
Call for each argument
"""
@abstractmethod
def get_configuration(self, search_type: T) -> Optional[R]:
r"""Returns value from configuration by given type
Parameter:
search_type: :class:`cpl.core.type.T`
Type to search for
Returns:
Object of Union[:class:`str`, :class:`cpl.core.configuration.configuration_model_abc.ConfigurationModelABC`]
"""
@abstractmethod
def parse_console_arguments(self, services: "ServiceProviderABC", error: bool = None) -> bool:
r"""Reads the console arguments
Parameter:
error: :class:`bool`
Defines is invalid argument error will be shown or not
Returns:
Bool to specify if executables were executed or not.
"""

View File

@@ -0,0 +1,21 @@
from abc import ABC, abstractmethod
def base_func(method):
method.is_base_func = True
return method
class ConfigurationModelABC(ABC):
@abstractmethod
def __init__(self):
r"""ABC for settings representation"""
@base_func
def from_dict(self, settings: dict):
r"""DEPRECATED: Set attributes as typed arguments in __init__ instead. See https://docs.sh-edraft.de/cpl/deprecated.html#ConfigurationModelABC-from_dict-method for further information
Converts attributes to dict
Parameter:
settings: :class:`dict`
"""

View File

@@ -0,0 +1,11 @@
from enum import Enum
class ConfigurationVariableNameEnum(Enum):
environment = "ENVIRONMENT"
name = "NAME"
customer = "CUSTOMER"
@staticmethod
def to_list():
return [var.value for var in ConfigurationVariableNameEnum]

View File

@@ -0,0 +1,40 @@
from typing import Type, Optional
from cpl.core.configuration.argument_executable_abc import ArgumentExecutableABC
from cpl.core.configuration.argument_abc import ArgumentABC
from cpl.core.configuration.validator_abc import ValidatorABC
class ExecutableArgument(ArgumentABC):
def __init__(
self,
token: str,
name: str,
aliases: list[str],
executable: Type[ArgumentExecutableABC],
prevent_next_executable: bool = False,
validators: list[Type[ValidatorABC]] = None,
console_arguments: list["ArgumentABC"] = None,
):
self._executable_type = executable
self._validators = validators
self._executable: Optional[ArgumentExecutableABC] = None
ArgumentABC.__init__(self, token, name, aliases, prevent_next_executable, console_arguments)
@property
def executable_type(self) -> type:
return self._executable_type
def set_executable(self, executable: ArgumentExecutableABC):
self._executable = executable
@property
def validators(self) -> list[Type[ValidatorABC]]:
return self._validators
def run(self, args: list[str]):
r"""Executes runnable if exists"""
if self._executable is None:
return
self._executable.execute(args)

View File

@@ -0,0 +1,13 @@
from cpl.core.configuration.argument_abc import ArgumentABC
class FlagArgument(ArgumentABC):
def __init__(
self,
token: str,
name: str,
aliases: list[str],
prevent_next_executable: bool = False,
console_arguments: list["ArgumentABC"] = None,
):
ArgumentABC.__init__(self, token, name, aliases, prevent_next_executable, console_arguments)

View File

@@ -0,0 +1,11 @@
from abc import ABC, abstractmethod
class ValidatorABC(ABC):
@abstractmethod
def __init__(self):
pass
@abstractmethod
def validate(self) -> bool:
pass

View File

@@ -0,0 +1,28 @@
from cpl.core.configuration.argument_abc import ArgumentABC
class VariableArgument(ArgumentABC):
def __init__(
self,
token: str,
name: str,
aliases: list[str],
value_token: str,
prevent_next_executable: bool = False,
console_arguments: list["ArgumentABC"] = None,
):
self._value_token = value_token
self._value: str = ""
ArgumentABC.__init__(self, token, name, aliases, prevent_next_executable, console_arguments)
@property
def value_token(self) -> str:
return self._value_token
@property
def value(self) -> str:
return self._value
def set_value(self, value: str):
self._value = value

View File

@@ -0,0 +1,5 @@
from .background_color_enum import BackgroundColorEnum
from .console import Console
from .console_call import ConsoleCall
from .foreground_color_enum import ForegroundColorEnum
from .spinner_thread import SpinnerThread

View File

@@ -0,0 +1,13 @@
from enum import Enum
class BackgroundColorEnum(Enum):
default = "on_default"
grey = "on_grey"
red = "on_red"
green = "on_green"
yellow = "on_yellow"
blue = "on_blue"
magenta = "on_magenta"
cyan = "on_cyan"
white = "on_white"

View File

@@ -0,0 +1,578 @@
import os
import sys
import time
from collections.abc import Callable
from typing import Union, Optional
from art import text2art
import colorama
from tabulate import tabulate
from termcolor import colored
from cpl.core.console.background_color_enum import BackgroundColorEnum
from cpl.core.console.console_call import ConsoleCall
from cpl.core.console.foreground_color_enum import ForegroundColorEnum
from cpl.core.console.spinner_thread import SpinnerThread
class Console:
r"""Useful functions for handling with input and output"""
colorama.init()
_is_first_write = True
_background_color: BackgroundColorEnum = BackgroundColorEnum.default
_foreground_color: ForegroundColorEnum = ForegroundColorEnum.default
_x: Optional[int] = None
_y: Optional[int] = None
_disabled: bool = False
_hold_back = False
_hold_back_calls: list[ConsoleCall] = []
_select_menu_items: list[str] = []
_is_first_select_menu_output = True
_selected_menu_item_index: int = 0
_selected_menu_item_char: str = ""
_selected_menu_option_foreground_color: ForegroundColorEnum = ForegroundColorEnum.default
_selected_menu_option_background_color: BackgroundColorEnum = BackgroundColorEnum.default
_selected_menu_cursor_foreground_color: ForegroundColorEnum = ForegroundColorEnum.default
_selected_menu_cursor_background_color: BackgroundColorEnum = BackgroundColorEnum.default
"""Properties"""
@classmethod
@property
def background_color(cls) -> str:
return str(cls._background_color.value)
@classmethod
@property
def foreground_color(cls) -> str:
return str(cls._foreground_color.value)
"""Settings"""
@classmethod
def set_hold_back(cls, value: bool):
cls._hold_back = value
@classmethod
def set_background_color(cls, color: Union[BackgroundColorEnum, str]):
r"""Sets the background color
Parameter:
color: Union[:class:`cpl.core.console.background_color_enum.BackgroundColorEnum`, :class:`str`]
Background color of the console
"""
if type(color) is str:
cls._background_color = BackgroundColorEnum[color]
else:
cls._background_color = color
@classmethod
def set_foreground_color(cls, color: Union[ForegroundColorEnum, str]):
r"""Sets the foreground color
Parameter:
color: Union[:class:`cpl.core.console.background_color_enum.BackgroundColorEnum`, :class:`str`]
Foreground color of the console
"""
if type(color) is str:
cls._foreground_color = ForegroundColorEnum[color]
else:
cls._foreground_color = color
@classmethod
def reset_cursor_position(cls):
r"""Resets cursor position"""
cls._x = None
cls._y = None
@classmethod
def set_cursor_position(cls, x: int, y: int):
r"""Sets cursor position
Parameter:
x: :class:`int`
X coordinate
y: :class:`int`
Y coordinate
"""
cls._x = x
cls._y = y
"""Useful protected functions"""
@classmethod
def _output(cls, string: str, x: int = None, y: int = None, end: str = None):
r"""Prints given output with given format
Parameter:
string: :class:`str`
Message to print
x: :class:`int`
X coordinate
y: :class:`int`
Y coordinate
end: :class:`str`
End character of the message (could be \n)
"""
if cls._is_first_write:
cls._is_first_write = False
if end is None:
end = "\n"
args = []
colored_args = []
if x is not None and y is not None:
args.append(f"\033[{y};{x}H")
elif cls._x is not None and cls._y is not None:
args.append(f"\033[{cls._y};{cls._x}H")
colored_args.append(string)
if (
cls._foreground_color != ForegroundColorEnum.default
and cls._background_color == BackgroundColorEnum.default
):
colored_args.append(cls._foreground_color.value)
elif (
cls._foreground_color == ForegroundColorEnum.default
and cls._background_color != BackgroundColorEnum.default
):
colored_args.append(cls._background_color.value)
elif (
cls._foreground_color != ForegroundColorEnum.default
and cls._background_color != BackgroundColorEnum.default
):
colored_args.append(cls._foreground_color.value)
colored_args.append(cls._background_color.value)
args.append(colored(*colored_args))
print(*args, end=end)
@classmethod
def _show_select_menu(cls):
r"""Shows the select menu"""
if not cls._is_first_select_menu_output:
for _ in range(0, len(cls._select_menu_items) + 1):
sys.stdout.write("\x1b[1A\x1b[2K")
else:
cls._is_first_select_menu_output = False
for i in range(0, len(cls._select_menu_items)):
Console.set_foreground_color(cls._selected_menu_cursor_foreground_color)
Console.set_background_color(cls._selected_menu_cursor_background_color)
placeholder = ""
for _ in cls._selected_menu_item_char:
placeholder += " "
Console.write_line(
f"{cls._selected_menu_item_char if cls._selected_menu_item_index == i else placeholder} "
)
Console.set_foreground_color(cls._selected_menu_option_foreground_color)
Console.set_background_color(cls._selected_menu_option_background_color)
Console.write(f"{cls._select_menu_items[i]}")
Console.write_line()
@classmethod
def _select_menu_key_press(cls, key):
r"""Event function when key press is detected
Parameter:
key: :class:`pynput.keyboard.Key`
Pressed key
"""
from pynput.keyboard import Key
if key == Key.down:
if cls._selected_menu_item_index == len(cls._select_menu_items) - 1:
return
cls._selected_menu_item_index += 1
cls._show_select_menu()
elif key == Key.up:
if cls._selected_menu_item_index == 0:
return
cls._selected_menu_item_index -= 1
cls._show_select_menu()
elif key == Key.enter:
return False
""" Useful public functions"""
@classmethod
def banner(cls, string: str):
r"""Prints the string as a banner
Parameter:
string: :class:`str`
Message to print as banner
"""
if cls._disabled:
return
if cls._hold_back:
cls._hold_back_calls.append(ConsoleCall(cls.banner, string))
return
cls.write_line(text2art(string))
@classmethod
def color_reset(cls):
r"""Resets the color settings"""
cls._background_color = BackgroundColorEnum.default
cls._foreground_color = ForegroundColorEnum.default
@classmethod
def clear(cls):
r"""Clears the console"""
if cls._hold_back:
cls._hold_back_calls.append(ConsoleCall(cls.clear))
return
os.system("cls" if os.name == "nt" else "clear")
@classmethod
def close(cls):
r"""Closes the application"""
if cls._disabled:
return
if cls._hold_back:
cls._hold_back_calls.append(ConsoleCall(cls.close))
return
Console.color_reset()
Console.write("\n\n\nPress any key to continue...")
Console.read()
sys.exit()
@classmethod
def disable(cls):
r"""Disables console interaction"""
cls._disabled = True
@classmethod
def error(cls, string: str, tb: str = None):
r"""Prints an error with traceback
Parameter:
string: :class:`str`
Error message
tb: :class:`str`
Error traceback
"""
if cls._disabled:
return
if cls._hold_back:
cls._hold_back_calls.append(ConsoleCall(cls.error, string, tb))
return
cls.set_foreground_color("red")
if tb is not None:
cls.write_line(f"{string} -> {tb}")
else:
cls.write_line(string)
cls.set_foreground_color("default")
@classmethod
def enable(cls):
r"""Enables console interaction"""
cls._disabled = False
@classmethod
def read(cls, output: str = None) -> str:
r"""Reads in line
Parameter:
output: :class:`str`
String to print before input
Returns:
input()
"""
if output is not None and not cls._hold_back:
cls.write_line(output)
return input()
@classmethod
def read_line(cls, output: str = None) -> str:
r"""Reads in next line
Parameter:
output: :class:`str`
String to print before input
Returns:
input()
"""
if cls._disabled and not cls._hold_back:
return ""
if output is not None:
cls.write_line(output)
cls._output("\n", end="")
return input()
@classmethod
def table(cls, header: list[str], values: list[list[str]]):
r"""Prints a table with header and values
Parameter:
header: List[:class:`str`]
Header of the table
values: List[List[:class:`str`]]
Values of the table
"""
if cls._disabled:
return
if cls._hold_back:
cls._hold_back_calls.append(ConsoleCall(cls.table, header, values))
return
table = tabulate(values, headers=header)
Console.write_line(table)
Console.write("\n")
@classmethod
def select(
cls,
char: str,
message: str,
options: list[str],
header_foreground_color: Union[str, ForegroundColorEnum] = ForegroundColorEnum.default,
header_background_color: Union[str, BackgroundColorEnum] = BackgroundColorEnum.default,
option_foreground_color: Union[str, ForegroundColorEnum] = ForegroundColorEnum.default,
option_background_color: Union[str, BackgroundColorEnum] = BackgroundColorEnum.default,
cursor_foreground_color: Union[str, ForegroundColorEnum] = ForegroundColorEnum.default,
cursor_background_color: Union[str, BackgroundColorEnum] = BackgroundColorEnum.default,
) -> str:
r"""Prints select menu
Parameter:
char: :class:`str`
Character to show which element is selected
message: :class:`str`
Message or header of the selection
options: List[:class:`str`]
Selectable options
header_foreground_color: Union[:class:`str`, :class:`cpl.core.console.foreground_color_enum.ForegroundColorEnum`]
Foreground color of the header
header_background_color: Union[:class:`str`, :class:`cpl.core.console.background_color_enum.BackgroundColorEnum`]
Background color of the header
option_foreground_color: Union[:class:`str`, :class:`cpl.core.console.foreground_color_enum.ForegroundColorEnum`]
Foreground color of the options
option_background_color: Union[:class:`str`, :class:`cpl.core.console.background_color_enum.BackgroundColorEnum`]
Background color of the options
cursor_foreground_color: Union[:class:`str`, :class:`cpl.core.console.foreground_color_enum.ForegroundColorEnum`]
Foreground color of the cursor
cursor_background_color: Union[:class:`str`, :class:`cpl.core.console.background_color_enum.BackgroundColorEnum`]
Background color of the cursor
Returns:
Selected option as :class:`str`
"""
cls._selected_menu_item_char = char
cls.options = options
cls._select_menu_items = cls.options
if option_foreground_color is not None:
cls._selected_menu_option_foreground_color = option_foreground_color
if option_background_color is not None:
cls._selected_menu_option_background_color = option_background_color
if cursor_foreground_color is not None:
cls._selected_menu_cursor_foreground_color = cursor_foreground_color
if cursor_background_color is not None:
cls._selected_menu_cursor_background_color = cursor_background_color
Console.set_foreground_color(header_foreground_color)
Console.set_background_color(header_background_color)
Console.write_line(message, "\n")
cls._show_select_menu()
from pynput import keyboard
with keyboard.Listener(on_press=cls._select_menu_key_press, suppress=False) as listener:
listener.join()
Console.color_reset()
return cls._select_menu_items[cls._selected_menu_item_index]
@classmethod
def spinner(
cls,
message: str,
call: Callable,
*args,
text_foreground_color: Union[str, ForegroundColorEnum] = None,
spinner_foreground_color: Union[str, ForegroundColorEnum] = None,
text_background_color: Union[str, BackgroundColorEnum] = None,
spinner_background_color: Union[str, BackgroundColorEnum] = None,
**kwargs,
) -> any:
r"""Shows spinner and calls given function, when function has ended the spinner stops
Parameter:
message: :class:`str`
Message of the spinner
call: :class:`Callable`
Function to call
args: :class:`list`
Arguments of the function
text_foreground_color: Union[:class:`str`, :class:`cpl.core.console.foreground_color_enum.ForegroundColorEnum`]
Foreground color of the text
spinner_foreground_color: Union[:class:`str`, :class:`cpl.core.console.foreground_color_enum.ForegroundColorEnum`]
Foreground color of the spinner
text_background_color: Union[:class:`str`, :class:`cpl.core.console.background_color_enum.BackgroundColorEnum`]
Background color of the text
spinner_background_color: Union[:class:`str`, :class:`cpl.core.console.background_color_enum.BackgroundColorEnum`]
Background color of the spinner
kwargs: :class:`dict`
Keyword arguments of the call
Returns:
Return value of call
"""
if cls._hold_back:
cls._hold_back_calls.append(ConsoleCall(cls.spinner, message, call, *args))
return
if text_foreground_color is not None:
cls.set_foreground_color(text_foreground_color)
if text_background_color is not None:
cls.set_background_color(text_background_color)
if type(spinner_foreground_color) is str:
spinner_foreground_color = ForegroundColorEnum[spinner_foreground_color]
if type(spinner_background_color) is str:
spinner_background_color = BackgroundColorEnum[spinner_background_color]
cls.write_line(message)
cls.set_hold_back(True)
spinner = None
if not cls._disabled:
spinner = SpinnerThread(len(message), spinner_foreground_color, spinner_background_color)
spinner.start()
return_value = None
try:
return_value = call(*args, **kwargs)
except KeyboardInterrupt:
if spinner is not None:
spinner.exit()
cls.close()
if spinner is not None:
spinner.stop_spinning()
cls.set_hold_back(False)
cls.set_foreground_color(ForegroundColorEnum.default)
cls.set_background_color(BackgroundColorEnum.default)
for call in cls._hold_back_calls:
call.function(*call.args)
cls._hold_back_calls = []
time.sleep(0.1)
return return_value
@classmethod
def write(cls, *args, end=""):
r"""Prints in active line
Parameter:
args: :class:`list`
Elements to print
end: :class:`str`
Last character to print
"""
if cls._disabled:
return
if cls._hold_back:
cls._hold_back_calls.append(ConsoleCall(cls.write, *args))
return
string = " ".join(map(str, args))
cls._output(string, end=end)
@classmethod
def write_at(cls, x: int, y: int, *args):
r"""Prints at given position
Parameter:
x: :class:`int`
X coordinate
y: :class:`int`
Y coordinate
args: :class:`list`
Elements to print
"""
if cls._disabled:
return
if cls._hold_back:
cls._hold_back_calls.append(ConsoleCall(cls.write_at, x, y, *args))
return
string = " ".join(map(str, args))
cls._output(string, x, y, end="")
@classmethod
def write_line(cls, *args):
r"""Prints to new line
Parameter:
args: :class:`list`
Elements to print
"""
if cls._disabled:
return
if cls._hold_back:
cls._hold_back_calls.append(ConsoleCall(cls.write_line, *args))
return
string = " ".join(map(str, args))
if not cls._is_first_write:
cls._output("")
cls._output(string, end="")
@classmethod
def write_line_at(cls, x: int, y: int, *args):
r"""Prints new line at given position
Parameter:
x: :class:`int`
X coordinate
y: :class:`int`
Y coordinate
args: :class:`list`
Elements to print
"""
if cls._disabled:
return
if cls._hold_back:
cls._hold_back_calls.append(ConsoleCall(cls.write_line_at, x, y, *args))
return
string = " ".join(map(str, args))
if not cls._is_first_write:
cls._output("", end="")
cls._output(string, x, y, end="")

View File

@@ -0,0 +1,24 @@
from collections.abc import Callable
class ConsoleCall:
r"""Represents a console call, for hold back when spinner is active
Parameter:
function: :class:`Callable`
Function to call
args: :class:`list`
List of arguments
"""
def __init__(self, function: Callable, *args):
self._func = function
self._args = args
@property
def function(self):
return self._func
@property
def args(self):
return self._args

View File

@@ -0,0 +1,13 @@
from enum import Enum
class ForegroundColorEnum(Enum):
default = "default"
grey = "grey"
red = "red"
green = "green"
yellow = "yellow"
blue = "blue"
magenta = "magenta"
cyan = "cyan"
white = "white"

View File

@@ -0,0 +1,96 @@
import os
import sys
import threading
import time
from termcolor import colored
from cpl.core.console.background_color_enum import BackgroundColorEnum
from cpl.core.console.foreground_color_enum import ForegroundColorEnum
class SpinnerThread(threading.Thread):
r"""Thread to show spinner in terminal
Parameter:
msg_len: :class:`int`
Length of the message
foreground_color: :class:`cpl.core.console.foreground_color.ForegroundColorEnum`
Foreground color of the spinner
background_color: :class:`cpl.core.console.background_color.BackgroundColorEnum`
Background color of the spinner
"""
def __init__(self, msg_len: int, foreground_color: ForegroundColorEnum, background_color: BackgroundColorEnum):
threading.Thread.__init__(self)
self._msg_len = msg_len
self._foreground_color = foreground_color
self._background_color = background_color
self._is_spinning = True
self._exit = False
@staticmethod
def _spinner():
r"""Selects active spinner char"""
while True:
for cursor in "|/-\\":
yield cursor
def _get_color_args(self) -> list[str]:
r"""Creates color arguments"""
color_args = []
if self._foreground_color is not None:
color_args.append(str(self._foreground_color.value))
if self._background_color is not None:
color_args.append(str(self._background_color.value))
return color_args
def run(self) -> None:
r"""Entry point of thread, shows the spinner"""
columns = 0
if sys.platform == "win32":
columns = os.get_terminal_size().columns
else:
term_rows, term_columns = os.popen("stty size", "r").read().split()
columns = int(term_columns)
end_msg = "done"
end_msg_pos = columns - self._msg_len - len(end_msg)
if end_msg_pos > 0:
print(f'{"" : >{end_msg_pos}}', end="")
else:
print("", end="")
first = True
spinner = self._spinner()
while self._is_spinning:
if first:
first = False
print(colored(f"{next(spinner): >{len(end_msg) - 1}}", *self._get_color_args()), end="")
else:
print(colored(f"{next(spinner): >{len(end_msg)}}", *self._get_color_args()), end="")
time.sleep(0.1)
back = ""
for i in range(0, len(end_msg)):
back += "\b"
print(back, end="")
sys.stdout.flush()
if not self._exit:
print(colored(end_msg, *self._get_color_args()), end="")
def stop_spinning(self):
r"""Stops the spinner"""
self._is_spinning = False
time.sleep(0.1)
def exit(self):
r"""Stops the spinner"""
self._is_spinning = False
self._exit = True
time.sleep(0.1)

View File

@@ -0,0 +1,3 @@
from .database_settings_name_enum import DatabaseSettingsNameEnum
from .database_settings import DatabaseSettings
from .table_abc import TableABC

View File

@@ -0,0 +1,2 @@
from .database_connection import DatabaseConnection
from .database_connection_abc import DatabaseConnectionABC

View File

@@ -0,0 +1,54 @@
from typing import Optional
import mysql.connector as sql
from mysql.connector.abstracts import MySQLConnectionAbstract
from mysql.connector.cursor import MySQLCursorBuffered
from cpl.core.database.connection.database_connection_abc import DatabaseConnectionABC
from cpl.core.database.database_settings import DatabaseSettings
from cpl.core.utils.credential_manager import CredentialManager
class DatabaseConnection(DatabaseConnectionABC):
r"""Representation of the database connection"""
def __init__(self):
DatabaseConnectionABC.__init__(self)
self._database: Optional[MySQLConnectionAbstract] = None
self._cursor: Optional[MySQLCursorBuffered] = None
@property
def server(self) -> MySQLConnectionAbstract:
return self._database
@property
def cursor(self) -> MySQLCursorBuffered:
return self._cursor
def connect(self, settings: DatabaseSettings):
connection = sql.connect(
host=settings.host,
port=settings.port,
user=settings.user,
passwd=CredentialManager.decrypt(settings.password),
charset=settings.charset,
use_unicode=settings.use_unicode,
buffered=settings.buffered,
auth_plugin=settings.auth_plugin,
ssl_disabled=settings.ssl_disabled,
)
connection.cursor().execute(f"CREATE DATABASE IF NOT EXISTS `{settings.database}`;")
self._database = sql.connect(
host=settings.host,
port=settings.port,
user=settings.user,
passwd=CredentialManager.decrypt(settings.password),
db=settings.database,
charset=settings.charset,
use_unicode=settings.use_unicode,
buffered=settings.buffered,
auth_plugin=settings.auth_plugin,
ssl_disabled=settings.ssl_disabled,
)
self._cursor = self._database.cursor()

View File

@@ -0,0 +1,32 @@
from abc import ABC, abstractmethod
from cpl.core.database.database_settings import DatabaseSettings
from mysql.connector.abstracts import MySQLConnectionAbstract
from mysql.connector.cursor import MySQLCursorBuffered
class DatabaseConnectionABC(ABC):
r"""ABC for the :class:`cpl.core.database.connection.database_connection.DatabaseConnection`"""
@abstractmethod
def __init__(self):
pass
@property
@abstractmethod
def server(self) -> MySQLConnectionAbstract:
pass
@property
@abstractmethod
def cursor(self) -> MySQLCursorBuffered:
pass
@abstractmethod
def connect(self, database_settings: DatabaseSettings):
r"""Connects to a database by connection string
Parameter:
connection_string: :class:`str`
Database connection string, see: https://docs.sqlalchemy.org/en/14/core/engines.html
"""

View File

@@ -0,0 +1,2 @@
from .database_context import DatabaseContext
from .database_context_abc import DatabaseContextABC

View File

@@ -0,0 +1,52 @@
from typing import Optional
from cpl.core.database.connection.database_connection import DatabaseConnection
from cpl.core.database.connection.database_connection_abc import DatabaseConnectionABC
from cpl.core.database.context.database_context_abc import DatabaseContextABC
from cpl.core.database.database_settings import DatabaseSettings
from mysql.connector.cursor import MySQLCursorBuffered
class DatabaseContext(DatabaseContextABC):
r"""Representation of the database context
Parameter:
database_settings: :class:`cpl.core.database.database_settings.DatabaseSettings`
"""
def __init__(self):
DatabaseContextABC.__init__(self)
self._db: DatabaseConnectionABC = DatabaseConnection()
self._settings: Optional[DatabaseSettings] = None
@property
def cursor(self) -> MySQLCursorBuffered:
self._ping_and_reconnect()
return self._db.cursor
def _ping_and_reconnect(self):
try:
self._db.server.ping(reconnect=True, attempts=3, delay=5)
except Exception:
# reconnect your cursor as you did in __init__ or wherever
if self._settings is None:
raise Exception("Call DatabaseContext.connect first")
self.connect(self._settings)
def connect(self, database_settings: DatabaseSettings):
if self._settings is None:
self._settings = database_settings
self._db.connect(database_settings)
self.save_changes()
def save_changes(self):
self._ping_and_reconnect()
self._db.server.commit()
def select(self, statement: str) -> list[tuple]:
self._ping_and_reconnect()
self._db.cursor.execute(statement)
return self._db.cursor.fetchall()

View File

@@ -0,0 +1,40 @@
from abc import ABC, abstractmethod
from cpl.core.database.database_settings import DatabaseSettings
from mysql.connector.cursor import MySQLCursorBuffered
class DatabaseContextABC(ABC):
r"""ABC for the :class:`cpl.core.database.context.database_context.DatabaseContext`"""
@abstractmethod
def __init__(self, *args):
pass
@property
@abstractmethod
def cursor(self) -> MySQLCursorBuffered:
pass
@abstractmethod
def connect(self, database_settings: DatabaseSettings):
r"""Connects to a database by connection settings
Parameter:
database_settings :class:`cpl.core.database.database_settings.DatabaseSettings`
"""
@abstractmethod
def save_changes(self):
r"""Saves changes of the database"""
@abstractmethod
def select(self, statement: str) -> list[tuple]:
r"""Runs SQL Statements
Parameter:
statement: :class:`str`
Returns:
list: Fetched list of selected elements
"""

View File

@@ -0,0 +1,73 @@
from typing import Optional
from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC
class DatabaseSettings(ConfigurationModelABC):
r"""Represents settings for the database connection"""
def __init__(
self,
host: str = None,
port: int = 3306,
user: str = None,
password: str = None,
database: str = None,
charset: str = "utf8mb4",
use_unicode: bool = False,
buffered: bool = False,
auth_plugin: str = "caching_sha2_password",
ssl_disabled: bool = False,
):
ConfigurationModelABC.__init__(self)
self._host: Optional[str] = host
self._port: Optional[int] = port
self._user: Optional[str] = user
self._password: Optional[str] = password
self._database: Optional[str] = database
self._charset: Optional[str] = charset
self._use_unicode: Optional[bool] = use_unicode
self._buffered: Optional[bool] = buffered
self._auth_plugin: Optional[str] = auth_plugin
self._ssl_disabled: Optional[bool] = ssl_disabled
@property
def host(self) -> Optional[str]:
return self._host
@property
def port(self) -> Optional[int]:
return self._port
@property
def user(self) -> Optional[str]:
return self._user
@property
def password(self) -> Optional[str]:
return self._password
@property
def database(self) -> Optional[str]:
return self._database
@property
def charset(self) -> Optional[str]:
return self._charset
@property
def use_unicode(self) -> Optional[bool]:
return self._use_unicode
@property
def buffered(self) -> Optional[bool]:
return self._buffered
@property
def auth_plugin(self) -> Optional[str]:
return self._auth_plugin
@property
def ssl_disabled(self) -> Optional[bool]:
return self._ssl_disabled

View File

@@ -0,0 +1,13 @@
from enum import Enum
class DatabaseSettingsNameEnum(Enum):
host = "Host"
port = "Port"
user = "User"
password = "Password"
database = "Database"
charset = "Charset"
use_unicode = "UseUnicode"
buffered = "Buffered"
auth_plugin = "AuthPlugin"

View File

@@ -0,0 +1,37 @@
from abc import ABC, abstractmethod
from datetime import datetime
from typing import Optional
class TableABC(ABC):
@abstractmethod
def __init__(self):
self._created_at: Optional[datetime] = datetime.now().isoformat()
self._modified_at: Optional[datetime] = datetime.now().isoformat()
@property
def created_at(self) -> datetime:
return self._created_at
@property
def modified_at(self) -> datetime:
return self._modified_at
@modified_at.setter
def modified_at(self, value: datetime):
self._modified_at = value
@property
@abstractmethod
def insert_string(self) -> str:
pass
@property
@abstractmethod
def udpate_string(self) -> str:
pass
@property
@abstractmethod
def delete_string(self) -> str:
pass

View File

@@ -0,0 +1,8 @@
from .scope import Scope
from .scope_abc import ScopeABC
from .service_collection import ServiceCollection
from .service_collection_abc import ServiceCollectionABC
from .service_descriptor import ServiceDescriptor
from .service_lifetime_enum import ServiceLifetimeEnum
from .service_provider import ServiceProvider
from .service_provider_abc import ServiceProviderABC

View File

@@ -0,0 +1,22 @@
from cpl.core.dependency_injection.scope_abc import ScopeABC
from cpl.core.dependency_injection.service_provider_abc import ServiceProviderABC
class Scope(ScopeABC):
def __init__(self, service_provider: ServiceProviderABC):
self._service_provider = service_provider
self._service_provider.set_scope(self)
ScopeABC.__init__(self)
def __enter__(self):
return self
def __exit__(self, *args):
self.dispose()
@property
def service_provider(self) -> ServiceProviderABC:
return self._service_provider
def dispose(self):
self._service_provider = None

View File

@@ -0,0 +1,21 @@
from abc import ABC, abstractmethod
class ScopeABC(ABC):
r"""ABC for the class :class:`cpl.core.dependency_injection.scope.Scope`"""
def __init__(self):
pass
@property
@abstractmethod
def service_provider(self):
r"""Returns to service provider of scope
Returns:
Object of type :class:`cpl.core.dependency_injection.service_provider_abc.ServiceProviderABC`
"""
@abstractmethod
def dispose(self):
r"""Sets service_provider to None"""

View File

@@ -0,0 +1,18 @@
from cpl.core.dependency_injection.scope import Scope
from cpl.core.dependency_injection.scope_abc import ScopeABC
from cpl.core.dependency_injection.service_provider_abc import ServiceProviderABC
class ScopeBuilder:
r"""Class to build :class:`cpl.core.dependency_injection.scope.Scope`"""
def __init__(self, service_provider: ServiceProviderABC) -> None:
self._service_provider = service_provider
def build(self) -> ScopeABC:
r"""Returns scope
Returns:
Object of type :class:`cpl.core.dependency_injection.scope.Scope`
"""
return Scope(self._service_provider)

View File

@@ -0,0 +1,79 @@
from typing import Union, Type, Callable, Optional
from cpl.core.configuration.configuration_abc import ConfigurationABC
from cpl.core.database.context.database_context_abc import DatabaseContextABC
from cpl.core.database.database_settings import DatabaseSettings
from cpl.core.dependency_injection.service_collection_abc import ServiceCollectionABC
from cpl.core.dependency_injection.service_descriptor import ServiceDescriptor
from cpl.core.dependency_injection.service_lifetime_enum import ServiceLifetimeEnum
from cpl.core.dependency_injection.service_provider import ServiceProvider
from cpl.core.dependency_injection.service_provider_abc import ServiceProviderABC
from cpl.core.log.logger_abc import LoggerABC
from cpl.core.log.logger_service import Logger
from cpl.core.pipes.pipe_abc import PipeABC
from cpl.core.type import T
class ServiceCollection(ServiceCollectionABC):
r"""Representation of the collection of services"""
def __init__(self, config: ConfigurationABC):
ServiceCollectionABC.__init__(self)
self._configuration: ConfigurationABC = config
self._database_context: Optional[DatabaseContextABC] = None
self._service_descriptors: list[ServiceDescriptor] = []
def _add_descriptor(self, service: Union[type, object], lifetime: ServiceLifetimeEnum, base_type: Callable = None):
found = False
for descriptor in self._service_descriptors:
if isinstance(service, descriptor.service_type):
found = True
if found:
service_type = service
if not isinstance(service, type):
service_type = type(service)
raise Exception(f"Service of type {service_type} already exists")
self._service_descriptors.append(ServiceDescriptor(service, lifetime, base_type))
def _add_descriptor_by_lifetime(self, service_type: Type, lifetime: ServiceLifetimeEnum, service: Callable = None):
if service is not None:
self._add_descriptor(service, lifetime, service_type)
else:
self._add_descriptor(service_type, lifetime)
return self
def add_db_context(self, db_context_type: Type[DatabaseContextABC], db_settings: DatabaseSettings):
self.add_singleton(DatabaseContextABC, db_context_type)
self._database_context = self.build_service_provider().get_service(DatabaseContextABC)
self._database_context.connect(db_settings)
def add_logging(self):
self.add_singleton(LoggerABC, Logger)
return self
def add_pipes(self):
for pipe in PipeABC.__subclasses__():
self.add_transient(PipeABC, pipe)
return self
def add_singleton(self, service_type: T, service: T = None):
self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.singleton, service)
return self
def add_scoped(self, service_type: T, service: T = None):
self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.scoped, service)
return self
def add_transient(self, service_type: T, service: T = None):
self._add_descriptor_by_lifetime(service_type, ServiceLifetimeEnum.transient, service)
return self
def build_service_provider(self) -> ServiceProviderABC:
sp = ServiceProvider(self._service_descriptors, self._configuration, self._database_context)
ServiceProviderABC.set_global_provider(sp)
return sp

View File

@@ -0,0 +1,90 @@
from abc import abstractmethod, ABC
from typing import Type
from cpl.core.database.database_settings import DatabaseSettings
from cpl.core.database.context.database_context_abc import DatabaseContextABC
from cpl.core.dependency_injection.service_provider_abc import ServiceProviderABC
from cpl.core.type import T
class ServiceCollectionABC(ABC):
r"""ABC for the class :class:`cpl.core.dependency_injection.service_collection.ServiceCollection`"""
@abstractmethod
def __init__(self):
pass
@abstractmethod
def add_db_context(self, db_context_type: Type[DatabaseContextABC], db_settings: DatabaseSettings):
r"""Adds database context
Parameter:
db_context: Type[:class:`cpl.core.database.context.database_context_abc.DatabaseContextABC`]
Database context
"""
@abstractmethod
def add_logging(self):
r"""Adds the CPL internal logger"""
@abstractmethod
def add_pipes(self):
r"""Adds the CPL internal pipes as transient"""
def add_discord(self):
r"""Adds the CPL discord"""
raise NotImplementedError("You should install and use the cpl-discord package")
def add_translation(self):
r"""Adds the CPL translation"""
raise NotImplementedError("You should install and use the cpl-translation package")
@abstractmethod
def add_transient(self, service_type: T, service: T = None) -> "ServiceCollectionABC":
r"""Adds a service with transient lifetime
Parameter:
service_type: :class:`Type`
Type of the service
service: :class:`Callable`
Object of the service
Returns:
self: :class:`cpl.core.dependency_injection.service_collection_abc.ServiceCollectionABC`
"""
@abstractmethod
def add_scoped(self, service_type: T, service: T = None) -> "ServiceCollectionABC":
r"""Adds a service with scoped lifetime
Parameter:
service_type: :class:`Type`
Type of the service
service: :class:`Callable`
Object of the service
Returns:
self: :class:`cpl.core.dependency_injection.service_collection_abc.ServiceCollectionABC`
"""
@abstractmethod
def add_singleton(self, service_type: T, service: T = None) -> "ServiceCollectionABC":
r"""Adds a service with singleton lifetime
Parameter:
service_type: :class:`Type`
Type of the service
service: :class:`Callable`
Object of the service
Returns:
self: :class:`cpl.core.dependency_injection.service_collection_abc.ServiceCollectionABC`
"""
@abstractmethod
def build_service_provider(self) -> ServiceProviderABC:
r"""Creates instance of the service provider
Returns:
Object of type :class:`cpl.core.dependency_injection.service_provider_abc.ServiceProviderABC`
"""

View File

@@ -0,0 +1,46 @@
from typing import Union, Optional
from cpl.core.dependency_injection.service_lifetime_enum import ServiceLifetimeEnum
class ServiceDescriptor:
r"""Descriptor of a service
Parameter:
implementation: Union[:class:`type`, Optional[:class:`object`]]
Object or type of service
lifetime: :class:`cpl.core.dependency_injection.service_lifetime_enum.ServiceLifetimeEnum`
Lifetime of the service
"""
def __init__(self, implementation: Union[type, Optional[object]], lifetime: ServiceLifetimeEnum, base_type=None):
self._service_type = implementation
self._implementation = implementation
self._lifetime = lifetime
if not isinstance(implementation, type):
self._service_type = type(implementation)
else:
self._implementation = None
self._base_type = base_type if base_type is not None else self._service_type
@property
def service_type(self) -> type:
return self._service_type
@property
def base_type(self) -> type:
return self._base_type
@property
def implementation(self) -> Union[type, Optional[object]]:
return self._implementation
@implementation.setter
def implementation(self, implementation: Union[type, Optional[object]]):
self._implementation = implementation
@property
def lifetime(self) -> ServiceLifetimeEnum:
return self._lifetime

View File

@@ -0,0 +1,7 @@
from enum import Enum
class ServiceLifetimeEnum(Enum):
singleton = 0
scoped = 1
transient = 2

View File

@@ -0,0 +1,168 @@
import copy
import typing
from inspect import signature, Parameter, Signature
from typing import Optional
from cpl.core.configuration.configuration_abc import ConfigurationABC
from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl.core.database.context.database_context_abc import DatabaseContextABC
from cpl.core.dependency_injection.scope_abc import ScopeABC
from cpl.core.dependency_injection.scope_builder import ScopeBuilder
from cpl.core.dependency_injection.service_descriptor import ServiceDescriptor
from cpl.core.dependency_injection.service_lifetime_enum import ServiceLifetimeEnum
from cpl.core.dependency_injection.service_provider_abc import ServiceProviderABC
from cpl.core.environment.application_environment_abc import ApplicationEnvironmentABC
from cpl.core.type import T, R
class ServiceProvider(ServiceProviderABC):
r"""Provider for the services
Parameter
---------
service_descriptors: list[:class:`cpl.core.dependency_injection.service_descriptor.ServiceDescriptor`]
Descriptor of the service
config: :class:`cpl.core.configuration.configuration_abc.ConfigurationABC`
CPL Configuration
db_context: Optional[:class:`cpl.core.database.context.database_context_abc.DatabaseContextABC`]
Database representation
"""
def __init__(
self,
service_descriptors: list[ServiceDescriptor],
config: ConfigurationABC,
db_context: Optional[DatabaseContextABC],
):
ServiceProviderABC.__init__(self)
self._service_descriptors: list[ServiceDescriptor] = service_descriptors
self._configuration: ConfigurationABC = config
self._database_context = db_context
self._scope: Optional[ScopeABC] = None
def _find_service(self, service_type: type) -> Optional[ServiceDescriptor]:
for descriptor in self._service_descriptors:
if descriptor.service_type == service_type or issubclass(descriptor.base_type, service_type):
return descriptor
return None
def _get_service(self, parameter: Parameter) -> Optional[object]:
for descriptor in self._service_descriptors:
if descriptor.service_type == parameter.annotation or issubclass(
descriptor.service_type, parameter.annotation
):
if descriptor.implementation is not None:
return descriptor.implementation
implementation = self.build_service(descriptor.service_type)
if descriptor.lifetime == ServiceLifetimeEnum.singleton:
descriptor.implementation = implementation
return implementation
# raise Exception(f'Service {parameter.annotation} not found')
def _get_services(self, t: type, *args, **kwargs) -> list[Optional[object]]:
implementations = []
for descriptor in self._service_descriptors:
if descriptor.service_type == t or issubclass(descriptor.service_type, t):
if descriptor.implementation is not None:
implementations.append(descriptor.implementation)
continue
implementation = self.build_service(descriptor.service_type, *args, **kwargs)
if descriptor.lifetime == ServiceLifetimeEnum.singleton:
descriptor.implementation = implementation
implementations.append(implementation)
return implementations
def build_by_signature(self, sig: Signature) -> list[R]:
params = []
for param in sig.parameters.items():
parameter = param[1]
if parameter.name != "self" and parameter.annotation != Parameter.empty:
if typing.get_origin(parameter.annotation) == list:
params.append(self._get_services(typing.get_args(parameter.annotation)[0]))
elif issubclass(parameter.annotation, ServiceProviderABC):
params.append(self)
elif issubclass(parameter.annotation, ApplicationEnvironmentABC):
params.append(self._configuration.environment)
elif issubclass(parameter.annotation, DatabaseContextABC):
params.append(self._database_context)
elif issubclass(parameter.annotation, ConfigurationModelABC):
params.append(self._configuration.get_configuration(parameter.annotation))
elif issubclass(parameter.annotation, ConfigurationABC):
params.append(self._configuration)
else:
params.append(self._get_service(parameter))
return params
def build_service(self, service_type: type, *args, **kwargs) -> object:
for descriptor in self._service_descriptors:
if descriptor.service_type == service_type or issubclass(descriptor.service_type, service_type):
if descriptor.implementation is not None:
service_type = type(descriptor.implementation)
else:
service_type = descriptor.service_type
break
sig = signature(service_type.__init__)
params = self.build_by_signature(sig)
return service_type(*params, *args, **kwargs)
def set_scope(self, scope: ScopeABC):
self._scope = scope
def create_scope(self) -> ScopeABC:
descriptors = []
for descriptor in self._service_descriptors:
if descriptor.lifetime == ServiceLifetimeEnum.singleton:
descriptors.append(descriptor)
else:
descriptors.append(copy.deepcopy(descriptor))
sb = ScopeBuilder(ServiceProvider(descriptors, self._configuration, self._database_context))
return sb.build()
def get_service(self, service_type: T, *args, **kwargs) -> Optional[R]:
result = self._find_service(service_type)
if result is None:
return None
if result.implementation is not None:
return result.implementation
implementation = self.build_service(service_type, *args, **kwargs)
if (
result.lifetime == ServiceLifetimeEnum.singleton
or result.lifetime == ServiceLifetimeEnum.scoped
and self._scope is not None
):
result.implementation = implementation
return implementation
def get_services(self, service_type: T, *args, **kwargs) -> list[Optional[R]]:
implementations = []
if typing.get_origin(service_type) == list:
raise Exception(f"Invalid type {service_type}! Expected single type not list of type")
implementations.extend(self._get_services(service_type))
return implementations

View File

@@ -0,0 +1,111 @@
import functools
from abc import abstractmethod, ABC
from inspect import Signature, signature
from typing import Optional
from cpl.core.dependency_injection.scope_abc import ScopeABC
from cpl.core.type import T, R
class ServiceProviderABC(ABC):
r"""ABC for the class :class:`cpl.core.dependency_injection.service_provider.ServiceProvider`"""
_provider: Optional["ServiceProviderABC"] = None
@abstractmethod
def __init__(self):
pass
@classmethod
def set_global_provider(cls, provider: "ServiceProviderABC"):
cls._provider = provider
@abstractmethod
def build_by_signature(self, sig: Signature) -> list[R]:
pass
@abstractmethod
def build_service(self, service_type: type, *args, **kwargs) -> object:
r"""Creates instance of given type
Parameter
---------
instance_type: :class:`type`
The type of the searched instance
Returns
-------
Object of the given type
"""
@abstractmethod
def set_scope(self, scope: ScopeABC):
r"""Sets the scope of service provider
Parameter
---------
Object of type :class:`cpl.core.dependency_injection.scope_abc.ScopeABC`
Service scope
"""
@abstractmethod
def create_scope(self) -> ScopeABC:
r"""Creates a service scope
Returns
-------
Object of type :class:`cpl.core.dependency_injection.scope_abc.ScopeABC`
"""
@abstractmethod
def get_service(self, instance_type: T, *args, **kwargs) -> Optional[R]:
r"""Returns instance of given type
Parameter
---------
instance_type: :class:`cpl.core.type.T`
The type of the searched instance
Returns
-------
Object of type Optional[:class:`cpl.core.type.T`]
"""
@abstractmethod
def get_services(self, service_type: T, *args, **kwargs) -> list[Optional[R]]:
r"""Returns instance of given type
Parameter
---------
service_type: :class:`cpl.core.type.T`
The type of the searched instance
Returns
-------
Object of type list[Optional[:class:`cpl.core.type.T`]
"""
@classmethod
def inject(cls, f=None):
r"""Decorator to allow injection into static and class methods
Parameter
---------
f: Callable
Returns
-------
function
"""
if f is None:
return functools.partial(cls.inject)
@functools.wraps(f)
def inner(*args, **kwargs):
if cls._provider is None:
raise Exception(f"{cls.__name__} not build!")
injection = [x for x in cls._provider.build_by_signature(signature(f)) if x is not None]
return f(*args, *injection, **kwargs)
return inner

View File

@@ -0,0 +1,3 @@
from .application_environment_abc import ApplicationEnvironmentABC
from .environment_name_enum import EnvironmentNameEnum
from .application_environment import ApplicationEnvironment

View File

@@ -0,0 +1,95 @@
import os
from datetime import datetime
from socket import gethostname
from typing import Optional
from cpl.core.environment.application_environment_abc import ApplicationEnvironmentABC
from cpl.core.environment.environment_name_enum import EnvironmentNameEnum
class ApplicationEnvironment(ApplicationEnvironmentABC):
r"""Represents environment of the application
Parameter:
name: :class:`cpl.core.environment.environment_name_enum.EnvironmentNameEnum`
"""
def __init__(self, name: EnvironmentNameEnum = EnvironmentNameEnum.production):
ApplicationEnvironmentABC.__init__(self)
self._environment_name: Optional[EnvironmentNameEnum] = name
self._app_name: Optional[str] = None
self._customer: Optional[str] = None
self._start_time: datetime = datetime.now()
self._end_time: datetime = datetime.now()
self._runtime_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
self._working_directory = os.getcwd()
@property
def environment_name(self) -> str:
return str(self._environment_name.value)
@environment_name.setter
def environment_name(self, environment_name: str):
self._environment_name = EnvironmentNameEnum(environment_name)
@property
def application_name(self) -> str:
return self._app_name if self._app_name is not None else ""
@application_name.setter
def application_name(self, application_name: str):
self._app_name = application_name
@property
def customer(self) -> str:
return self._customer if self._customer is not None else ""
@customer.setter
def customer(self, customer: str):
self._customer = customer
@property
def host_name(self):
return gethostname()
@property
def start_time(self) -> datetime:
return self._start_time
@property
def end_time(self) -> datetime:
return self._end_time
@end_time.setter
def end_time(self, end_time: datetime):
self._end_time = end_time
@property
def date_time_now(self) -> datetime:
return datetime.now()
@property
def working_directory(self) -> str:
return str(self._working_directory)
@property
def runtime_directory(self) -> str:
return str(self._runtime_directory)
def set_runtime_directory(self, runtime_directory: str):
if runtime_directory != "":
self._runtime_directory = runtime_directory
return
self._runtime_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def set_working_directory(self, working_directory: str):
if working_directory != "":
self._working_directory = working_directory
os.chdir(self._working_directory)
return
self._working_directory = os.path.abspath("./")
os.chdir(self._working_directory)

View File

@@ -0,0 +1,98 @@
from abc import ABC, abstractmethod
from datetime import datetime
class ApplicationEnvironmentABC(ABC):
r"""ABC of the class :class:`cpl.core.environment.application_environment.ApplicationEnvironment`"""
@abstractmethod
def __init__(self):
pass
@property
@abstractmethod
def environment_name(self) -> str:
pass
@environment_name.setter
@abstractmethod
def environment_name(self, environment_name: str):
pass
@property
@abstractmethod
def application_name(self) -> str:
pass
@application_name.setter
@abstractmethod
def application_name(self, application_name: str):
pass
@property
@abstractmethod
def customer(self) -> str:
pass
@customer.setter
@abstractmethod
def customer(self, customer: str):
pass
@property
@abstractmethod
def host_name(self) -> str:
pass
@property
@abstractmethod
def start_time(self) -> datetime:
pass
@start_time.setter
@abstractmethod
def start_time(self, start_time: datetime):
pass
@property
@abstractmethod
def end_time(self):
pass
@end_time.setter
@abstractmethod
def end_time(self, end_time: datetime):
pass
@property
@abstractmethod
def date_time_now(self) -> datetime:
pass
@property
@abstractmethod
def working_directory(self) -> str:
pass
@property
@abstractmethod
def runtime_directory(self) -> str:
pass
@abstractmethod
def set_runtime_directory(self, runtime_directory: str):
r"""Sets the current runtime directory
Parameter:
runtime_directory: :class:`str`
Path of the runtime directory
"""
@abstractmethod
def set_working_directory(self, working_directory: str):
r"""Sets the current working directory
Parameter:
working_directory: :class:`str`
Path of the current working directory
"""

View File

@@ -0,0 +1,8 @@
from enum import Enum
class EnvironmentNameEnum(Enum):
production = "production"
staging = "staging"
testing = "testing"
development = "development"

View File

@@ -0,0 +1,5 @@
from .logger_service import Logger
from .logger_abc import LoggerABC
from .logging_level_enum import LoggingLevelEnum
from .logging_settings import LoggingSettings
from .logging_settings_name_enum import LoggingSettingsNameEnum

View File

@@ -0,0 +1,88 @@
from abc import abstractmethod, ABC
class LoggerABC(ABC):
r"""ABC for :class:`cpl.core.log.logger_service.Logger`"""
@abstractmethod
def __init__(self):
ABC.__init__(self)
@abstractmethod
def header(self, string: str):
r"""Writes a header message
Parameter:
string: :class:`str`
String to write as header
"""
@abstractmethod
def trace(self, name: str, message: str):
r"""Writes a trace message
Parameter:
name: :class:`str`
Message name
message: :class:`str`
Message string
"""
@abstractmethod
def debug(self, name: str, message: str):
r"""Writes a debug message
Parameter:
name: :class:`str`
Message name
message: :class:`str`
Message string
"""
@abstractmethod
def info(self, name: str, message: str):
r"""Writes an information
Parameter:
name: :class:`str`
Message name
message: :class:`str`
Message string
"""
@abstractmethod
def warn(self, name: str, message: str):
r"""Writes an warning
Parameter:
name: :class:`str`
Message name
message: :class:`str`
Message string
"""
@abstractmethod
def error(self, name: str, message: str, ex: Exception = None):
r"""Writes an error
Parameter:
name: :class:`str`
Error name
message: :class:`str`
Error message
ex: :class:`Exception`
Thrown exception
"""
@abstractmethod
def fatal(self, name: str, message: str, ex: Exception = None):
r"""Writes an error and ends the program
Parameter:
name: :class:`str`
Error name
message: :class:`str`
Error message
ex: :class:`Exception`
Thrown exception
"""

View File

@@ -0,0 +1,291 @@
import datetime
import os
import sys
import traceback
from string import Template
from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl.core.console.console import Console
from cpl.core.console.foreground_color_enum import ForegroundColorEnum
from cpl.core.environment.application_environment_abc import ApplicationEnvironmentABC
from cpl.core.log.logger_abc import LoggerABC
from cpl.core.log.logging_level_enum import LoggingLevelEnum
from cpl.core.log.logging_settings import LoggingSettings
from cpl.core.time.time_format_settings import TimeFormatSettings
class Logger(LoggerABC):
r"""Service for logging
Parameter:
logging_settings: :class:`cpl.core.log.logging_settings.LoggingSettings`
Settings for the logger
time_format: :class:`cpl.core.time.time_format_settings.TimeFormatSettings`
Time format settings
env: :class:`cpl.core.environment.application_environment_abc.ApplicationEnvironmentABC`
Environment of the application
"""
def __init__(
self, logging_settings: LoggingSettings, time_format: TimeFormatSettings, env: ApplicationEnvironmentABC
):
LoggerABC.__init__(self)
self._env = env
self._log_settings: LoggingSettings = logging_settings
self._time_format_settings: TimeFormatSettings = time_format
self._check_for_settings(self._time_format_settings, TimeFormatSettings)
self._check_for_settings(self._log_settings, LoggingSettings)
self._level = self._log_settings.level
self._console = self._log_settings.console
self.create()
@property
def _log(self) -> str:
return Template(self._log_settings.filename).substitute(
date_time_now=self._env.date_time_now.strftime(self._time_format_settings.date_time_format),
date_now=self._env.date_time_now.strftime(self._time_format_settings.date_format),
time_now=self._env.date_time_now.strftime(self._time_format_settings.time_format),
start_time=self._env.start_time.strftime(self._time_format_settings.date_time_log_format),
)
@property
def _path(self) -> str:
return Template(self._log_settings.path).substitute(
date_time_now=self._env.date_time_now.strftime(self._time_format_settings.date_time_format),
date_now=self._env.date_time_now.strftime(self._time_format_settings.date_format),
time_now=self._env.date_time_now.strftime(self._time_format_settings.time_format),
start_time=self._env.start_time.strftime(self._time_format_settings.date_time_log_format),
)
def _check_for_settings(self, settings: ConfigurationModelABC, settings_type: type):
self._level = LoggingLevelEnum.OFF
self._console = LoggingLevelEnum.FATAL
if settings is None:
self.fatal(__name__, f"Configuration for {settings_type} not found")
def _get_datetime_now(self) -> str:
r"""Returns the date and time by given format
Returns:
Date and time in given format
"""
try:
return datetime.datetime.now().strftime(self._time_format_settings.date_time_format)
except Exception as e:
self.error(__name__, "Cannot get time", ex=e)
def _get_date(self) -> str:
r"""Returns the date by given format
Returns:
Date in given format
"""
try:
return datetime.datetime.now().strftime(self._time_format_settings.date_format)
except Exception as e:
self.error(__name__, "Cannot get date", ex=e)
def create(self) -> None:
r"""Creates path tree and logfile"""
""" path """
try:
# check if log file path exists
if not os.path.exists(self._path):
os.makedirs(self._path)
except Exception as e:
self._fatal_console(__name__, "Cannot create log dir", ex=e)
""" create new log file """
try:
# open log file, create if not exists
path = f"{self._path}{self._log}"
permission = "a+"
if not os.path.isfile(path):
permission = "w+"
f = open(path, permission)
Console.write_line(f"[{__name__}]: Using log file: {path}")
f.close()
except Exception as e:
self._fatal_console(__name__, "Cannot open log file", ex=e)
def _append_log(self, string: str):
r"""Writes to logfile
Parameter:
string: :class:`str`
"""
try:
# open log file and append always
if not os.path.isdir(self._path):
self._warn_console(__name__, "Log directory not found, try to recreate logger")
self.create()
with open(self._path + self._log, "a+", encoding="utf-8") as f:
f.write(string + "\n")
f.close()
except Exception as e:
self._fatal_console(__name__, f"Cannot append log file, message: {string}", ex=e)
def _get_string(self, name: str, level: LoggingLevelEnum, message: str) -> str:
r"""Returns input as log entry format
Parameter:
name: :class:`str`
Name of the message
level: :class:`cpl.core.log.logging_level_enum.LoggingLevelEnum`
Logging level
message: :class:`str`
Log message
Returns:
Formatted string for logging
"""
log_level = level.name
return f"<{self._get_datetime_now()}> [ {log_level} ] [ {name} ]: {message}"
def header(self, string: str):
# append log and print message
self._append_log(string)
Console.set_foreground_color(ForegroundColorEnum.default)
Console.write_line(string)
Console.set_foreground_color(ForegroundColorEnum.default)
def trace(self, name: str, message: str):
output = self._get_string(name, LoggingLevelEnum.TRACE, message)
# check if message can be written to log
if self._level.value >= LoggingLevelEnum.TRACE.value:
self._append_log(output)
# check if message can be shown in console
if self._console.value >= LoggingLevelEnum.TRACE.value:
Console.set_foreground_color(ForegroundColorEnum.grey)
Console.write_line(output)
Console.set_foreground_color(ForegroundColorEnum.default)
def debug(self, name: str, message: str):
output = self._get_string(name, LoggingLevelEnum.DEBUG, message)
# check if message can be written to log
if self._level.value >= LoggingLevelEnum.DEBUG.value:
self._append_log(output)
# check if message can be shown in console
if self._console.value >= LoggingLevelEnum.DEBUG.value:
Console.set_foreground_color(ForegroundColorEnum.blue)
Console.write_line(output)
Console.set_foreground_color(ForegroundColorEnum.default)
def info(self, name: str, message: str):
output = self._get_string(name, LoggingLevelEnum.INFO, message)
# check if message can be written to log
if self._level.value >= LoggingLevelEnum.INFO.value:
self._append_log(output)
# check if message can be shown in console
if self._console.value >= LoggingLevelEnum.INFO.value:
Console.set_foreground_color(ForegroundColorEnum.green)
Console.write_line(output)
Console.set_foreground_color(ForegroundColorEnum.default)
def warn(self, name: str, message: str):
output = self._get_string(name, LoggingLevelEnum.WARN, message)
# check if message can be written to log
if self._level.value >= LoggingLevelEnum.WARN.value:
self._append_log(output)
# check if message can be shown in console
if self._console.value >= LoggingLevelEnum.WARN.value:
Console.set_foreground_color(ForegroundColorEnum.yellow)
Console.write_line(output)
Console.set_foreground_color(ForegroundColorEnum.default)
def error(self, name: str, message: str, ex: Exception = None):
output = ""
if ex is not None:
tb = traceback.format_exc()
self.error(name, message)
output = self._get_string(name, LoggingLevelEnum.ERROR, f"{ex} -> {tb}")
else:
output = self._get_string(name, LoggingLevelEnum.ERROR, message)
# check if message can be written to log
if self._level.value >= LoggingLevelEnum.ERROR.value:
self._append_log(output)
# check if message can be shown in console
if self._console.value >= LoggingLevelEnum.ERROR.value:
Console.set_foreground_color(ForegroundColorEnum.red)
Console.write_line(output)
Console.set_foreground_color(ForegroundColorEnum.default)
def fatal(self, name: str, message: str, ex: Exception = None):
output = ""
if ex is not None:
tb = traceback.format_exc()
self.error(name, message)
output = self._get_string(name, LoggingLevelEnum.FATAL, f"{ex} -> {tb}")
else:
output = self._get_string(name, LoggingLevelEnum.FATAL, message)
# check if message can be written to log
if self._level.value >= LoggingLevelEnum.FATAL.value:
self._append_log(output)
# check if message can be shown in console
if self._console.value >= LoggingLevelEnum.FATAL.value:
Console.set_foreground_color(ForegroundColorEnum.red)
Console.write_line(output)
Console.set_foreground_color(ForegroundColorEnum.default)
sys.exit()
def _warn_console(self, name: str, message: str):
r"""Writes a warning to console only
Parameter:
name: :class:`str`
Error name
message: :class:`str`
Error message
"""
# check if message can be shown in console
if self._console.value >= LoggingLevelEnum.WARN.value:
Console.set_foreground_color(ForegroundColorEnum.yellow)
Console.write_line(self._get_string(name, LoggingLevelEnum.WARN, message))
Console.set_foreground_color(ForegroundColorEnum.default)
def _fatal_console(self, name: str, message: str, ex: Exception = None):
r"""Writes an error to console only
Parameter:
name: :class:`str`
Error name
message: :class:`str`
Error message
ex: :class:`Exception`
Thrown exception
"""
output = ""
if ex is not None:
tb = traceback.format_exc()
self.error(name, message)
output = self._get_string(name, LoggingLevelEnum.ERROR, f"{ex} -> {tb}")
else:
output = self._get_string(name, LoggingLevelEnum.ERROR, message)
# check if message can be shown in console
if self._console.value >= LoggingLevelEnum.FATAL.value:
Console.set_foreground_color(ForegroundColorEnum.red)
Console.write_line(output)
Console.set_foreground_color(ForegroundColorEnum.default)
sys.exit()

View File

@@ -0,0 +1,11 @@
from enum import Enum
class LoggingLevelEnum(Enum):
OFF = 0 # Nothing
FATAL = 1 # Error that cause exit
ERROR = 2 # Non fatal error
WARN = 3 # Error that can later be fatal
INFO = 4 # Normal information's
DEBUG = 5 # Detailed app state
TRACE = 6 # Detailed app information's

View File

@@ -0,0 +1,53 @@
from typing import Optional
from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl.core.log.logging_level_enum import LoggingLevelEnum
class LoggingSettings(ConfigurationModelABC):
r"""Representation of logging settings"""
def __init__(
self,
path: str = None,
filename: str = None,
console_log_level: LoggingLevelEnum = None,
file_log_level: LoggingLevelEnum = None,
):
ConfigurationModelABC.__init__(self)
self._path: Optional[str] = path
self._filename: Optional[str] = filename
self._console: Optional[LoggingLevelEnum] = console_log_level
self._level: Optional[LoggingLevelEnum] = file_log_level
@property
def path(self) -> str:
return self._path
@path.setter
def path(self, path: str) -> None:
self._path = path
@property
def filename(self) -> str:
return self._filename
@filename.setter
def filename(self, filename: str) -> None:
self._filename = filename
@property
def console(self) -> LoggingLevelEnum:
return self._console
@console.setter
def console(self, console: LoggingLevelEnum) -> None:
self._console = console
@property
def level(self) -> LoggingLevelEnum:
return self._level
@level.setter
def level(self, level: LoggingLevelEnum) -> None:
self._level = level

View File

@@ -0,0 +1,8 @@
from enum import Enum
class LoggingSettingsNameEnum(Enum):
path = "Path"
filename = "Filename"
console_level = "ConsoleLogLevel"
file_level = "FileLogLevel"

View File

@@ -0,0 +1,5 @@
from .email import EMail
from .email_client_service import EMailClient
from .email_client_abc import EMailClientABC
from .email_client_settings import EMailClientSettings
from .email_client_settings_name_enum import EMailClientSettingsNameEnum

View File

@@ -0,0 +1,3 @@
from .bool_pipe import BoolPipe
from .ip_address_pipe import IPAddressPipe
from .pipe_abc import PipeABC

View File

@@ -0,0 +1,9 @@
from cpl.core.pipes.pipe_abc import PipeABC
class BoolPipe(PipeABC):
def __init__(self):
pass
def transform(self, value: bool, *args):
return "True" if value else "False"

View File

@@ -0,0 +1,24 @@
from cpl.core.pipes.pipe_abc import PipeABC
class IPAddressPipe(PipeABC):
def __init__(self):
pass
def transform(self, value: list[int], *args):
string = ""
if len(value) != 4:
raise Exception("Invalid IP")
for i in range(0, len(value)):
byte = value[i]
if byte > 255 or byte < 0:
raise Exception("Invalid IP")
if i == len(value) - 1:
string += f"{byte}"
else:
string += f"{byte}."
return string

View File

@@ -0,0 +1,11 @@
from abc import ABC, abstractmethod
class PipeABC(ABC):
@abstractmethod
def __init__(self):
pass
@abstractmethod
def transform(self, value: any, *args):
pass

View File

@@ -0,0 +1,17 @@
from cpl_cli.configuration import VersionSettingsNameEnum
from cpl.core.pipes.pipe_abc import PipeABC
class VersionPipe(PipeABC):
def __init__(self):
pass
def transform(self, value: dict, *args):
for atr in VersionSettingsNameEnum:
if atr.value not in value:
raise KeyError(atr.value)
v_str = f"{value[VersionSettingsNameEnum.major.value]}.{value[VersionSettingsNameEnum.minor.value]}"
if value[VersionSettingsNameEnum.micro.value] is not None:
v_str += f".{value[VersionSettingsNameEnum.micro.value]}"
return v_str

View File

@@ -0,0 +1,2 @@
from .time_format_settings import TimeFormatSettings
from .time_format_settings_names_enum import TimeFormatSettingsNamesEnum

View File

@@ -0,0 +1,52 @@
from typing import Optional
from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC
class TimeFormatSettings(ConfigurationModelABC):
r"""Representation of time format settings"""
def __init__(
self,
date_format: str = None,
time_format: str = None,
date_time_format: str = None,
date_time_log_format: str = None,
):
ConfigurationModelABC.__init__(self)
self._date_format: Optional[str] = date_format
self._time_format: Optional[str] = time_format
self._date_time_format: Optional[str] = date_time_format
self._date_time_log_format: Optional[str] = date_time_log_format
@property
def date_format(self) -> str:
return self._date_format
@date_format.setter
def date_format(self, date_format: str) -> None:
self._date_format = date_format
@property
def time_format(self) -> str:
return self._time_format
@time_format.setter
def time_format(self, time_format: str):
self._time_format = time_format
@property
def date_time_format(self) -> str:
return self._date_time_format
@date_time_format.setter
def date_time_format(self, date_time_format: str) -> None:
self._date_time_format = date_time_format
@property
def date_time_log_format(self):
return self._date_time_log_format
@date_time_log_format.setter
def date_time_log_format(self, date_time_now_format: str) -> None:
self._date_time_log_format = date_time_now_format

View File

@@ -0,0 +1,8 @@
from enum import Enum
class TimeFormatSettingsNamesEnum(Enum):
date_format = "DateFormat"
time_format = "TimeFormat"
date_time_format = "DateTimeFormat"
date_time_log_format = "DateTimeLogFormat"

View File

@@ -0,0 +1,4 @@
from typing import TypeVar
T = TypeVar("T")
R = TypeVar("R")

View File

@@ -0,0 +1,3 @@
from .credential_manager import CredentialManager
from .string import String
from .pip import Pip

View File

@@ -0,0 +1,46 @@
import base64
class CredentialManager:
r"""Handles credential encryption and decryption"""
@staticmethod
def encrypt(string: str) -> str:
r"""Encode with base64
Parameter:
string: :class:`str`
String to encode
Returns:
Encoded string
"""
return base64.b64encode(string.encode("utf-8")).decode("utf-8")
@staticmethod
def decrypt(string: str) -> str:
r"""Decode with base64
Parameter:
string: :class:`str`
String to decode
Returns:
Decoded string
"""
return base64.b64decode(string).decode("utf-8")
@staticmethod
def build_string(string: str, credentials: str):
r"""Builds string with credentials in it
Parameter:
string: :class:`str`
String in which the variable is replaced by credentials
credentials: :class:`str`
String to encode
Returns:
Decoded string
"""
return string.replace("$credentials", CredentialManager.decrypt(credentials))

View File

@@ -0,0 +1,53 @@
import enum
from inspect import signature, Parameter
from cpl.core.utils import String
class JSONProcessor:
@staticmethod
def process(_t: type, values: dict) -> object:
args = []
kwargs = {}
sig = signature(_t.__init__)
for param in sig.parameters.items():
parameter = param[1]
if parameter.name == "self" or parameter.annotation == Parameter.empty:
continue
name = String.first_to_upper(String.convert_to_camel_case(parameter.name))
name_first_lower = String.first_to_lower(name)
if name in values or name_first_lower in values or name.upper() in values:
value = ""
if name in values:
value = values[name]
values.pop(name)
elif name_first_lower in values:
value = values[name_first_lower]
values.pop(name_first_lower)
else:
value = values[name.upper()]
values.pop(name.upper())
if isinstance(value, dict) and not issubclass(parameter.annotation, dict):
value = JSONProcessor.process(parameter.annotation, value)
if issubclass(parameter.annotation, enum.Enum):
value = parameter.annotation[value]
if type(value) != parameter.annotation:
value = parameter.annotation(value)
args.append(value)
elif parameter.name == "kwargs" and parameter.annotation == dict:
kwargs = values
elif parameter.default != Parameter.empty:
args.append(parameter.default)
else:
args.append(None)
return _t(*args, **kwargs)

View File

@@ -0,0 +1,130 @@
import os
import subprocess
import sys
from contextlib import suppress
from typing import Optional
class Pip:
r"""Executes pip commands"""
_executable = sys.executable
_env = os.environ
"""Getter"""
@classmethod
def get_executable(cls) -> str:
return cls._executable
"""Setter"""
@classmethod
def set_executable(cls, executable: str):
r"""Sets the executable
Parameter:
executable: :class:`str`
The python command
"""
if executable is None or executable == sys.executable:
return
cls._executable = executable
if not os.path.islink(cls._executable) or not os.path.isfile(executable):
return
path = os.path.dirname(os.path.dirname(cls._executable))
cls._env = os.environ
if sys.platform == "win32":
cls._env["PATH"] = f"{path}\\bin" + os.pathsep + os.environ.get("PATH", "")
else:
cls._env["PATH"] = f"{path}/bin" + os.pathsep + os.environ.get("PATH", "")
cls._env["VIRTUAL_ENV"] = path
@classmethod
def reset_executable(cls):
r"""Resets the executable to system standard"""
cls._executable = sys.executable
"""Public utils functions"""
@classmethod
def get_package(cls, package: str) -> Optional[str]:
r"""Gets given package py local pip list
Parameter:
package: :class:`str`
Returns:
The package name as string
"""
result = None
with suppress(Exception):
args = [cls._executable, "-m", "pip", "freeze", "--all"]
result = subprocess.check_output(args, stderr=subprocess.DEVNULL, env=cls._env)
if result is None:
return None
for p in str(result.decode()).split("\n"):
if p.startswith(package):
return p
return None
@classmethod
def get_outdated(cls) -> bytes:
r"""Gets table of outdated packages
Returns:
Bytes string of the command result
"""
args = [cls._executable, "-m", "pip", "list", "--outdated"]
return subprocess.check_output(args, env=cls._env)
@classmethod
def install(cls, package: str, *args, source: str = None, stdout=None, stderr=None):
r"""Installs given package
Parameter:
package: :class:`str`
The name of the package
args: :class:`list`
Arguments for the command
source: :class:`str`
Extra index URL
stdout: :class:`str`
Stdout of subprocess.run
stderr: :class:`str`
Stderr of subprocess.run
"""
pip_args = [cls._executable, "-m", "pip", "install"]
for arg in args:
pip_args.append(arg)
pip_args.append(package)
if source is not None:
pip_args.append(f"--extra-index-url")
pip_args.append(source)
subprocess.run(pip_args, stdout=stdout, stderr=stderr, env=cls._env)
@classmethod
def uninstall(cls, package: str, stdout=None, stderr=None):
r"""Uninstalls given package
Parameter:
package: :class:`str`
The name of the package
stdout: :class:`str`
Stdout of subprocess.run
stderr: :class:`str`
Stderr of subprocess.run
"""
args = [cls._executable, "-m", "pip", "uninstall", "--yes", package]
subprocess.run(args, stdout=stdout, stderr=stderr, env=cls._env)

View File

@@ -0,0 +1,92 @@
import re
import string
import random
class String:
r"""Useful functions for strings"""
@staticmethod
def convert_to_camel_case(chars: str) -> str:
r"""Converts string to camel case
Parameter:
chars: :class:`str`
String to convert
Returns:
String converted to CamelCase
"""
converted_name = chars
char_set = string.punctuation + " "
for char in char_set:
if char in converted_name:
converted_name = "".join(word.title() for word in converted_name.split(char))
return converted_name
@staticmethod
def convert_to_snake_case(chars: str) -> str:
r"""Converts string to snake case
Parameter:
chars: :class:`str`
String to convert
Returns:
String converted to snake_case
"""
# convert to train-case to CamelCase
if "_" in chars:
chars = chars.replace("_", "-")
if "-" in chars:
chars = "".join(word.title() for word in chars.split("-"))
if " " in chars:
new_chars = ""
for word in chars.split(" "):
new_chars += String.first_to_upper(word)
chars = new_chars
pattern1 = re.compile(r"(.)([A-Z][a-z]+)")
pattern2 = re.compile(r"([a-z0-9])([A-Z])")
file_name = re.sub(pattern1, r"\1_\2", chars)
return re.sub(pattern2, r"\1_\2", file_name).lower()
@staticmethod
def first_to_upper(chars: str) -> str:
r"""Converts first char to upper
Parameter:
chars: :class:`str`
String to convert
Returns:
String with first char as upper
"""
return f"{chars[0].upper()}{chars[1:]}"
@staticmethod
def first_to_lower(chars: str) -> str:
r"""Converts first char to lower
Parameter:
chars: :class:`str`
String to convert
Returns:
String with first char as lower
"""
return f"{chars[0].lower()}{chars[1:]}"
@staticmethod
def random_string(chars: str, length: int) -> str:
r"""Creates random string by given chars and length
Returns:
String of random chars
"""
return "".join(random.choice(chars) for _ in range(length))

View File

@@ -0,0 +1,29 @@
[build-system]
requires = ["setuptools>=70.1.0", "wheel>=0.43.0"]
build-backend = "setuptools.build_meta"
[project]
name = "cpl-core"
version = "2024.7.0"
description = "CPL core"
readme = "CPL core package"
requires-python = ">=3.12"
license = "MIT"
authors = [
{ name = "Sven Heidemann", email = "sven.heidemann@sh-edraft.de" }
]
keywords = ["cpl", "core", "backend", "shared", "library"]
dynamic = ["dependencies", "optional-dependencies"]
[project.urls]
Homepage = "https://www.sh-edraft.de"
[tool.setuptools.packages.find]
where = ["."]
include = ["cpl*"]
[tool.setuptools.dynamic]
dependencies = { file = ["requirements.txt"] }
optional-dependencies.dev = { file = ["requirements.dev.txt"] }

View File

@@ -0,0 +1 @@
black==25.1.0

View File

@@ -0,0 +1,5 @@
art==6.5
colorama==0.4.6
tabulate==0.9.0
termcolor==3.1.0
mysql-connector-python==9.4.0