diff --git a/configurize/cli.py b/configurize/cli.py index 2f4aeb0..677d245 100644 --- a/configurize/cli.py +++ b/configurize/cli.py @@ -5,7 +5,12 @@ warnings.filterwarnings("ignore", ".*") from configurize import Config -from configurize.utils import compare_in_vscode, get_object_from_file, show_or_compare +from configurize.utils import ( + compare_in_vscode, + get_module_from_file, + get_object_from_file, + show_or_compare, +) def cfshow(ref: str | Config, exp: str | Config = None, key=None, query=None): @@ -22,10 +27,28 @@ def cfshow(ref: str | Config, exp: str | Config = None, key=None, query=None): with mock_imports(): if isinstance(ref, str): - ref = get_object_from_file(ref, "Exp")() + try: + ref = get_object_from_file(ref, "Exp")() + except: # No Exp, just show last class + mod = get_module_from_file(ref) + cfgs = [ + v + for v in mod.__dict__.values() + if isinstance(v, type) and issubclass(v, Config) + ] + ref = cfgs[-1]() if isinstance(exp, str) and os.path.exists(exp): - exp = get_object_from_file(exp, "Exp")() + try: + exp = get_object_from_file(exp, "Exp")() + except: # No Exp, just show last class + mod = get_module_from_file(exp) + cfgs = [ + v + for v in mod.__dict__.values() + if isinstance(v, type) and issubclass(v, Config) + ] + exp = cfgs[-1]() _ref = ref # hold the reference if exp: diff --git a/configurize/config.py b/configurize/config.py index bd912ff..f4dc18c 100644 --- a/configurize/config.py +++ b/configurize/config.py @@ -14,6 +14,13 @@ from .reference import CfgReferenceError, Ref from .utils import filter_traceback_frames, get_func_brief, writable_property +__REPR_FLAG = False # when enabled, all getattr fail will be skip + + +class Repr(str): + def __repr__(self): + return str(self) + class TaskSpec(TypedDict, total=False): """Indicate resource & entrypoint for specific task. @@ -379,14 +386,22 @@ def __getattribute__(self, name: str): try: if not name.startswith("_"): return self._get(name) - except AttributeError as e: - e.__traceback__ = filter_traceback_frames("configurize", e.__traceback__) - raise e from None + except Exception as e: + if __REPR_FLAG: + return Repr("❌ " + e.__repr__()) + else: + e.__traceback__ = filter_traceback_frames( + "configurize", e.__traceback__ + ) + raise e from None return super().__getattribute__(name) def __repr__(self): from pprint import pformat + global __REPR_FLAG + __REPR_FLAG = True + text = [f"{self._class_name}("] for k, v in self.to_dict(rep=True).items(): @@ -398,6 +413,7 @@ def __repr__(self): text.append(")") if self.root() is self and self.__class__.__name__ == "Exp": text.append(f"🗺️ TL;DR 🗺️\n{self._brief()}") + __REPR_FLAG = False return "\n".join(text) def _brief(self) -> str: diff --git a/configurize/utils.py b/configurize/utils.py index 4607b3b..1b536f3 100644 --- a/configurize/utils.py +++ b/configurize/utils.py @@ -51,21 +51,29 @@ def filter_traceback_frames( return new_tb -def get_object_from_file(file: str, name: str = "Exp") -> object: +def get_module_from_file(file: str) -> object: """ - get object by file. + get module by file. Args: file (str): file path. - name (str): object name. """ import importlib - import sys module_name_without_ext = os.path.splitext(os.path.basename(file))[0] directory_path = os.path.dirname(file) sys.path.insert(0, directory_path) current_exp = importlib.import_module(module_name_without_ext) - sys.path.pop(0) + return current_exp + + +def get_object_from_file(file: str, name: str = "Exp") -> object: + """ + get object by file. + Args: + file (str): file path. + name (str): object name. + """ + current_exp = get_module_from_file(file) obj = getattr(current_exp, name) return obj diff --git a/pyproject.toml b/pyproject.toml index f50e9f0..9c37ef2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "configurize" -version = "0.1.7" +version = "0.1.8" description = "Hierarchical configuration management with inheritance, cross-references, and diffing" readme = "README.md" license = {text = "MIT"}