diff --git a/conf/script/src/test/test_build_system/test_compiler/test_host/test_get_info/test_location/test_msvc/test_find_msvc.py b/conf/script/src/test/test_build_system/test_compiler/test_host/test_get_info/test_location/test_msvc/test_find_msvc.py index ca75806c..c95db8ea 100644 --- a/conf/script/src/test/test_build_system/test_compiler/test_host/test_get_info/test_location/test_msvc/test_find_msvc.py +++ b/conf/script/src/test/test_build_system/test_compiler/test_host/test_get_info/test_location/test_msvc/test_find_msvc.py @@ -5,7 +5,7 @@ from unittest.mock import MagicMock import build_system.cmd.compiler.host.get_info.location.msvc -import utils.cli.main +from utils.meta_prog.introspection import * class TestFindMSVC(unittest.TestCase): @@ -22,5 +22,5 @@ def test_no_arg_not_found(self, mock_vswhere: MagicMock): self.assertIsNone(result) -if utils.cli.main.is_caller_main(): +if is_caller_main(): unittest.main() diff --git a/conf/script/src/test/test_build_system/test_compiler/test_host/test_get_info/test_version/test_msvc/test_fetch_msvc_version.py b/conf/script/src/test/test_build_system/test_compiler/test_host/test_get_info/test_version/test_msvc/test_fetch_msvc_version.py index f7be362f..4cccd0ec 100644 --- a/conf/script/src/test/test_build_system/test_compiler/test_host/test_get_info/test_version/test_msvc/test_fetch_msvc_version.py +++ b/conf/script/src/test/test_build_system/test_compiler/test_host/test_get_info/test_version/test_msvc/test_fetch_msvc_version.py @@ -6,7 +6,7 @@ from unittest.mock import MagicMock import build_system.cmd.compiler.host.get_info.version.msvc -import utils.cli.main +from utils.meta_prog.introspection import * class TestFetchMSVCVersion(unittest.TestCase): @@ -45,5 +45,5 @@ def test_random_arg_return_none(self, mock_location: MagicMock, mock_vswhere: Ma assert return_value is expected_return_value -if utils.cli.main.is_caller_main(): +if is_caller_main(): unittest.main() diff --git a/conf/script/src/test/test_utils/error/test_managed.py b/conf/script/src/test/test_utils/error/test_managed.py index 1794ed28..6d36fffe 100644 --- a/conf/script/src/test/test_utils/error/test_managed.py +++ b/conf/script/src/test/test_utils/error/test_managed.py @@ -2,11 +2,11 @@ import unittest -import utils.cli.main import utils.error.format import utils.error.managed import utils.error.meta import utils.error.status +from utils.meta_prog.introspection import * class TestManage(unittest.TestCase): @@ -127,5 +127,5 @@ def __init__(self): self.assert_decorated_error_type(DecoratedError) -if utils.cli.main.is_caller_main(): +if is_caller_main(): unittest.main() diff --git a/conf/script/src/test/test_utils/error/test_meta.py b/conf/script/src/test/test_utils/error/test_meta.py index ea01d235..df9f3cbe 100644 --- a/conf/script/src/test/test_utils/error/test_meta.py +++ b/conf/script/src/test/test_utils/error/test_meta.py @@ -3,8 +3,8 @@ import abc import unittest -import utils.cli.main import utils.error.meta +from utils.meta_prog.introspection import * class TestErrorMeta(unittest.TestCase): @@ -42,5 +42,5 @@ def dummy(self): ErrorMetaImplChild() -if utils.cli.main.is_caller_main(): +if is_caller_main(): unittest.main() diff --git a/conf/script/src/utils/auto_print.py b/conf/script/src/utils/auto_print.py index 7cb219f6..7b52059d 100644 --- a/conf/script/src/utils/auto_print.py +++ b/conf/script/src/utils/auto_print.py @@ -1,5 +1,6 @@ # Consider using the dataclasses package +# TODO : functools -> wraps ? # From https://stackoverflow.com/a/33800620/2924010 def auto_repr(cls): def __repr__(self): @@ -12,6 +13,7 @@ def __repr__(self): return cls +# TODO : functools -> wraps ? # From https://stackoverflow.com/a/33800620/2924010 def auto_str(cls): def __str__(self): diff --git a/conf/script/src/utils/cli/main.py b/conf/script/src/utils/cli/main.py index 92afa5ec..567249f4 100644 --- a/conf/script/src/utils/cli/main.py +++ b/conf/script/src/utils/cli/main.py @@ -1,12 +1,7 @@ -import inspect -from types import FrameType -from typing import Callable, Final, cast +from typing import Callable import colorama -MACRO___NAME__: Final[str] = '__name__' -MACRO___NAME___MAIN: Final[str] = '__main__' - def init(): colorama.init() @@ -16,14 +11,6 @@ def deinit(): colorama.deinit() -def is_caller_main() -> bool: - # See https://stackoverflow.com/a/57712700/ - caller_frame = cast(FrameType, cast(FrameType, inspect.currentframe()).f_back) - caller_script_name = caller_frame.f_locals[MACRO___NAME__] - - return caller_script_name == MACRO___NAME___MAIN - - def wrap_main(main_func: Callable): init() main_func() diff --git a/conf/script/src/utils/error/managed.py b/conf/script/src/utils/error/managed.py index f70859e6..c72ceb3d 100644 --- a/conf/script/src/utils/error/managed.py +++ b/conf/script/src/utils/error/managed.py @@ -16,6 +16,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) +# TODO : functools -> wraps ? class ManageClass: def __new__(cls, decorated_cls: Optional[type] = None, error_formatter_cls: Type[utils.error.format.BaseFormattedErrorMixin] = utils.error.format.FormattedErrorMixin, diff --git a/conf/script/src/utils/error/synthesis.py b/conf/script/src/utils/error/synthesis.py index 6de0905f..6c6f6637 100644 --- a/conf/script/src/utils/error/synthesis.py +++ b/conf/script/src/utils/error/synthesis.py @@ -3,6 +3,7 @@ import utils.error.cls_def import utils.error.status +# TODO : Remove, deprecated ALL_ERRORS: Final = [ utils.error.cls_def.SuccessWarning, utils.error.cls_def.UnsupportedError, diff --git a/conf/script/src/utils/meta_prog/encapsulation/__init__.py b/conf/script/src/utils/meta_prog/encapsulation/__init__.py index 1cc9d89a..0275de28 100644 --- a/conf/script/src/utils/meta_prog/encapsulation/__init__.py +++ b/conf/script/src/utils/meta_prog/encapsulation/__init__.py @@ -1 +1,2 @@ from .export import * +from .no_export import * diff --git a/conf/script/src/utils/meta_prog/encapsulation/export.py b/conf/script/src/utils/meta_prog/encapsulation/export.py index d7a72a21..f7f51979 100644 --- a/conf/script/src/utils/meta_prog/encapsulation/export.py +++ b/conf/script/src/utils/meta_prog/encapsulation/export.py @@ -1,21 +1,22 @@ import inspect import typing -__all__ = ['export'] +from utils.meta_prog.introspection import * + +__all__: TAlias_Macro_All = ['export'] def export(func: typing.Callable): - attribute_name_all: typing.Final[str] = '__all__' - module_api: list[str] + module_api: TAlias_Macro_All module = inspect.getmodule(func) - func_api = func.__qualname__ + func_api = getattr(func, Macro.QUALNAME) - if not hasattr(module, attribute_name_all): - module_api = [] - setattr(module, attribute_name_all, module_api) + if not hasattr(module, Macro.ALL): + module_api = TAlias_Macro_All() + setattr(module, Macro.ALL, module_api) else: - module_api = getattr(module, attribute_name_all) + module_api = getattr(module, Macro.ALL) module_api.append(func_api) diff --git a/conf/script/src/utils/meta_prog/encapsulation/no_export.py b/conf/script/src/utils/meta_prog/encapsulation/no_export.py new file mode 100644 index 00000000..75d5961b --- /dev/null +++ b/conf/script/src/utils/meta_prog/encapsulation/no_export.py @@ -0,0 +1,15 @@ +import inspect +import typing + +from utils.meta_prog.introspection import * + +__all__: TAlias_Macro_All = ['no_export'] + + +def no_export(func: typing.Callable): + module = inspect.getmodule(func) + module_api = TAlias_Macro_All() + + setattr(module, Macro.ALL, module_api) + + return func diff --git a/conf/script/src/utils/meta_prog/generics/cls_proxy.py b/conf/script/src/utils/meta_prog/generics/cls_proxy.py index c6bc7fbf..32455e81 100644 --- a/conf/script/src/utils/meta_prog/generics/cls_proxy.py +++ b/conf/script/src/utils/meta_prog/generics/cls_proxy.py @@ -4,6 +4,7 @@ import utils.meta_prog.generics.data +# TODO : functools -> wraps ? class GenericClassProxy(utils.meta_prog.generics.data.GenericsDataMixin, utils.meta_prog.generics.cls_wrapper.GenericClassWrapperMixin): from typing import TypeVar diff --git a/conf/script/src/utils/meta_prog/generics/cls_wrapper.py b/conf/script/src/utils/meta_prog/generics/cls_wrapper.py index b0623f55..77a819f0 100644 --- a/conf/script/src/utils/meta_prog/generics/cls_wrapper.py +++ b/conf/script/src/utils/meta_prog/generics/cls_wrapper.py @@ -1,6 +1,7 @@ import utils.meta_prog.generics.cls_wrapper_data +# TODO : functools -> wraps ? class GenericClassWrapperMixin(utils.meta_prog.generics.cls_wrapper_data.GenericClassWrapperDataMixin): def __init__(self, *args, **kwargs) -> None: diff --git a/conf/script/src/utils/meta_prog/introspection/__init__.py b/conf/script/src/utils/meta_prog/introspection/__init__.py new file mode 100644 index 00000000..f3d1eaeb --- /dev/null +++ b/conf/script/src/utils/meta_prog/introspection/__init__.py @@ -0,0 +1,3 @@ +from .attr_manip import * +from .caller import * +from .macro import * diff --git a/conf/script/src/utils/meta_prog/introspection/attr_manip.py b/conf/script/src/utils/meta_prog/introspection/attr_manip.py new file mode 100644 index 00000000..750c1db3 --- /dev/null +++ b/conf/script/src/utils/meta_prog/introspection/attr_manip.py @@ -0,0 +1,24 @@ +from typing import * + +from .macro import * + +__all__: TAlias_Macro_All = ['get_or_create_attr'] + +T_Attr_Val = TypeVar('T_Attr_Val') +TAlias_Attr_Val_Generator = Callable[[], T_Attr_Val] + + +def get_or_create_attr(obj, + attr: str, + default_val_generator: TAlias_Attr_Val_Generator = lambda: T_Attr_Val()) -> T_Attr_Val: + attr_val: T_Attr_Val + + assert hasattr(obj, Macro.DICT) + + if not hasattr(obj, attr): + attr_val = default_val_generator() + setattr(obj, attr, attr_val) + else: + attr_val = getattr(obj, attr) + + return attr_val diff --git a/conf/script/src/utils/meta_prog/introspection/caller.py b/conf/script/src/utils/meta_prog/introspection/caller.py new file mode 100644 index 00000000..764b4d2c --- /dev/null +++ b/conf/script/src/utils/meta_prog/introspection/caller.py @@ -0,0 +1,35 @@ +import inspect +from types import FrameType +from typing import Final + +from .macro import * + +__all__ = ['get_nth_caller', 'get_caller', 'is_caller_main'] + +CALLER_MIN_N: Final[int] = 1 + + +def get_nth_caller(n: int = CALLER_MIN_N) -> FrameType: + assert n >= CALLER_MIN_N + + stack_indices = range(CALLER_MIN_N, n) + caller_frame: FrameType = inspect.currentframe() + + for _ in stack_indices: + caller_frame = caller_frame.f_back + + return caller_frame + + +def get_caller() -> FrameType: + return get_nth_caller(n=CALLER_MIN_N + 1) + + +def is_frame_main(frame: FrameType) -> bool: + frame_script_name = frame.f_locals[Macro.NAME] + return frame_script_name == Macro.MAIN + + +def is_caller_main() -> bool: + caller = get_caller() + return is_frame_main(frame=caller) diff --git a/conf/script/src/utils/meta_prog/introspection/macro.py b/conf/script/src/utils/meta_prog/introspection/macro.py new file mode 100644 index 00000000..8c3ee0a1 --- /dev/null +++ b/conf/script/src/utils/meta_prog/introspection/macro.py @@ -0,0 +1,22 @@ +from enum import Enum, auto, unique +from typing import Final + +TAlias_Macro_All = list[str] + +__all__: TAlias_Macro_All = ['Macro', 'TAlias_Macro_All'] + + +class AutoMacroFromName(str, Enum): + + def _generate_next_value_(name: str, start, count, last_values) -> str: + __AFFIX: Final[str] = '__' + return __AFFIX + name.lower() + __AFFIX + + +@unique +class Macro(AutoMacroFromName): + ALL = auto() + DICT = auto() + MAIN = auto() + NAME = auto() + QUALNAME = auto() diff --git a/conf/script/src/utils/more_typing.py b/conf/script/src/utils/more_typing.py index b5760be6..72ea2821 100644 --- a/conf/script/src/utils/more_typing.py +++ b/conf/script/src/utils/more_typing.py @@ -2,8 +2,8 @@ import pathlib import typing -T_PathLike_Constraints = (pathlib.Path, os.PathLike, str, bytes) -T_AnyPath_Constraints = (pathlib.Path, os.PathLike, str, bytes, type(None)) +TConstraints_PathLike = (pathlib.Path, os.PathLike, str, bytes) +TConstraints_AnyPath = (pathlib.Path, os.PathLike, str, bytes, type(None)) PathLike = typing.Union[pathlib.Path, os.PathLike, str, bytes] AnyPath = typing.Union[pathlib.Path, os.PathLike, str, bytes, type(None)]