diff --git a/.gitignore b/.gitignore index 7b9f092a..1bf87600 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,20 @@ +# Override of : +### Python ### +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Project specific +!/conf/script/src/ +!/conf/script/src/* +!/conf/script/src/**/* + # Created by https://www.toptal.com/developers/gitignore/api/git,vim,c++,meson,macos,linux,batch,emacs,eclipse,windows,netbeans,clion+all,notepadpp,powershell,intellij+all,visualstudio,visualstudiocode,python,pycharm+all # Edit at https://www.toptal.com/developers/gitignore?templates=git,vim,c++,meson,macos,linux,batch,emacs,eclipse,windows,netbeans,clion+all,notepadpp,powershell,intellij+all,visualstudio,visualstudiocode,python,pycharm+all @@ -566,16 +583,6 @@ celerybeat.pid # SageMath parsed files *.sage.py -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ -pythonenv* - # Spyder project settings .spyderproject .spyproject @@ -1004,4 +1011,5 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ -# End of https://www.toptal.com/developers/gitignore/api/git,vim,c++,meson,macos,linux,batch,emacs,eclipse,windows,netbeans,clion+all,notepadpp,powershell,intellij+all,visualstudio,visualstudiocode,python,pycharm+all \ No newline at end of file +# End of https://www.toptal.com/developers/gitignore/api/git,vim,c++,meson,macos,linux,batch,emacs,eclipse,windows,netbeans,clion+all,notepadpp,powershell,intellij+all,visualstudio,visualstudiocode,python,pycharm+all + diff --git a/conf/script/src/build_system/cmd/hierarchy/clean_build_dir/cli.py b/conf/script/src/build_system/cmd/hierarchy/clean_build_dir/cli.py index 855e47c5..76eaeb9f 100644 --- a/conf/script/src/build_system/cmd/hierarchy/clean_build_dir/cli.py +++ b/conf/script/src/build_system/cmd/hierarchy/clean_build_dir/cli.py @@ -16,7 +16,7 @@ def cli_clean_build_dir(): "folder, where the build system organizes into subfolders specific builds.") add_optional_path_arg(arg_parser, ROOT_DIR_ARG, path_arg_help="The project's root directory") - root_dir: AnyPath = parse_optional_path_arg(arg_parser, ROOT_DIR_ARG) + root_dir: TUnion_AnyPath = parse_optional_path_arg(arg_parser, ROOT_DIR_ARG) arg_parser.parse_args() def cli_cmd(): diff --git a/conf/script/src/build_system/cmd/hierarchy/create_build_dir/cli.py b/conf/script/src/build_system/cmd/hierarchy/create_build_dir/cli.py index 17aa9b92..3272b30a 100644 --- a/conf/script/src/build_system/cmd/hierarchy/create_build_dir/cli.py +++ b/conf/script/src/build_system/cmd/hierarchy/create_build_dir/cli.py @@ -15,7 +15,7 @@ def cli_create_build_dir(): description=f"Creates the project's '{colorama.Fore.LIGHTBLACK_EX}{BUILD_DIR_NAME}{colorama.Style.RESET_ALL}' folder.") add_optional_path_arg(arg_parser, ROOT_DIR_ARG, path_arg_help="The project's root directory") - root_dir: AnyPath = parse_optional_path_arg(arg_parser, ROOT_DIR_ARG) + root_dir: TUnion_AnyPath = parse_optional_path_arg(arg_parser, ROOT_DIR_ARG) arg_parser.parse_args() def cli_cmd(): diff --git a/conf/script/src/build_system/cmd/hierarchy/create_targets_build_dirs_structure/cli.py b/conf/script/src/build_system/cmd/hierarchy/create_targets_build_dirs_structure/cli.py index 0ab2b05e..e2eb27aa 100644 --- a/conf/script/src/build_system/cmd/hierarchy/create_targets_build_dirs_structure/cli.py +++ b/conf/script/src/build_system/cmd/hierarchy/create_targets_build_dirs_structure/cli.py @@ -18,7 +18,7 @@ def cli_create_targets_build_dirs_structure(): path_arg=BUILD_DIR_ARG, path_arg_help=f"The project's {BUILD_DIR_NAME} directory") - build_dir: AnyPath = parse_optional_path_arg(arg_parser, BUILD_DIR_ARG) + build_dir: TUnion_AnyPath = parse_optional_path_arg(arg_parser, BUILD_DIR_ARG) arg_parser.parse_args() def cli_cmd(): diff --git a/conf/script/src/build_system/cmd/hierarchy/find_build_dir/cli.py b/conf/script/src/build_system/cmd/hierarchy/find_build_dir/cli.py index 94f5e532..23fa181b 100644 --- a/conf/script/src/build_system/cmd/hierarchy/find_build_dir/cli.py +++ b/conf/script/src/build_system/cmd/hierarchy/find_build_dir/cli.py @@ -15,7 +15,7 @@ def cli_find_build_dir(): description=f"Finds the project's '{colorama.Fore.LIGHTBLACK_EX}{BUILD_DIR_NAME}{colorama.Style.RESET_ALL}' folder.") add_optional_path_arg(arg_parser, ROOT_DIR_ARG, path_arg_help="The project's root directory") - root_dir: AnyPath = parse_optional_path_arg(arg_parser, ROOT_DIR_ARG) + root_dir: TUnion_AnyPath = parse_optional_path_arg(arg_parser, ROOT_DIR_ARG) arg_parser.parse_args() def cli_cmd(): diff --git a/conf/script/src/build_system/cmd/hierarchy/find_conf_dir/cli.py b/conf/script/src/build_system/cmd/hierarchy/find_conf_dir/cli.py index 817829df..9cdd2a4d 100644 --- a/conf/script/src/build_system/cmd/hierarchy/find_conf_dir/cli.py +++ b/conf/script/src/build_system/cmd/hierarchy/find_conf_dir/cli.py @@ -15,7 +15,7 @@ def cli_find_conf_dir(): description=f"Finds the project's '{colorama.Fore.LIGHTBLACK_EX}{CONF_DIR_NAME}{colorama.Style.RESET_ALL}' folder.") add_optional_path_arg(arg_parser, ROOT_DIR_ARG, path_arg_help="The project's root directory") - root_dir: AnyPath = parse_optional_path_arg(arg_parser, ROOT_DIR_ARG) + root_dir: TUnion_AnyPath = parse_optional_path_arg(arg_parser, ROOT_DIR_ARG) arg_parser.parse_args() def cli_cmd(): diff --git a/conf/script/src/build_system/cmd/setup/_priv/cli/colorize.py b/conf/script/src/build_system/cmd/setup/_priv/cli/colorize.py index 8d1ba36d..514725c4 100644 --- a/conf/script/src/build_system/cmd/setup/_priv/cli/colorize.py +++ b/conf/script/src/build_system/cmd/setup/_priv/cli/colorize.py @@ -15,5 +15,5 @@ def colorize_label(label: str) -> str: return colorama.Fore.CYAN + label + colorama.Style.RESET_ALL -def colorize_path(path_info: PathLike) -> str: +def colorize_path(path_info: TUnion_PathLike) -> str: return colorama.Fore.LIGHTBLACK_EX + str(path_info) + colorama.Style.RESET_ALL diff --git a/conf/script/src/build_system/cmd/setup/_priv/setup_steps/step_save_compiler_env.py b/conf/script/src/build_system/cmd/setup/_priv/setup_steps/step_save_compiler_env.py index 4757ddb7..a8217437 100644 --- a/conf/script/src/build_system/cmd/setup/_priv/setup_steps/step_save_compiler_env.py +++ b/conf/script/src/build_system/cmd/setup/_priv/setup_steps/step_save_compiler_env.py @@ -6,6 +6,7 @@ import javaproperties from build_system.build_target import * +from ext.utils.string import * from file_structure import * from ..cli import * diff --git a/conf/script/src/build_system/cmd/setup/pub/cli_setup.py b/conf/script/src/build_system/cmd/setup/pub/cli_setup.py index 067b891c..c8b39197 100644 --- a/conf/script/src/build_system/cmd/setup/pub/cli_setup.py +++ b/conf/script/src/build_system/cmd/setup/pub/cli_setup.py @@ -16,7 +16,7 @@ def cli_setup_build_system(): 'folder and setup specific build system builds inside.') add_optional_path_arg(arg_parser, ROOT_DIR_ARG, path_arg_help="The project's root directory") - root_dir: AnyPath = parse_optional_path_arg(arg_parser, ROOT_DIR_ARG) + root_dir: TUnion_AnyPath = parse_optional_path_arg(arg_parser, ROOT_DIR_ARG) arg_parser.parse_args() def cli_cmd(): diff --git a/conf/script/src/cli/arg_parsing.py b/conf/script/src/cli/arg_parsing.py index 59ebff17..0d49e255 100644 --- a/conf/script/src/cli/arg_parsing.py +++ b/conf/script/src/cli/arg_parsing.py @@ -11,7 +11,7 @@ def add_optional_path_arg(arg_parser: argparse.ArgumentParser, path_arg: CLIArg = CLIArg.create_default_path_arg(), - path_arg_default_value: AnyPath = None, path_arg_help: Optional[AnyStr] = None): + path_arg_default_value: TUnion_AnyPath = None, path_arg_help: Optional[AnyStr] = None): arg_parser.add_argument(path_arg.prefixed_name, type=Path, nargs=argparse.OPTIONAL, const=path_arg_default_value, default=path_arg_default_value, help=path_arg_help) @@ -21,17 +21,17 @@ def _assure_no_unknown_parsed_args(arg_parser: argparse.ArgumentParser, unknown_ error.raise_or_exit_cli(arg_parser, print_usage=True) -def _assure_nonempty_parsed_path(arg_parser: argparse.ArgumentParser, path_arg_name: str, parsed_path: AnyPath): +def _assure_nonempty_parsed_path(arg_parser: argparse.ArgumentParser, path_arg_name: str, parsed_path: TUnion_AnyPath): if str(parsed_path) == str(): error = EmptyParsedArgError(path_arg_name) error.raise_or_exit_cli(arg_parser, print_usage=True) -def parse_optional_path_arg(arg_parser: argparse.ArgumentParser, path_arg: CLIArg = CLIArg.create_default_path_arg()) -> AnyPath: +def parse_optional_path_arg(arg_parser: argparse.ArgumentParser, path_arg: CLIArg = CLIArg.create_default_path_arg()) -> TUnion_AnyPath: parsed_args, unknown_parsed_args = arg_parser.parse_known_args([path_arg.prefixed_name]) _assure_no_unknown_parsed_args(arg_parser, unknown_parsed_args) - parsed_path: AnyPath = getattr(parsed_args, path_arg.name) if path_arg.name in parsed_args else None + parsed_path: TUnion_AnyPath = getattr(parsed_args, path_arg.name) if path_arg.name in parsed_args else None _assure_nonempty_parsed_path(arg_parser, path_arg.name, parsed_path) return parsed_path diff --git a/conf/script/src/ext/meta_prog/encapsulation/__init__.py b/conf/script/src/ext/meta_prog/encapsulation/__init__.py index 0275de28..5cf7029c 100644 --- a/conf/script/src/ext/meta_prog/encapsulation/__init__.py +++ b/conf/script/src/ext/meta_prog/encapsulation/__init__.py @@ -1,2 +1,3 @@ +from .clear_args_kwargs import * from .export import * from .no_export import * diff --git a/conf/script/src/ext/meta_prog/encapsulation/clear_args_kwargs.py b/conf/script/src/ext/meta_prog/encapsulation/clear_args_kwargs.py new file mode 100644 index 00000000..4224eb8f --- /dev/null +++ b/conf/script/src/ext/meta_prog/encapsulation/clear_args_kwargs.py @@ -0,0 +1,12 @@ +__all__ = ['ClearArgsKwargs'] + +from typing import Any + + +class ClearArgsKwargs: + + def __init__(self, *args, **kwargs) -> None: + super().__init__() + + def __new__(cls, *args, **kwargs) -> Any: + return super().__new__(cls) diff --git a/conf/script/src/ext/meta_prog/generics/cls_mixin.py b/conf/script/src/ext/meta_prog/generics/cls_mixin.py index 43f4f3e3..270a95e0 100644 --- a/conf/script/src/ext/meta_prog/generics/cls_mixin.py +++ b/conf/script/src/ext/meta_prog/generics/cls_mixin.py @@ -5,6 +5,9 @@ class GenericClassMixin(GenericsDataMixin): + def __new__(cls, *args, **kwargs): + return super().__new__(cls, *args, **kwargs) + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/conf/script/src/ext/meta_prog/generics/cls_proxy.py b/conf/script/src/ext/meta_prog/generics/cls_proxy.py index 1d643893..04ec7079 100644 --- a/conf/script/src/ext/meta_prog/generics/cls_proxy.py +++ b/conf/script/src/ext/meta_prog/generics/cls_proxy.py @@ -1,28 +1,45 @@ __all__ = ['GenericClassProxy'] import itertools -from typing import TypeVar +from typing import Any, TypeVar from .cls_wrapper import * from .cls_wrapper_data import * from .data import * +from ..encapsulation import * +from .proxy_verifier_mixin import * # TODO : functools -> wraps ? -class GenericClassProxy(GenericsDataMixin, - GenericClassWrapperMixin): +class GenericClassProxy(ProxyGenericsVerifierMixin, + GenericsDataMixin, + GenericClassWrapperMixin, + ClearArgsKwargs): - def __init__(self, - generic_cls: TAlias_generic_cls, - *args, - generics: tuple[type] = tuple(), - **kwargs) -> None: - generics_by_type_vars = self.__create_generics_by_type_vars(generic_cls=generic_cls, generics=generics) - super().__init__(*args, generic_cls=generic_cls, generics_by_type_vars=generics_by_type_vars, **kwargs) + def __new__(cls, + generic_cls: TAlias_generic_cls, + *args, + generics: tuple[type] = tuple(), + **kwargs): + generics_by_type_vars = cls.__create_generics_by_type_vars(generic_cls=generic_cls, generics=generics) + return super().__new__(cls, *args, generic_cls=generic_cls, generics_by_type_vars=generics_by_type_vars, **kwargs) + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) def __call__(self, *args, **kwargs): return self.wrapped_generic_cls(*args, generics_by_type_vars=self.generics_by_type_vars, **kwargs) + def __getattribute__(self, name: str) -> Any: + attr: Any + + try: + attr = super().__getattribute__(name) + except AttributeError: + attr = getattr(self.wrapped_generic_cls, name) + + return attr + @classmethod def __create_generics_by_type_vars(cls, generic_cls: TAlias_generic_cls, diff --git a/conf/script/src/ext/meta_prog/generics/cls_proxy_injector.py b/conf/script/src/ext/meta_prog/generics/cls_proxy_injector.py index 690d2df9..48ba6c86 100644 --- a/conf/script/src/ext/meta_prog/generics/cls_proxy_injector.py +++ b/conf/script/src/ext/meta_prog/generics/cls_proxy_injector.py @@ -10,20 +10,23 @@ class GenericClassProxyInjectorMixin(GenericClassMixin): __TAlias_Generics_Subscript_Op = Optional[Union[tuple, type]] - def __init__(self, - *args, - generics_by_type_vars: TAlias_Generics_By_TypeVars = None, - **kwargs) -> None: + def __new__(cls, + *args, + generics_by_type_vars: TAlias_Generics_By_TypeVars = None, + **kwargs): if generics_by_type_vars is None: - generic_cls = type(self) - generics = tuple() + generic_cls = cls + generics: tuple[type] = tuple() cls_proxy = GenericClassProxy(generic_cls=generic_cls, generics=generics) generics_by_type_vars = cls_proxy.generics_by_type_vars - super().__init__(*args, generics_by_type_vars=generics_by_type_vars, **kwargs) + return super().__new__(cls, *args, generics_by_type_vars=generics_by_type_vars, **kwargs) + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) @classmethod - def __class_getitem__(cls, item: __TAlias_Generics_Subscript_Op): - generics = item if isinstance(item, tuple) else (item,) + def __class_getitem__(cls, key: __TAlias_Generics_Subscript_Op): + generics = key if isinstance(key, tuple) else (key,) return GenericClassProxy(generic_cls=cls, generics=generics) diff --git a/conf/script/src/ext/meta_prog/generics/cls_wrapper.py b/conf/script/src/ext/meta_prog/generics/cls_wrapper.py index 4437ef59..21d27024 100644 --- a/conf/script/src/ext/meta_prog/generics/cls_wrapper.py +++ b/conf/script/src/ext/meta_prog/generics/cls_wrapper.py @@ -6,6 +6,9 @@ # TODO : functools -> wraps ? class GenericClassWrapperMixin(GenericClassWrapperDataMixin): + def __new__(cls, *args, **kwargs): + return super().__new__(cls, *args, **kwargs) + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/conf/script/src/ext/meta_prog/generics/cls_wrapper_data.py b/conf/script/src/ext/meta_prog/generics/cls_wrapper_data.py index 06614ae6..3214bb3e 100644 --- a/conf/script/src/ext/meta_prog/generics/cls_wrapper_data.py +++ b/conf/script/src/ext/meta_prog/generics/cls_wrapper_data.py @@ -1,14 +1,19 @@ __all__ = ['TAlias_generic_cls', 'GenericClassWrapperDataMixin'] -from typing import Final, Generic +from typing import Generic TAlias_generic_cls = type[Generic] class GenericClassWrapperDataMixin: - wrapped_generic_cls: Final[TAlias_generic_cls] + wrapped_generic_cls: TAlias_generic_cls - def __init__(self, generic_cls: TAlias_generic_cls, *args, **kwargs) -> None: - self.wrapped_generic_cls = generic_cls + def __new__(cls, generic_cls: TAlias_generic_cls, *args, **kwargs): + instance = super().__new__(cls, *args, **kwargs) + instance.wrapped_generic_cls = generic_cls + + return instance + + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/conf/script/src/ext/meta_prog/generics/data.py b/conf/script/src/ext/meta_prog/generics/data.py index 6a0f7d35..d6c0c1f9 100644 --- a/conf/script/src/ext/meta_prog/generics/data.py +++ b/conf/script/src/ext/meta_prog/generics/data.py @@ -1,16 +1,22 @@ __all__ = ['TAlias_Generics_By_TypeVars', 'GenericsDataMixin'] -from typing import Final, Optional, TypeVar +from typing import Optional, TypeVar TAlias_Generics_By_TypeVars = dict[TypeVar, Optional[type]] class GenericsDataMixin: - generics_by_type_vars: Final[TAlias_Generics_By_TypeVars] + generics_by_type_vars: TAlias_Generics_By_TypeVars - def __init__(self, *args, - generics_by_type_vars: TAlias_Generics_By_TypeVars = None, - **kwargs) -> None: - self.generics_by_type_vars = generics_by_type_vars if generics_by_type_vars is not None else {} + def __new__(cls, + *args, + generics_by_type_vars: TAlias_Generics_By_TypeVars = None, + **kwargs): + instance = super().__new__(cls, *args, **kwargs) + instance.generics_by_type_vars = generics_by_type_vars if generics_by_type_vars is not None else {} + + return instance + + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) diff --git a/conf/script/src/ext/meta_prog/generics/proxy_verifier_mixin.py b/conf/script/src/ext/meta_prog/generics/proxy_verifier_mixin.py new file mode 100644 index 00000000..2d204cd8 --- /dev/null +++ b/conf/script/src/ext/meta_prog/generics/proxy_verifier_mixin.py @@ -0,0 +1,32 @@ +__all__ = ['ProxyGenericsVerifierMixin'] + +from typing import TypeVar + +from .cls_wrapper_data import * +from .data import * + + +class ProxyGenericsVerifierMixin(GenericsDataMixin, GenericClassWrapperDataMixin): + + def __new__(cls, *args, **kwargs): + instance: cls = super().__new__(cls, *args, **kwargs) + instance._verify_generics() + + return instance + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + + def _verify_generics(self) -> None: + generic_type_vars: tuple[TypeVar] = self.__get_cls_generic_type_vars(self.wrapped_generic_cls) + + if len(self.generics_by_type_vars) != len(generic_type_vars): + raise TypeError(f'Wrong number of generic types for {type(self.wrapped_generic_cls)}.') + + # TODO : Change me for checking if it matches the actual TypeVars, i.e. if the bound and the constraints are respected. + if tuple(self.generics_by_type_vars.keys()) != generic_type_vars: + raise TypeError(f'Supplied generic types do not match the class\'s generic type vars requirements.') + + @staticmethod + def __get_cls_generic_type_vars(cls: TAlias_generic_cls) -> tuple[TypeVar]: + return cls.__parameters__ diff --git a/conf/script/src/ext/meta_prog/introspection/caller.py b/conf/script/src/ext/meta_prog/introspection/caller.py index 11f22090..25c20cec 100644 --- a/conf/script/src/ext/meta_prog/introspection/caller.py +++ b/conf/script/src/ext/meta_prog/introspection/caller.py @@ -1,5 +1,6 @@ __all__ = ['get_nth_caller', 'get_caller', + 'get_caller_func_name', 'is_frame_main', 'is_caller_main'] @@ -29,6 +30,10 @@ def get_caller() -> FrameType: return get_nth_caller(n=_CALLER_MIN_N + 1) +def get_caller_func_name() -> str: + return get_caller().f_code.co_name + + def is_frame_main(frame: FrameType) -> bool: frame_script_name = frame.f_locals[Macro.NAME] return frame_script_name == Macro.MAIN diff --git a/conf/script/src/ext/utils/__init__.py b/conf/script/src/ext/utils/__init__.py index 67d731a3..9da9f402 100644 --- a/conf/script/src/ext/utils/__init__.py +++ b/conf/script/src/ext/utils/__init__.py @@ -1 +1,2 @@ from .path import * +from .string import * diff --git a/conf/script/src/ext/utils/path/more_path.py b/conf/script/src/ext/utils/path/more_path.py index 1f20e62b..b6df2fbb 100644 --- a/conf/script/src/ext/utils/path/more_path.py +++ b/conf/script/src/ext/utils/path/more_path.py @@ -1,7 +1,28 @@ -__all__ = ['is_dir_empty'] +__all__ = ['is_dir_empty', + 'cast_path_like'] from pathlib import Path +from typing import cast + +from .path_typing import * +from ..string import * def is_dir_empty(dir_path: Path) -> bool: return not any(dir_path.iterdir()) + + +def cast_path_like(target_cls: type[T_PurePathLike], src_path_like: TUnion_PathLike, encoding: str = UTF_8) -> T_PurePathLike: + casted_path_like: T_PurePathLike + + if isinstance(src_path_like, target_cls): + casted_path_like = cast(target_cls, src_path_like) + elif issubclass(target_cls, Path): + casted_path_like = target_cls(src_path_like) + else: + if isinstance(src_path_like, Path): + src_path_like = str(src_path_like) + + casted_path_like = cast_any_str(target_cls=target_cls, src_any_str=src_path_like, encoding=encoding) + + return casted_path_like diff --git a/conf/script/src/ext/utils/path/path_typing.py b/conf/script/src/ext/utils/path/path_typing.py index 80fcbac2..2cb7148b 100644 --- a/conf/script/src/ext/utils/path/path_typing.py +++ b/conf/script/src/ext/utils/path/path_typing.py @@ -1,19 +1,34 @@ -__all__ = ['TConstraints_PathLike', - 'TConstraints_AnyPath', - 'PathLike', - 'AnyPath', +__all__ = ['TUnion_PurePath', + 'TUnion_PurePathLike', + 'TUnion_AnyPurePath', + 'TUnion_Path', + 'TUnion_PathLike', + 'TUnion_AnyPath', + 'T_PurePath', + 'T_PurePathLike', + 'T_AnyPurePath', + 'T_Path', 'T_PathLike', 'T_AnyPath'] import os -from pathlib import Path +from pathlib import Path, PurePath from typing import TypeVar, Union -TConstraints_PathLike = (Path, os.PathLike, str, bytes) -TConstraints_AnyPath = (Path, os.PathLike, str, bytes, type(None)) +from ..string import * -PathLike = Union[Path, os.PathLike, str, bytes] -AnyPath = Union[Path, os.PathLike, str, bytes, type(None)] +TUnion_PurePath = Union[PurePath, os.PathLike] +TUnion_PurePathLike = Union[TUnion_PurePath, TUnion_AnyStr] +TUnion_AnyPurePath = Union[TUnion_PurePathLike, type(None)] +TUnion_Path = Union[Path, os.PathLike] +TUnion_PathLike = Union[TUnion_Path, TUnion_AnyStr] +TUnion_AnyPath = Union[TUnion_PathLike, type(None)] + +T_PurePath = TypeVar("T_PurePath", PurePath, os.PathLike) +T_PurePathLike = TypeVar("T_PurePathLike", PurePath, os.PathLike, str, bytes) +T_AnyPurePath = TypeVar("T_AnyPurePath", PurePath, os.PathLike, str, bytes, type(None)) + +T_Path = TypeVar("T_Path", Path, os.PathLike) T_PathLike = TypeVar("T_PathLike", Path, os.PathLike, str, bytes) T_AnyPath = TypeVar("T_AnyPath", Path, os.PathLike, str, bytes, type(None)) diff --git a/conf/script/src/ext/utils/string/__init__.py b/conf/script/src/ext/utils/string/__init__.py new file mode 100644 index 00000000..6d85e3e1 --- /dev/null +++ b/conf/script/src/ext/utils/string/__init__.py @@ -0,0 +1,3 @@ +from .encoding_names import * +from .more_str import * +from .str_typing import * diff --git a/conf/script/src/ext/utils/string/encoding_names.py b/conf/script/src/ext/utils/string/encoding_names.py new file mode 100644 index 00000000..12804822 --- /dev/null +++ b/conf/script/src/ext/utils/string/encoding_names.py @@ -0,0 +1,6 @@ +__all__ = ['UTF_8'] + +from encodings import utf_8 +from typing import Final + +UTF_8: Final[str] = utf_8.getregentry().name diff --git a/conf/script/src/ext/utils/string/more_str.py b/conf/script/src/ext/utils/string/more_str.py new file mode 100644 index 00000000..15b9716a --- /dev/null +++ b/conf/script/src/ext/utils/string/more_str.py @@ -0,0 +1,17 @@ +__all__ = ['cast_any_str'] + +from typing import AnyStr, cast + +from .encoding_names import * +from .str_typing import * + + +def cast_any_str(target_cls: type[AnyStr], src_any_str: TUnion_AnyStr, encoding: str = UTF_8) -> AnyStr: + casted_any_str: AnyStr + + if isinstance(src_any_str, target_cls): + casted_any_str = cast(target_cls, src_any_str) + else: + casted_any_str = target_cls(src_any_str, encoding) + + return casted_any_str diff --git a/conf/script/src/ext/utils/string/str_typing.py b/conf/script/src/ext/utils/string/str_typing.py new file mode 100644 index 00000000..d7c4ef5c --- /dev/null +++ b/conf/script/src/ext/utils/string/str_typing.py @@ -0,0 +1,5 @@ +__all__ = ['TUnion_AnyStr'] + +from typing import Union + +TUnion_AnyStr = Union[str, bytes] diff --git a/conf/script/src/file_structure/fs_file_system.py b/conf/script/src/file_structure/fs_file_system.py index ed956603..d120be4e 100644 --- a/conf/script/src/file_structure/fs_file_system.py +++ b/conf/script/src/file_structure/fs_file_system.py @@ -1,9 +1,5 @@ -__all__ = ['BUILD_DIR_PERMISSIONS', - 'UTF_8'] - -from encodings import utf_8 +__all__ = ['BUILD_DIR_PERMISSIONS'] from ._type_alias import * BUILD_DIR_PERMISSIONS: Final[int] = 0o770 -UTF_8: TAlias_Name = utf_8.getregentry().name diff --git a/conf/script/src/host/env/__init__.py b/conf/script/src/host/env/__init__.py new file mode 100644 index 00000000..6dc31189 --- /dev/null +++ b/conf/script/src/host/env/__init__.py @@ -0,0 +1,5 @@ +from .env_var import * +from .env_var_base_it import * +from .env_var_fwd import * +from .env_var_key_it import * +from .local_env import * diff --git a/conf/script/src/host/env/env_var.py b/conf/script/src/host/env/env_var.py new file mode 100644 index 00000000..2e5dbbc5 --- /dev/null +++ b/conf/script/src/host/env/env_var.py @@ -0,0 +1,117 @@ +__all__ = ['EnvVar'] + +import os +from collections.abc import Iterator +from collections.abc import Mapping +from typing import AnyStr, Final, Generic, Optional + +from ext.meta_prog.encapsulation import * +from ext.meta_prog.generics import * +from ext.utils.path import * +from ext.utils.string import * +from .env_var_fwd import * + + +class EnvVar(GenericClassProxyInjectorMixin, Mapping[T_Env_Key, TAlias_Env_Values], Generic[T_Env_Key, T_Env_Single_Val], ClearArgsKwargs): + __env_key: T_Env_Key + __env_values: TAlias_Env_Values + + __ENV_VAR_ITEM_COUNT: Final[int] = 1 + + def __init__(self, + *args, + key: T_Env_Key = None, + values: Optional[TAlias_Env_Values] = None, + joined_values: Optional[AnyStr] = None, + **kwargs) -> None: + super().__init__(*args, **kwargs) + + generic_env_key: type = self.generics_by_type_vars[T_Env_Key] + + self.__verify_generics() + self.__verify_key_type(key=key) + + self.__env_key = key if key is not None else generic_env_key() + + if values is not None: + env_values = values + + elif joined_values is not None: + split_values: list[AnyStr] = self.__split_joined_values(joined_values=joined_values) + env_values = self.__cast_split_values(split_values=split_values) + + else: + env_values = TAlias_Env_Values() + + self.__env_values = env_values + + def __contains__(self, key: T_Env_Key) -> bool: + self.__verify_key_type(key=key) + env_key: T_Env_Key = self.get_env_key() + + return key is env_key or key == env_key + + def __getitem__(self, key: T_Env_Key) -> TAlias_Env_Values: + if key not in self: + raise KeyError() + + return self.get_env_values() + + def __len__(self) -> int: + return self.__ENV_VAR_ITEM_COUNT + + def __iter__(self) -> Iterator[T_Env_Key]: + return self.iter_key() + + def __str__(self) -> str: + joined_values = self.join_values(str_cls=str) + return f'{self.get_env_key()}={joined_values}' + + def get_env_key(self) -> T_Env_Key: + return self.__env_key + + def get_env_values(self) -> TAlias_Env_Values: + return self.__env_values + + def iter_key(self) -> Iterator[T_Env_Key]: + from host.env.env_var_key_it import EnvVarKeyIt + return EnvVarKeyIt(env_var=self) + + def iter_values(self) -> Iterator[T_Env_Single_Val]: + return iter(self.get_env_values()) + + def cast_values_to_any_str(self, target_cls: type[AnyStr]) -> list[AnyStr]: + return [cast_path_like(target_cls=target_cls, src_path_like=value) for value in self.get_env_values()] + + def join_values(self, str_cls: type[AnyStr]) -> AnyStr: + env_values_sep: AnyStr = self.__get_env_values_sep(joined_values_cls=str_cls) + casted_values: list[AnyStr] = self.cast_values_to_any_str(target_cls=str_cls) + + return env_values_sep.join(casted_values) + + @staticmethod + def __get_env_values_sep(joined_values_cls: type[AnyStr]) -> AnyStr: + return cast_any_str(target_cls=joined_values_cls, src_any_str=os.pathsep) + + def __split_joined_values(self, joined_values: AnyStr) -> list[AnyStr]: + env_values_sep: AnyStr = self.__get_env_values_sep(joined_values_cls=type(joined_values)) + split_values: list[AnyStr] = joined_values.strip(env_values_sep).split(sep=env_values_sep) + + return split_values + + def __cast_split_values(self, split_values: list[AnyStr]) -> TAlias_Env_Values: + type_env_single_val = self.generics_by_type_vars[T_Env_Single_Val] + return [cast_path_like(target_cls=type_env_single_val, src_path_like=value) for value in split_values] + + def __verify_generics(self): + if self.generics_by_type_vars[T_Env_Key] is None: + raise TypeError(f'Missing `T_Env_Key` generic type') + + if self.generics_by_type_vars[T_Env_Single_Val] is None: + raise TypeError(f'Missing `T_Env_Single_Val` generic type') + + def __verify_key_type(self, key: T_Env_Key) -> None: + generic_env_key = self.generics_by_type_vars[T_Env_Key] + + if not isinstance(key, generic_env_key): + raise TypeError() diff --git a/conf/script/src/host/env/env_var_base_it.py b/conf/script/src/host/env/env_var_base_it.py new file mode 100644 index 00000000..62c4fd54 --- /dev/null +++ b/conf/script/src/host/env/env_var_base_it.py @@ -0,0 +1,36 @@ +__all__ = ['EnvVarBaseIt'] + +from abc import ABCMeta, abstractmethod +from collections.abc import Iterator +from typing import Final, final + +from .env_var import * +from .env_var_fwd import * + + +class EnvVarBaseIt(Iterator[T_Env_Key], metaclass=ABCMeta): + __has_itered_over_env: bool + __env_var: Final[EnvVar] + + def __init__(self, env_var: EnvVar) -> None: + self.__has_itered_over_env = False + self.__env_var = env_var + + @final + def get_env_var(self) -> EnvVar: + return self.__env_var + + @final + def __next__(self) -> T_Env_Key: + self.__verify_has_next() + self.__has_itered_over_env = True + + return self.get_env_var().get_env_key() + + def __verify_has_next(self): + if self.__has_itered_over_env: + raise StopIteration() + + @abstractmethod + def _peek_next(self) -> T_Env_Key: + raise NotImplementedError() diff --git a/conf/script/src/host/env/env_var_fwd.py b/conf/script/src/host/env/env_var_fwd.py new file mode 100644 index 00000000..f46f3a2b --- /dev/null +++ b/conf/script/src/host/env/env_var_fwd.py @@ -0,0 +1,12 @@ +__all__ = ['T_Env_Key', + 'T_Env_Single_Val', + 'TAlias_Env_Values'] + +from typing import AnyStr, TypeVar + +from ext.utils.path import * + +T_Env_Key = AnyStr +T_Env_Single_Val = T_PurePathLike + +TAlias_Env_Values = list[T_Env_Single_Val] diff --git a/conf/script/src/host/env/env_var_key_it.py b/conf/script/src/host/env/env_var_key_it.py new file mode 100644 index 00000000..e3f9f671 --- /dev/null +++ b/conf/script/src/host/env/env_var_key_it.py @@ -0,0 +1,13 @@ +__all__ = ['EnvVarKeyIt'] + +from typing import final + +from .env_var_base_it import * +from .env_var_fwd import * + + +@final +class EnvVarKeyIt(EnvVarBaseIt[T_Env_Key]): + + def _peek_next(self) -> T_Env_Key: + return self.get_env_var().get_env_key() diff --git a/conf/script/src/host/env/local_env.py b/conf/script/src/host/env/local_env.py new file mode 100644 index 00000000..10913707 --- /dev/null +++ b/conf/script/src/host/env/local_env.py @@ -0,0 +1,10 @@ +__all__ = ['LocalEnv'] + +from dataclasses import dataclass + +from .env_var import * + + +@dataclass(init=False, order=True) +class LocalEnv: + env_vars: list[EnvVar] diff --git a/conf/script/src/test/test_ext/__init__.py b/conf/script/src/test/test_error/__init__.py similarity index 100% rename from conf/script/src/test/test_ext/__init__.py rename to conf/script/src/test/test_error/__init__.py diff --git a/conf/script/src/test/test_ext/error/__init__.py b/conf/script/src/test/test_error/core/__init__.py similarity index 100% rename from conf/script/src/test/test_ext/error/__init__.py rename to conf/script/src/test/test_error/core/__init__.py diff --git a/conf/script/src/test/test_ext/error/core/test_managed.py b/conf/script/src/test/test_error/core/test_managed.py similarity index 100% rename from conf/script/src/test/test_ext/error/core/test_managed.py rename to conf/script/src/test/test_error/core/test_managed.py diff --git a/conf/script/src/test/test_ext/error/core/test_meta.py b/conf/script/src/test/test_error/core/test_meta.py similarity index 100% rename from conf/script/src/test/test_ext/error/core/test_meta.py rename to conf/script/src/test/test_error/core/test_meta.py diff --git a/conf/script/src/test/test_ext/error/core/__init__.py b/conf/script/src/test/test_ext/error/core/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/conf/script/src/test/test_host/__init__.py b/conf/script/src/test/test_host/__init__.py new file mode 100644 index 00000000..a9a2c5b3 --- /dev/null +++ b/conf/script/src/test/test_host/__init__.py @@ -0,0 +1 @@ +__all__ = [] diff --git a/conf/script/src/test/test_host/test_env/__init__.py b/conf/script/src/test/test_host/test_env/__init__.py new file mode 100644 index 00000000..a79fe22f --- /dev/null +++ b/conf/script/src/test/test_host/test_env/__init__.py @@ -0,0 +1 @@ +from .test_env_var import * diff --git a/conf/script/src/test/test_host/test_env/_env_var_test_param_data_components.py b/conf/script/src/test/test_host/test_env/_env_var_test_param_data_components.py new file mode 100644 index 00000000..74b5cd1e --- /dev/null +++ b/conf/script/src/test/test_host/test_env/_env_var_test_param_data_components.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 + +__all__ = ['EnvVarTestParamDataComponents'] + +from os import pathsep +from pathlib import PurePath +from typing import Any, Final, final + + +@final +class EnvVarTestParamDataComponents: + TAlias_param_types = Final[list[type]] + TAlias_param_data_by_type = Final[dict[type: list[Any]]] + + __TAlias_param_data_str = Final[list[str]] + __TAlias_param_data_path = Final[list[PurePath]] + __TAlias_invalid_param_data_none = Final[list[type[None]]] + __TAlias_invalid_param_data_int = Final[list[type[int]]] + __TAlias_invalid_param_data_bool = Final[list[type[bool]]] + __TAlias_invalid_param_data_float = Final[list[type[float]]] + __TAlias_invalid_joined_values_data_path = Final[list[type[PurePath]]] + + valid_key_data_str: __TAlias_param_data_str = ['', 'key', 'test', '123', 'KEY', 'key_', '_key', 'z-abc', 'space space'] + valid_values_windows_path_data_str: __TAlias_param_data_str = [ + 'C:\\', + 'C:\\Users', + 'C:\\Users\\', + 'C:\\Users\\Public', + 'C:\\Users\\Public\\', + 'C:\\Users\\Public\\wow.doge', + '.\\', + '.\\Users', + '.\\Users\\', + '.\\Users\\Public', + '.\\Users\\Public\\', + '.\\Users\\Public\\wow.doge'] + valid_values_unix_path_data_str: __TAlias_param_data_str = [ + '/usr', + '/usr/', + '/usr/tmp', + '/usr/tmp/', + '~'] + valid_values_data_str: __TAlias_param_data_str = \ + valid_key_data_str + \ + [f'%{key_str}%' for key_str in valid_key_data_str] + \ + valid_values_windows_path_data_str + \ + valid_values_unix_path_data_str + \ + ['val1;', + 'val1;val2', + 'val1;val2;', + 'many_postfix_sep;;;;', + ';;;;;many_prefix_sep'] + valid_values_data_path: __TAlias_param_data_path = \ + [PurePath(values_path_str) for values_path_str in valid_values_windows_path_data_str] + \ + [PurePath(values_path_str) for values_path_str in valid_values_unix_path_data_str] + valid_joined_values_data_str: __TAlias_param_data_str = [ + pathsep.join(valid_key_data_str), + pathsep.join(valid_values_windows_path_data_str), + pathsep.join(valid_values_unix_path_data_str)] + + invalid_param_data_none: __TAlias_invalid_param_data_none = [None] + invalid_param_data_int: __TAlias_invalid_param_data_int = [-52323, -7, -10, -5, -1, 0, 1, 5, 7, 10, 25893] + invalid_param_data_bool: __TAlias_invalid_param_data_bool = [False, True] + invalid_param_data_float: __TAlias_invalid_param_data_float = [-0.1, -0.000001, -0.4123, -0.00546, -10.9023, -345.245, + 0.0, + 0.1, 0.000001, 0.4123, 0.00546, 10.9023, 345.245] + invalid_joined_values_data_path: __TAlias_invalid_joined_values_data_path = valid_values_data_path diff --git a/conf/script/src/test/test_host/test_env/env_var_test_param_data.py b/conf/script/src/test/test_host/test_env/env_var_test_param_data.py new file mode 100644 index 00000000..a6f3d57b --- /dev/null +++ b/conf/script/src/test/test_host/test_env/env_var_test_param_data.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 + +__all__ = ['EnvVarTestParamData'] + +from pathlib import PurePath +from typing import final + +from ext.utils.string import * +from ._env_var_test_param_data_components import EnvVarTestParamDataComponents as _fwd + + +@final +class EnvVarTestParamData: + valid_key_types: _fwd.TAlias_param_types = [str, bytes] + valid_values_types: _fwd.TAlias_param_types = [str, bytes, PurePath] + valid_joined_values_types: _fwd.TAlias_param_types = [str, bytes] + + invalid_key_types: _fwd.TAlias_param_types = [type(None), int, bool, float] + invalid_values_types: _fwd.TAlias_param_types = [type(None), int, bool, float] + invalid_joined_values_types: _fwd.TAlias_param_types = [type(None), int, bool, float, PurePath] + + valid_key_data_by_type: _fwd.TAlias_param_data_by_type = { + str: _fwd.valid_key_data_str, + bytes: [_key_str.encode(UTF_8) for _key_str in _fwd.valid_key_data_str] + } + valid_values_data_by_type: _fwd.TAlias_param_data_by_type = { + str: _fwd.valid_values_data_str, + bytes: [_values_str.encode(UTF_8) for _values_str in _fwd.valid_values_data_str], + PurePath: _fwd.valid_values_data_path + } + valid_joined_values_data_by_type: _fwd.TAlias_param_data_by_type = { + str: _fwd.valid_joined_values_data_str, + bytes: [_values_str.encode(UTF_8) for _values_str in _fwd.valid_joined_values_data_str], + } + + invalid_key_data_by_type: _fwd.TAlias_param_data_by_type = { + type(None): _fwd.invalid_param_data_none, + int: _fwd.invalid_param_data_int, + bool: _fwd.invalid_param_data_bool, + float: _fwd.invalid_param_data_float + } + invalid_values_data_by_type: _fwd.TAlias_param_data_by_type = { + type(None): _fwd.invalid_param_data_none, + int: _fwd.invalid_param_data_int, + bool: _fwd.invalid_param_data_bool, + float: _fwd.invalid_param_data_float + } + invalid_joined_values_data_by_type: _fwd.TAlias_param_data_by_type = { + type(None): _fwd.invalid_param_data_none, + int: _fwd.invalid_param_data_int, + bool: _fwd.invalid_param_data_bool, + float: _fwd.invalid_param_data_float, + PurePath: _fwd.invalid_joined_values_data_path + } diff --git a/conf/script/src/test/test_host/test_env/test_env_var.py b/conf/script/src/test/test_host/test_env/test_env_var.py new file mode 100644 index 00000000..e6fddfb7 --- /dev/null +++ b/conf/script/src/test/test_host/test_env/test_env_var.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 + +__all__ = ['TestEnvVar'] + +import unittest +from collections import Iterable +from typing import Callable, Optional + +from ext.meta_prog.introspection.caller import * +from host.env.env_var import * +from .env_var_test_param_data import EnvVarTestParamData as test_data + + +class TestEnvVar(unittest.TestCase): + __TAlias_generic_test_func = Callable[[type, type], None] + + def test_ref_cls_no_generics(self): + _ = EnvVar + self.assertIs(_, EnvVar) + + def test_ref_cls_valid_generics(self): + def _test_impl(key_type: type, values_type: type): + _ = EnvVar[key_type, values_type] + + self.__with_valid_generic_types(test_func=_test_impl) + + # TODO : See TODO in `ext.meta_prog.generics.proxy_verifier_mixin.ProxyGenericsVerifierMixin._verify_generics` + @unittest.skip('Skipping due to missing feature : verify if a type respects a TypeVar\'s constraints.') + def test_ref_cls_invalid_generics(self): + def _test_impl(key_type: type, values_type: type): + with self.assertRaises(TypeError): + _ = EnvVar[key_type, values_type] + + self.__with_invalid_generic_types(test_func=_test_impl) + + def test_constructor_no_generics_no_args_raises(self): + with self.assertRaises(TypeError): + _ = EnvVar() + + def test_constructor_no_generics_only_key_arg_raises(self): + with self.assertRaises(TypeError): + _ = EnvVar(key='key') + + def test_constructor_no_generics_only_values_arg_raises(self): + with self.assertRaises(TypeError): + _ = EnvVar(values=['values']) + + def test_constructor_no_generics_only_empty_values_arg_raises(self): + with self.assertRaises(TypeError): + _ = EnvVar(values=[]) + + def test_constructor_no_generics_only_joined_values_arg_raises(self): + with self.assertRaises(TypeError): + _ = EnvVar(joined_values='joined_values') + + def test_constructor_valid_generics_no_args_raises(self): + def _test_impl(key_type: type, values_type: type): + with self.assertRaises(TypeError): + _ = EnvVar[key_type, values_type]() + + self.__with_valid_generic_types(test_func=_test_impl) + + def __with_valid_generic_types(self, + test_func: __TAlias_generic_test_func, + msg: Optional[str] = None): + self.__with_generic_types(generic_types_iterative_func=self.__for_valid_generic_types, + test_func=test_func, + msg=msg) + + def __with_invalid_generic_types(self, + test_func: __TAlias_generic_test_func, + msg: Optional[str] = None): + self.__with_generic_types(generic_types_iterative_func=self.__for_invalid_generic_types, + test_func=test_func, + msg=msg) + + def __with_generic_types(self, + generic_types_iterative_func: type['__for_valid_generic_types'], + test_func: __TAlias_generic_test_func, + msg: Optional[str] = None): + def wrap_subtest(key_type: type, values_type: type): + kwargs = {'key_type': key_type, + 'values_type': values_type} + + if msg is not None and len(msg) > 0: + kwargs |= {'msg': msg} + + with self.subTest(**kwargs): + test_func(key_type, values_type) + + generic_types_iterative_func(test_func=wrap_subtest) + + @classmethod + def __for_valid_generic_types(cls, test_func: __TAlias_generic_test_func): + cls.__for_generic_types(key_types=test_data.valid_key_types, + values_types=test_data.valid_values_types, + test_func=test_func) + + @classmethod + def __for_invalid_generic_types(cls, test_func: __TAlias_generic_test_func): + cls.__for_generic_types(key_types=test_data.invalid_key_types, + values_types=test_data.invalid_values_types, + test_func=test_func) + + @staticmethod + def __for_generic_types(key_types: Iterable[type], + values_types: Iterable[type], + test_func: __TAlias_generic_test_func): + for key_type in key_types: + for values_type in values_types: + test_func(key_type, values_type) + + +if is_caller_main(): + unittest.main()