From 20a4043e1b31b8eb72c8c713680d806d1cd8bf56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=AE=E5=9C=B0=E5=AE=8F=E6=A8=B9?= Date: Thu, 27 May 2021 12:10:59 +0900 Subject: [PATCH 01/14] Change pkg_name to src parameter --- autoload/module_loader.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/autoload/module_loader.py b/autoload/module_loader.py index 239bc92..45400bd 100644 --- a/autoload/module_loader.py +++ b/autoload/module_loader.py @@ -1,4 +1,5 @@ import inspect +import warnings from importlib import import_module from os import listdir from os import path as os_path @@ -97,35 +98,51 @@ def load_function(self, file_name: str) -> Callable: def load_classes( self, - pkg_name: str, + src: str, excludes: Optional[Iterable[str]] = None, recursive: bool = False, + *args, + **kwargs, ) -> Tuple[Type]: """Import Python package and return classes. - :param pkg_name: Python package name (directory name). + :param src: Python package or module name. You can input relative path like '../example' based on 'base_path'. :param excludes: Python file names you don't want to import in the package. :param recursive: If True, import Python package recursively. :return: class objects defined in the Python package according to rules. """ + pkg_name = kwargs.get("pkg_name") + if kwargs.get("pkg_name") is not None: + warnings.warn( + "'pkg_name' is deprecated. Please use 'src' parameter.", FutureWarning + ) + src = pkg_name self.__context = ContextFactory.get(LoadType.clazz) - return self.__load_resources(pkg_name, excludes=excludes, recursive=recursive) + return self.__load_resources(src, excludes=excludes, recursive=recursive) def load_functions( self, - pkg_name: str, + src: str, excludes: Optional[Iterable[str]] = None, recursive: bool = False, + *args, + **kwargs, ) -> Tuple[Callable]: """Import Python package and return functions. - :param pkg_name: Python package name (directory name). + :param src: Python package or module name. You can input relative path like '../example' based on 'base_path'. :param excludes: Python file names you don't want to import in the package. :param recursive: If True, import Python package recursively. :return: function objects defined in the Python package according to rules. """ + pkg_name = kwargs.get("pkg_name") + if kwargs.get("pkg_name") is not None: + warnings.warn( + "'pkg_name' is deprecated. Please use 'src' parameter.", FutureWarning + ) + src = pkg_name self.__context = ContextFactory.get(LoadType.func) - return self.__load_resources(pkg_name, excludes=excludes, recursive=recursive) + return self.__load_resources(src, excludes=excludes, recursive=recursive) def __path_fix(self, name: str) -> str: if not name or name == "." or name == "/" or name == "./": From feaa078f79ea765c04a183844e5141fa052b8ed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=AE=E5=9C=B0=E5=AE=8F=E6=A8=B9?= Date: Thu, 27 May 2021 21:17:26 +0900 Subject: [PATCH 02/14] Add Importable class --- autoload/_import.py | 91 +++++++++++++++++++++++++++++++++++++++ autoload/module_loader.py | 15 ++----- 2 files changed, 94 insertions(+), 12 deletions(-) create mode 100644 autoload/_import.py diff --git a/autoload/_import.py b/autoload/_import.py new file mode 100644 index 0000000..2ba4254 --- /dev/null +++ b/autoload/_import.py @@ -0,0 +1,91 @@ +from importlib import import_module +from os import listdir +from os import path as os_path +from sys import path as sys_path +from typing import Callable, Iterable, List, Optional + + +def _exclude_py(path: str) -> str: + if path.endswith(".py"): + return path.replace(".py", "") + return path + + +def _exclude_ex(file_name: str) -> str: + return os_path.splitext(file_name)[0] + + +class Importable: + def __init__( + self, + path: str, + recursive: bool = False, + excludes: Optional[Iterable[str]] = None, + ): + fix_excludes = [_exclude_py(e) for e in excludes] + self.__path: str = _exclude_py(path) + self.__recursive = recursive + children = self._load_children(excludes) + self.__children: List[Importable] = [ + child for child in children if child.get_base_name() in fix_excludes + ] + + @property + def path(self): + return self.__path + + def get_base_name(self): + return _exclude_ex(os_path.basename(self.__path)) + + def get_all_paths(self): + return [self.__path].extend([child.path for child in self.__children]) + + def get_children_files(self): + return [child.get_base_name() for child in self.__children] + + def has_children(self) -> bool: + return len(self.__children) > 0 + + def load_all(self, callback: Callable): + children_files = self.get_children_files() + if len(children_files) == 0: + return + for file in children_files: + module = import_module(file) + callback(file, module) + + def _load_children(self, excludes: Optional[Iterable[str]] = None): + return [] + + +class _Module(Importable): + pass + + +class _Package(Importable): + def _load_children(self, excludes: Optional[Iterable[str]] = None): + path = self.__path + recursive = self.__recursive + children = [] + for file_or_dir in listdir(path): + if file_or_dir in excludes: + continue + if file_or_dir.endswith(".py"): + children.append(Importable(f"{path}/{_exclude_ex(file_or_dir)}")) + continue + children.append( + _Package( + f"{path}/{file_or_dir}/", recursive=recursive, excludes=excludes + ) + ) + return children + + +class ImportableFactory: + @staticmethod + def get(path: str, *args, **kwargs): + if os_path.isdir(path): + if path not in sys_path: + sys_path.append(path) + return _Package(path, *args, **kwargs) + return _Module(path) diff --git a/autoload/module_loader.py b/autoload/module_loader.py index 45400bd..e9fa12d 100644 --- a/autoload/module_loader.py +++ b/autoload/module_loader.py @@ -8,6 +8,7 @@ from ._context import Context, ContextFactory from ._globals import LoadType +from ._import import ImportableFactory from .exception import LoaderStrictModeError __all__ = "ModuleLoader" @@ -221,15 +222,6 @@ def __load_resources( recursive: Optional[bool] = False, ) -> Tuple[_T]: target_dir = self.__path_fix(pkg_name) - if not os_path.isdir(target_dir): - raise NotADirectoryError(f"Not Found The Directory : {target_dir}") - if target_dir not in sys_path: - sys_path.append(target_dir) - files = [ - os_path.splitext(file)[0] - for file in listdir(target_dir) - if file.endswith(".py") - ] private = _access_private() exclude_files = list(private.DEFAULT_EXCLUDES) exclude_files.append(os_path.basename(private.detect_call_path())) @@ -240,14 +232,13 @@ def __load_resources( if not isinstance(exclude, str): raise TypeError("The contents of the excludes must all be strings") exclude_files.append(exclude) - fix_excludes = [exclude.replace(".py", "") for exclude in exclude_files] - excluded_files = set(files) - set(fix_excludes) + importable = ImportableFactory.get(target_dir, recursive, excludes) mods: List[_T] = [] decorator_attr = private.DECORATOR_ATTR context = self.__context is_strict = self.__strict load_type_name = context.load_type.value - for file in excluded_files: + for file in importable.get_children_files(): module = import_module(file) target_load_name = context.draw_comparison(file) is_found = False From 5c0e1e40c6e6660c7a58b7176c6dd6a925839c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=AE=E5=9C=B0=E5=AE=8F=E6=A8=B9?= Date: Sat, 29 May 2021 11:08:06 +0900 Subject: [PATCH 03/14] Delegate some roles to Importable class --- autoload/_context.py | 4 +- autoload/_import.py | 130 +++++++++++++++++++++++++++++++------- autoload/module_loader.py | 79 ++--------------------- 3 files changed, 112 insertions(+), 101 deletions(-) diff --git a/autoload/_context.py b/autoload/_context.py index 61e6eb3..c943ddf 100644 --- a/autoload/_context.py +++ b/autoload/_context.py @@ -15,11 +15,11 @@ def load_type(self): @abstractmethod def predicate(self): - raise Exception("'predicate' function is not defined.") + raise Exception("'predicate' method is not defined.") @abstractmethod def draw_comparison(self, file: str): - raise Exception("'draw_comparison' function is not defined.") + raise Exception("'draw_comparison' method is not defined.") class _ClassContext(Context): diff --git a/autoload/_import.py b/autoload/_import.py index 2ba4254..44e5231 100644 --- a/autoload/_import.py +++ b/autoload/_import.py @@ -1,8 +1,14 @@ +import inspect +from abc import ABC, abstractmethod +from dataclasses import dataclass from importlib import import_module from os import listdir from os import path as os_path from sys import path as sys_path -from typing import Callable, Iterable, List, Optional +from typing import Any, Callable, Iterable, List, Optional + +from ._context import Context +from .exception import LoaderStrictModeError def _exclude_py(path: str) -> str: @@ -15,17 +21,31 @@ def _exclude_ex(file_name: str) -> str: return os_path.splitext(file_name)[0] -class Importable: - def __init__( - self, - path: str, - recursive: bool = False, - excludes: Optional[Iterable[str]] = None, - ): - fix_excludes = [_exclude_py(e) for e in excludes] - self.__path: str = _exclude_py(path) - self.__recursive = recursive - children = self._load_children(excludes) +def _flatten(iterable: Iterable[Any]) -> Iterable[Any]: + for el in iterable: + if isinstance(el, Iterable) and not isinstance(el, (str, bytes)): + yield from _flatten(el) + else: + yield el + + +_DECORATOR_ATTR = "_load_flg" + + +@dataclass(frozen=True) +class ImportOption: + recursive: bool = False + excludes: Optional[Iterable[str]] = None + is_strict: bool = False + + +class Importable(ABC): + def __init__(self, path: str, context: Context, option: ImportOption): + fix_excludes = [_exclude_py(e) for e in option.excludes] + self.__path = _exclude_py(path) + self.__context = context + self.__option = option + children = self._load_children() self.__children: List[Importable] = [ child for child in children if child.get_base_name() in fix_excludes ] @@ -34,6 +54,10 @@ def __init__( def path(self): return self.__path + @abstractmethod + def import_resources(self): + raise Exception("'import_resources' method is not defined.") + def get_base_name(self): return _exclude_ex(os_path.basename(self.__path)) @@ -54,30 +78,88 @@ def load_all(self, callback: Callable): module = import_module(file) callback(file, module) - def _load_children(self, excludes: Optional[Iterable[str]] = None): + def _load_children(self): return [] class _Module(Importable): - pass + def import_resources(self): + file = self.get_base_name() + context = self.__context + is_strict = self.__option.is_strict + load_type_name = context.load_type.value + module = import_module(file) + target_load_name = context.draw_comparison(file) + is_found = False + error = None + mods = [] + members = inspect.getmembers(module, context.predicate()) + for mod_name, mod in members: + is_name_match = target_load_name == mod_name + if hasattr(mod, _DECORATOR_ATTR): + if not getattr(mod, "_load_flg"): + continue + if is_found: + # High priority error + error = LoaderStrictModeError( + f"Loader can only load a " + f"'{target_load_name}' {load_type_name} in {file} module." + f"\nPlease check '{mod_name}' in {file} module." + ) + break + if is_strict and not is_name_match: + error = LoaderStrictModeError( + f"Loader can't load '{mod_name}' in {file} module." + f"\nPlease rename '{target_load_name}' {load_type_name}." + ) + continue + mods.append(mod) + if is_strict: + if error: + # High priority error + error = LoaderStrictModeError( + f"Loader can only load a " + f"'{target_load_name}' {load_type_name} " + f"in {file} module." + ) + break + is_found = True + continue + if not is_name_match: + continue + mods.append(mod) + if is_strict: + if error: + # High priority error + error = LoaderStrictModeError( + f"Loader can only load a " + f"'{target_load_name}' {load_type_name} in {file} module." + ) + break + is_found = True + if error is not None: + raise error + return mods class _Package(Importable): - def _load_children(self, excludes: Optional[Iterable[str]] = None): + def import_resources(self): + return _flatten([child.import_resources() for child in self.__children]) + + def _load_children(self): path = self.__path - recursive = self.__recursive + context = self.__context + option = self.__option children = [] for file_or_dir in listdir(path): - if file_or_dir in excludes: + if file_or_dir in option.excludes: continue if file_or_dir.endswith(".py"): - children.append(Importable(f"{path}/{_exclude_ex(file_or_dir)}")) - continue - children.append( - _Package( - f"{path}/{file_or_dir}/", recursive=recursive, excludes=excludes + children.append( + Importable(f"{path}/{_exclude_ex(file_or_dir)}", context, option) ) - ) + continue + children.append(_Package(f"{path}/{file_or_dir}/", context, option)) return children @@ -88,4 +170,4 @@ def get(path: str, *args, **kwargs): if path not in sys_path: sys_path.append(path) return _Package(path, *args, **kwargs) - return _Module(path) + return _Module(path, *args, **kwargs) diff --git a/autoload/module_loader.py b/autoload/module_loader.py index e9fa12d..c304e5a 100644 --- a/autoload/module_loader.py +++ b/autoload/module_loader.py @@ -8,7 +8,7 @@ from ._context import Context, ContextFactory from ._globals import LoadType -from ._import import ImportableFactory +from ._import import ImportableFactory, ImportOption from .exception import LoaderStrictModeError __all__ = "ModuleLoader" @@ -175,7 +175,6 @@ def __path_fix(self, name: str) -> str: break path = name[i + 1 :] level += 1 - # TODO: Error Handling if path is not None: base_path_arr = self.__base_path.split("/") result_base_path = "/".join( @@ -232,80 +231,10 @@ def __load_resources( if not isinstance(exclude, str): raise TypeError("The contents of the excludes must all be strings") exclude_files.append(exclude) - importable = ImportableFactory.get(target_dir, recursive, excludes) - mods: List[_T] = [] - decorator_attr = private.DECORATOR_ATTR context = self.__context - is_strict = self.__strict - load_type_name = context.load_type.value - for file in importable.get_children_files(): - module = import_module(file) - target_load_name = context.draw_comparison(file) - is_found = False - error = None - members = inspect.getmembers(module, context.predicate()) - for mod_name, mod in members: - is_name_match = target_load_name == mod_name - if hasattr(mod, decorator_attr): - if not getattr(mod, "_load_flg"): - continue - if is_found: - # High priority error - error = LoaderStrictModeError( - f"Loader can only load a " - f"'{target_load_name}' {load_type_name} in {file} module." - f"\nPlease check '{mod_name}' in {file} module." - ) - break - if is_strict and not is_name_match: - error = LoaderStrictModeError( - f"Loader can't load '{mod_name}' in {file} module." - f"\nPlease rename '{target_load_name}' {load_type_name}." - ) - continue - mods.append(mod) - if is_strict: - if error: - # High priority error - error = LoaderStrictModeError( - f"Loader can only load a " - f"'{target_load_name}' {load_type_name} " - f"in {file} module." - ) - break - is_found = True - continue - if not is_name_match: - continue - mods.append(mod) - if is_strict: - if error: - # High priority error - error = LoaderStrictModeError( - f"Loader can only load a " - f"'{target_load_name}' {load_type_name} in {file} module." - ) - break - is_found = True - if error is not None: - raise error - if recursive: - dirs = [ - f - for f in listdir(target_dir) - if os_path.isdir(f"{target_dir}{f}") and f not in private.EXCLUDE_DIRS - ] - if len(dirs) > 0: - for dir in dirs: - fix_pkg_name = pkg_name - if not fix_pkg_name.endswith("/"): - fix_pkg_name += "/" - recursive_mods = self.__load_resources( - fix_pkg_name + dir, - excludes=excludes, - recursive=recursive, - ) - mods += recursive_mods + import_option = ImportOption(recursive, excludes, self.__strict) + importable = ImportableFactory.get(target_dir, context, import_option) + mods: List[_T] = importable.import_resources() has_order_mods = [ mod for mod in mods From ae064554ae86ccb7992ef7e1643a334983bda283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=AE=E5=9C=B0=E5=AE=8F=E6=A8=B9?= Date: Sat, 29 May 2021 12:45:43 +0900 Subject: [PATCH 04/14] Pass test load_classes --- autoload/_import.py | 59 ++++++++++++++++++++++++--------------- autoload/module_loader.py | 38 ++++++++++--------------- 2 files changed, 51 insertions(+), 46 deletions(-) diff --git a/autoload/_import.py b/autoload/_import.py index 44e5231..02a236a 100644 --- a/autoload/_import.py +++ b/autoload/_import.py @@ -11,8 +11,12 @@ from .exception import LoaderStrictModeError +def _is_module(name: str): + return name.endswith(".py") + + def _exclude_py(path: str) -> str: - if path.endswith(".py"): + if _is_module(path): return path.replace(".py", "") return path @@ -35,40 +39,40 @@ def _flatten(iterable: Iterable[Any]) -> Iterable[Any]: @dataclass(frozen=True) class ImportOption: recursive: bool = False - excludes: Optional[Iterable[str]] = None + excludes: Iterable[str] = () is_strict: bool = False class Importable(ABC): def __init__(self, path: str, context: Context, option: ImportOption): fix_excludes = [_exclude_py(e) for e in option.excludes] - self.__path = _exclude_py(path) - self.__context = context - self.__option = option + self._path = _exclude_py(path) + self._context = context + self._option = option children = self._load_children() - self.__children: List[Importable] = [ - child for child in children if child.get_base_name() in fix_excludes + self._children: List[Importable] = [ + child for child in children if child.get_base_name() not in fix_excludes ] @property def path(self): - return self.__path + return self._path @abstractmethod def import_resources(self): raise Exception("'import_resources' method is not defined.") def get_base_name(self): - return _exclude_ex(os_path.basename(self.__path)) + return _exclude_ex(os_path.basename(self._path)) def get_all_paths(self): - return [self.__path].extend([child.path for child in self.__children]) + return [self._path].extend([child.path for child in self._children]) def get_children_files(self): - return [child.get_base_name() for child in self.__children] + return [child.get_base_name() for child in self._children] def has_children(self) -> bool: - return len(self.__children) > 0 + return len(self._children) > 0 def load_all(self, callback: Callable): children_files = self.get_children_files() @@ -85,8 +89,8 @@ def _load_children(self): class _Module(Importable): def import_resources(self): file = self.get_base_name() - context = self.__context - is_strict = self.__option.is_strict + context = self._context + is_strict = self._option.is_strict load_type_name = context.load_type.value module = import_module(file) target_load_name = context.draw_comparison(file) @@ -144,22 +148,31 @@ def import_resources(self): class _Package(Importable): def import_resources(self): - return _flatten([child.import_resources() for child in self.__children]) + return [ + resource + for child in self._children + for resource in child.import_resources() + ] def _load_children(self): - path = self.__path - context = self.__context - option = self.__option + path = self._path + option = self._option children = [] for file_or_dir in listdir(path): if file_or_dir in option.excludes: continue - if file_or_dir.endswith(".py"): - children.append( - Importable(f"{path}/{_exclude_ex(file_or_dir)}", context, option) - ) + is_module = _is_module(file_or_dir) + is_pkg = os_path.isdir(f"{path}/{file_or_dir}") + if not is_module and not is_pkg: + continue + if is_pkg and not option.recursive: continue - children.append(_Package(f"{path}/{file_or_dir}/", context, option)) + fixed_file_or_dir = _exclude_ex(file_or_dir) if is_module else file_or_dir + children.append( + ImportableFactory.get( + f"{path}/{fixed_file_or_dir}", self._context, option + ) + ) return children diff --git a/autoload/module_loader.py b/autoload/module_loader.py index c304e5a..968ffe7 100644 --- a/autoload/module_loader.py +++ b/autoload/module_loader.py @@ -1,7 +1,6 @@ import inspect import warnings from importlib import import_module -from os import listdir from os import path as os_path from sys import path as sys_path from typing import Any, Callable, Iterable, List, Optional, Tuple, Type, TypeVar @@ -9,7 +8,6 @@ from ._context import Context, ContextFactory from ._globals import LoadType from ._import import ImportableFactory, ImportOption -from .exception import LoaderStrictModeError __all__ = "ModuleLoader" @@ -20,14 +18,8 @@ class __Private: """ THIS_FILE = os_path.basename(__file__) - DEFAULT_EXCLUDES = ( - "__init__.py", - THIS_FILE, - ) + DEFAULT_EXCLUDES = ("__init__.py", THIS_FILE, "__pycache__") DECORATOR_ATTR = "_load_flg" - EXCLUDE_DIRS = { - "__pycache__", - } def __new__(cls, *args, **kwargs): raise Exception(f"{cls.__name__} can't be initialized.") @@ -100,7 +92,7 @@ def load_function(self, file_name: str) -> Callable: def load_classes( self, src: str, - excludes: Optional[Iterable[str]] = None, + excludes: Iterable[str] = (), recursive: bool = False, *args, **kwargs, @@ -124,7 +116,7 @@ def load_classes( def load_functions( self, src: str, - excludes: Optional[Iterable[str]] = None, + excludes: Iterable[str] = (), recursive: bool = False, *args, **kwargs, @@ -152,20 +144,20 @@ def __path_fix(self, name: str) -> str: result_path = self.__base_path + name # example: /foo/bar/ if name.endswith("/"): - return result_path + return result_path.rstrip("/") # example: /foo/bar - return result_path + "/" + return result_path if name.startswith("."): if name[1] != ".": if name[1] == "/": result_path = self.__base_path + name[1:] # example: ./foo/ if name.endswith("/"): - return result_path + return result_path.rstrip("/") # example: ./foo - return result_path + "/" + return result_path # example: .foo.bar - return self.__base_path + "/".join(name.split(".")) + "/" + return self.__base_path + "/".join(name.split(".")) level = 0 path = None for i in range(len(name)): @@ -183,15 +175,15 @@ def __path_fix(self, name: str) -> str: if path.startswith("/"): if path.endswith("/"): # example: ../foo/ - return result_base_path + path + return result_base_path + path.rstrip("/") # example: ../foo - return result_base_path + path + "/" + return result_base_path + path # example: ..foo.bar path = "/".join(path.split(".")) - return result_base_path + "/" + path + "/" + return result_base_path + "/" + path # example: foo.bar path = "/".join(name.split(".")) - return self.__base_path + "/" + path + "/" + return self.__base_path + "/" + path def __load_resource(self, file_name: str) -> _T: target_file = ( @@ -217,8 +209,8 @@ def __load_resource(self, file_name: str) -> _T: def __load_resources( self, pkg_name: str, - excludes: Optional[Iterable[str]] = None, - recursive: Optional[bool] = False, + excludes: Iterable[str] = (), + recursive: bool = False, ) -> Tuple[_T]: target_dir = self.__path_fix(pkg_name) private = _access_private() @@ -232,7 +224,7 @@ def __load_resources( raise TypeError("The contents of the excludes must all be strings") exclude_files.append(exclude) context = self.__context - import_option = ImportOption(recursive, excludes, self.__strict) + import_option = ImportOption(recursive, exclude_files, self.__strict) importable = ImportableFactory.get(target_dir, context, import_option) mods: List[_T] = importable.import_resources() has_order_mods = [ From d5cf5391cfce985287b8429e82a6f72ff536e388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=AE=E5=9C=B0=E5=AE=8F=E6=A8=B9?= Date: Sat, 29 May 2021 13:38:11 +0900 Subject: [PATCH 05/14] Pass all tests --- autoload/_import.py | 32 ++++++++++---------------------- autoload/module_loader.py | 22 +++------------------- 2 files changed, 13 insertions(+), 41 deletions(-) diff --git a/autoload/_import.py b/autoload/_import.py index 02a236a..41fb1db 100644 --- a/autoload/_import.py +++ b/autoload/_import.py @@ -5,7 +5,7 @@ from os import listdir from os import path as os_path from sys import path as sys_path -from typing import Any, Callable, Iterable, List, Optional +from typing import Any, Callable, Iterable, List from ._context import Context from .exception import LoaderStrictModeError @@ -25,14 +25,6 @@ def _exclude_ex(file_name: str) -> str: return os_path.splitext(file_name)[0] -def _flatten(iterable: Iterable[Any]) -> Iterable[Any]: - for el in iterable: - if isinstance(el, Iterable) and not isinstance(el, (str, bytes)): - yield from _flatten(el) - else: - yield el - - _DECORATOR_ATTR = "_load_flg" @@ -44,12 +36,14 @@ class ImportOption: class Importable(ABC): - def __init__(self, path: str, context: Context, option: ImportOption): - fix_excludes = [_exclude_py(e) for e in option.excludes] + def __init__( + self, path: str, context: Context, option: ImportOption = ImportOption() + ): self._path = _exclude_py(path) self._context = context self._option = option children = self._load_children() + fix_excludes = [_exclude_py(e) for e in option.excludes] self._children: List[Importable] = [ child for child in children if child.get_base_name() not in fix_excludes ] @@ -74,14 +68,6 @@ def get_children_files(self): def has_children(self) -> bool: return len(self._children) > 0 - def load_all(self, callback: Callable): - children_files = self.get_children_files() - if len(children_files) == 0: - return - for file in children_files: - module = import_module(file) - callback(file, module) - def _load_children(self): return [] @@ -179,8 +165,10 @@ def _load_children(self): class ImportableFactory: @staticmethod def get(path: str, *args, **kwargs): - if os_path.isdir(path): - if path not in sys_path: - sys_path.append(path) + is_dir = os_path.isdir(path) + fixed_path = path if is_dir else "/".join(path.split("/")[:-1]) + if fixed_path not in sys_path: + sys_path.append(fixed_path) + if is_dir: return _Package(path, *args, **kwargs) return _Module(path, *args, **kwargs) diff --git a/autoload/module_loader.py b/autoload/module_loader.py index 968ffe7..3ed8186 100644 --- a/autoload/module_loader.py +++ b/autoload/module_loader.py @@ -186,25 +186,9 @@ def __path_fix(self, name: str) -> str: return self.__base_path + "/" + path def __load_resource(self, file_name: str) -> _T: - target_file = ( - file_name.replace(".py", "") if file_name.endswith(".py") else file_name - ) - fix_path_arr = self.__path_fix(target_file).split("/") - target_file = fix_path_arr[-2] - target_path = "/".join(fix_path_arr[:-2]) - if target_path not in sys_path: - sys_path.append(target_path) - module = import_module(target_file) - context = self.__context - comparison = context.draw_comparison(target_file) - for mod_name, resource in inspect.getmembers(module, context.predicate()): - if hasattr(resource, _access_private().DECORATOR_ATTR) and getattr( - resource, "_load_flg" - ): - return resource - if comparison != mod_name: - continue - return resource + fix_path = self.__path_fix(file_name) + importable = ImportableFactory.get(fix_path, self.__context) + return importable.import_resources()[0] def __load_resources( self, From 2472f4f2c9fbe62ad7f81e28c3efca0d49b8f3bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=AE=E5=9C=B0=E5=AE=8F=E6=A8=B9?= Date: Sat, 29 May 2021 14:44:26 +0900 Subject: [PATCH 06/14] Add test cases --- autoload/module_loader.py | 4 +-- tests/clazz/base/test_autoload_module.py | 38 ++++++++++++++++-------- tests/func/base/test_autoload_module.py | 16 ++++++++-- 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/autoload/module_loader.py b/autoload/module_loader.py index 3ed8186..2e2c6d4 100644 --- a/autoload/module_loader.py +++ b/autoload/module_loader.py @@ -192,11 +192,11 @@ def __load_resource(self, file_name: str) -> _T: def __load_resources( self, - pkg_name: str, + src: str, excludes: Iterable[str] = (), recursive: bool = False, ) -> Tuple[_T]: - target_dir = self.__path_fix(pkg_name) + target_dir = self.__path_fix(src) private = _access_private() exclude_files = list(private.DEFAULT_EXCLUDES) exclude_files.append(os_path.basename(private.detect_call_path())) diff --git a/tests/clazz/base/test_autoload_module.py b/tests/clazz/base/test_autoload_module.py index cb96a80..67b2c0a 100644 --- a/tests/clazz/base/test_autoload_module.py +++ b/tests/clazz/base/test_autoload_module.py @@ -63,6 +63,19 @@ def test_load_class(self): with self.subTest(file_name=file_name): self.assertEqual(self.loader.load_class(file_name)(), expected) + def test_load_classes_by_module(self): + test_cases = ( + ("module_1", {Module1()}), + (".packageD.not_load", set()), + (".packageD.module_d1", {ModuleD1(), ModuleD2(), ModuleD3()}), + (".packageD.module_d2", {ModuleD4(), ModuleD5()}), + ) + for pkg_name, expected in test_cases: + with self.subTest(pkg_name=pkg_name): + classes = self.loader.load_classes(pkg_name) + instances = set([clazz() for clazz in classes]) + self.assertSetEqual(instances, expected) + def test_load_classes_exclude(self): # Module2 tagged 'load = False' basepkg_result = {Module3(), Module1()} @@ -82,12 +95,12 @@ def test_load_classes_exclude(self): def test_load_classes_complex_path_load(self): pkgB_result = {ModuleB3(), ModuleB2(), CustomModuleB1()} test_cases = ( - ("../packageA/packageB", None, pkgB_result), - ("..packageA.packageB", None, pkgB_result), + ("../packageA/packageB", pkgB_result), + ("..packageA.packageB", pkgB_result), ) - for pkg_name, exclude, expected in test_cases: - with self.subTest(pkg_name=pkg_name, exclude=exclude): - classes = self.loader.load_classes(pkg_name, exclude) + for pkg_name, expected in test_cases: + with self.subTest(pkg_name=pkg_name): + classes = self.loader.load_classes(pkg_name) instances = set([clazz() for clazz in classes]) self.assertSetEqual(instances, expected) @@ -95,13 +108,13 @@ def test_load_classes_partial_order(self): # Only ModuleA1 has order. pkgA_result = (ModuleA2(), ModuleA3(), ModuleA1()) test_cases = ( - ("../packageA", None, pkgA_result), - ("../packageA/", None, pkgA_result), - ("..packageA", None, pkgA_result), + ("../packageA", pkgA_result), + ("../packageA/", pkgA_result), + ("..packageA", pkgA_result), ) - for pkg_name, exclude, expected in test_cases: - with self.subTest(pkg_name=pkg_name, exclude=exclude): - classes = self.loader.load_classes(pkg_name, exclude) + for pkg_name, expected in test_cases: + with self.subTest(pkg_name=pkg_name): + classes = self.loader.load_classes(pkg_name) instances = [clazz() for clazz in classes] if not instances[0] == expected[0]: self.fail() @@ -186,8 +199,7 @@ def test_strict_mode_raise_error(self): self.loader = ModuleLoader(strict=True) test_cases = ( "packageD", - # 'load_classes' will be able to load not only package but also module. - # "packageD.module_d1" + "packageD.module_d1" ) for pkg_name in test_cases: with self.assertRaises(LoaderStrictModeError): diff --git a/tests/func/base/test_autoload_module.py b/tests/func/base/test_autoload_module.py index 4e95f78..8bdd4b4 100644 --- a/tests/func/base/test_autoload_module.py +++ b/tests/func/base/test_autoload_module.py @@ -47,6 +47,19 @@ def test_load_function(self): with self.subTest(file_name=file_name): self.assertEqual(self.loader.load_function(file_name)(), expected) + def test_load_functions_by_module(self): + test_cases = ( + ("func1", {func1()}), + (".packageD.not_load", set()), + (".packageD.package_d_func1", {package_d_func1(), multiple2(), multiple3()}), + (".packageD.package_d_func2", {multiple4(), multiple5()}), + ) + for pkg_name, expected in test_cases: + with self.subTest(pkg_name=pkg_name): + classes = self.loader.load_functions(pkg_name) + instances = set([clazz() for clazz in classes]) + self.assertSetEqual(instances, expected) + def test_load_functions_exclude(self): basepkg_result = {func3(), func2(), func1()} test_cases = ( @@ -158,8 +171,7 @@ def test_strict_mode_raise_error(self): self.loader = ModuleLoader(strict=True) test_cases = ( "packageD", - # 'load_classes' will be able to load not only package but also module. - # "packageD.module_d1" + "packageD.package_d_func1" ) for pkg_name in test_cases: with self.assertRaises(LoaderStrictModeError): From de38b918bad8c06afb28a04a2a0551bfafc62326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=AE=E5=9C=B0=E5=AE=8F=E6=A8=B9?= Date: Sat, 29 May 2021 14:44:54 +0900 Subject: [PATCH 07/14] Version Up --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6a6a139..d200e2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "autoload-module" -version = "1.4.0" +version = "1.5.0" description = "Python Autoload Module" authors = ["Hiroki Miyaji "] license = "MIT" From 565666e4cab15db684533691db7ad1a4c4faa54d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=AE=E5=9C=B0=E5=AE=8F=E6=A8=B9?= Date: Sat, 29 May 2021 15:06:58 +0900 Subject: [PATCH 08/14] Fix README and Add tests. --- README.md | 19 +++++++++++++------ autoload/module_loader.py | 8 ++++++-- tests/clazz/base/test_autoload_module.py | 6 +++--- tests/func/base/test_autoload_module.py | 6 +++--- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index ff8e5db..27504d0 100644 --- a/README.md +++ b/README.md @@ -75,12 +75,19 @@ About strict parameter, please see [here](#NOTE) . #### load_classes ``` load_classes( - pkg_name: str, - excludes: Optional[Iterable[str]] = None, + src: str, + excludes: Iterable[str] = (), recursive: bool = False, ) -> Tuple[Type]: ``` -This method read the Python package and return the tuple of class objects. +This method read the Python package or module and return the tuple of class objects. + +**NOTE** + +From version 1.5.0, `pkg_name` parameter is duplicated. +It will be deleted soon. +`load_functions` is also same. + - Directory ``` pkg/ @@ -168,12 +175,12 @@ loader.load_classes("..otherpkg") #### load_functions ``` load_functions( - pkg_name: str, - excludes: Optional[Iterable[str]] = None, + src: str, + excludes: Iterable[str] = (), recursive: bool = False, ) -> Tuple[Callable]: ``` -This method read the Python package and return the tuple of functions. +This method read the Python package or module and return the tuple of functions. The usage is the same as `load_classes`. ##### NOTE diff --git a/autoload/module_loader.py b/autoload/module_loader.py index 2e2c6d4..aba0ef8 100644 --- a/autoload/module_loader.py +++ b/autoload/module_loader.py @@ -91,7 +91,7 @@ def load_function(self, file_name: str) -> Callable: def load_classes( self, - src: str, + src: Optional[str] = None, # Temporary Optional because of pkg_name excludes: Iterable[str] = (), recursive: bool = False, *args, @@ -110,12 +110,14 @@ def load_classes( "'pkg_name' is deprecated. Please use 'src' parameter.", FutureWarning ) src = pkg_name + if src is None: + raise TypeError("'src' parameter is required.") self.__context = ContextFactory.get(LoadType.clazz) return self.__load_resources(src, excludes=excludes, recursive=recursive) def load_functions( self, - src: str, + src: Optional[str] = None, # Temporary Optional because of pkg_name excludes: Iterable[str] = (), recursive: bool = False, *args, @@ -134,6 +136,8 @@ def load_functions( "'pkg_name' is deprecated. Please use 'src' parameter.", FutureWarning ) src = pkg_name + if src is None: + raise TypeError("'src' parameter is required.") self.__context = ContextFactory.get(LoadType.func) return self.__load_resources(src, excludes=excludes, recursive=recursive) diff --git a/tests/clazz/base/test_autoload_module.py b/tests/clazz/base/test_autoload_module.py index 67b2c0a..1078a85 100644 --- a/tests/clazz/base/test_autoload_module.py +++ b/tests/clazz/base/test_autoload_module.py @@ -86,9 +86,9 @@ def test_load_classes_exclude(self): (".", ["module_3", "module_2"], {Module1()}), (".", ("module_3", "module_2"), {Module1()}), ) - for pkg_name, exclude, expected in test_cases: - with self.subTest(pkg_name=pkg_name, exclude=exclude): - classes = self.loader.load_classes(pkg_name, exclude) + for pkg_name, excludes, expected in test_cases: + with self.subTest(pkg_name=pkg_name, excludes=excludes): + classes = self.loader.load_classes(pkg_name=pkg_name, excludes=excludes) instances = set([clazz() for clazz in classes]) self.assertSetEqual(instances, expected) diff --git a/tests/func/base/test_autoload_module.py b/tests/func/base/test_autoload_module.py index 8bdd4b4..7b89b49 100644 --- a/tests/func/base/test_autoload_module.py +++ b/tests/func/base/test_autoload_module.py @@ -69,9 +69,9 @@ def test_load_functions_exclude(self): (".", ["func3", "func2"], {func1()}), (".", ("func3", "func2"), {func1()}), ) - for pkg_name, exclude, expected in test_cases: - with self.subTest(pkg_name=pkg_name, exclude=exclude): - functions = self.loader.load_functions(pkg_name, exclude) + for pkg_name, excludes, expected in test_cases: + with self.subTest(pkg_name=pkg_name, excludes=excludes): + functions = self.loader.load_functions(pkg_name=pkg_name, excludes=excludes) results = set([function() for function in functions]) self.assertSetEqual(results, expected) From a42f401d606e3102df1e9295e0c0774f7c9f6669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=AE=E5=9C=B0=E5=AE=8F=E6=A8=B9?= Date: Sat, 29 May 2021 17:18:23 +0900 Subject: [PATCH 09/14] Remove unused import statement --- autoload/_import.py | 2 +- autoload/module_loader.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/autoload/_import.py b/autoload/_import.py index 41fb1db..4626eac 100644 --- a/autoload/_import.py +++ b/autoload/_import.py @@ -5,7 +5,7 @@ from os import listdir from os import path as os_path from sys import path as sys_path -from typing import Any, Callable, Iterable, List +from typing import Iterable, List from ._context import Context from .exception import LoaderStrictModeError diff --git a/autoload/module_loader.py b/autoload/module_loader.py index aba0ef8..e2b1508 100644 --- a/autoload/module_loader.py +++ b/autoload/module_loader.py @@ -1,8 +1,6 @@ import inspect import warnings -from importlib import import_module from os import path as os_path -from sys import path as sys_path from typing import Any, Callable, Iterable, List, Optional, Tuple, Type, TypeVar from ._context import Context, ContextFactory From b47b405e23e82d86519611f647a286bb128c1f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=AE=E5=9C=B0=E5=AE=8F=E6=A8=B9?= Date: Sat, 29 May 2021 17:19:51 +0900 Subject: [PATCH 10/14] Remove unused variable --- autoload/module_loader.py | 1 - 1 file changed, 1 deletion(-) diff --git a/autoload/module_loader.py b/autoload/module_loader.py index e2b1508..68a4101 100644 --- a/autoload/module_loader.py +++ b/autoload/module_loader.py @@ -17,7 +17,6 @@ class __Private: THIS_FILE = os_path.basename(__file__) DEFAULT_EXCLUDES = ("__init__.py", THIS_FILE, "__pycache__") - DECORATOR_ATTR = "_load_flg" def __new__(cls, *args, **kwargs): raise Exception(f"{cls.__name__} can't be initialized.") From b08d3684e6cf97dce6fec7f52cd2b4daecb6ebd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=AE=E5=9C=B0=E5=AE=8F=E6=A8=B9?= Date: Sat, 29 May 2021 17:34:35 +0900 Subject: [PATCH 11/14] Add type hints --- autoload/_globals.py | 4 ++++ autoload/_import.py | 28 ++++++++-------------------- autoload/module_loader.py | 13 +++++-------- 3 files changed, 17 insertions(+), 28 deletions(-) diff --git a/autoload/_globals.py b/autoload/_globals.py index 212fc0a..68e2416 100644 --- a/autoload/_globals.py +++ b/autoload/_globals.py @@ -1,6 +1,10 @@ from enum import Enum +from typing import Any, Callable, Type, TypeVar class LoadType(Enum): func = "function" clazz = "class" + + +Class_Or_Func = TypeVar("Class_Or_Func", Type[Any], Callable) diff --git a/autoload/_import.py b/autoload/_import.py index 4626eac..00ac237 100644 --- a/autoload/_import.py +++ b/autoload/_import.py @@ -8,10 +8,11 @@ from typing import Iterable, List from ._context import Context +from ._globals import Class_Or_Func from .exception import LoaderStrictModeError -def _is_module(name: str): +def _is_module(name: str) -> bool: return name.endswith(".py") @@ -48,32 +49,19 @@ def __init__( child for child in children if child.get_base_name() not in fix_excludes ] - @property - def path(self): - return self._path - @abstractmethod - def import_resources(self): + def import_resources(self) -> List[Class_Or_Func]: raise Exception("'import_resources' method is not defined.") - def get_base_name(self): + def get_base_name(self) -> str: return _exclude_ex(os_path.basename(self._path)) - def get_all_paths(self): - return [self._path].extend([child.path for child in self._children]) - - def get_children_files(self): - return [child.get_base_name() for child in self._children] - - def has_children(self) -> bool: - return len(self._children) > 0 - def _load_children(self): return [] class _Module(Importable): - def import_resources(self): + def import_resources(self) -> List[Class_Or_Func]: file = self.get_base_name() context = self._context is_strict = self._option.is_strict @@ -133,14 +121,14 @@ def import_resources(self): class _Package(Importable): - def import_resources(self): + def import_resources(self) -> List[Class_Or_Func]: return [ resource for child in self._children for resource in child.import_resources() ] - def _load_children(self): + def _load_children(self) -> List[Importable]: path = self._path option = self._option children = [] @@ -164,7 +152,7 @@ def _load_children(self): class ImportableFactory: @staticmethod - def get(path: str, *args, **kwargs): + def get(path: str, *args, **kwargs) -> Importable: is_dir = os_path.isdir(path) fixed_path = path if is_dir else "/".join(path.split("/")[:-1]) if fixed_path not in sys_path: diff --git a/autoload/module_loader.py b/autoload/module_loader.py index 68a4101..f1155bd 100644 --- a/autoload/module_loader.py +++ b/autoload/module_loader.py @@ -1,10 +1,10 @@ import inspect import warnings from os import path as os_path -from typing import Any, Callable, Iterable, List, Optional, Tuple, Type, TypeVar +from typing import Callable, Iterable, List, Optional, Tuple, Type from ._context import Context, ContextFactory -from ._globals import LoadType +from ._globals import Class_Or_Func, LoadType from ._import import ImportableFactory, ImportOption __all__ = "ModuleLoader" @@ -48,9 +48,6 @@ def _access_private(): return __Private -_T = TypeVar("_T", Type[Any], Callable) - - class ModuleLoader: def __init__(self, base_path: Optional[str] = None, strict: bool = False): """initialize @@ -186,7 +183,7 @@ def __path_fix(self, name: str) -> str: path = "/".join(name.split(".")) return self.__base_path + "/" + path - def __load_resource(self, file_name: str) -> _T: + def __load_resource(self, file_name: str) -> Class_Or_Func: fix_path = self.__path_fix(file_name) importable = ImportableFactory.get(fix_path, self.__context) return importable.import_resources()[0] @@ -196,7 +193,7 @@ def __load_resources( src: str, excludes: Iterable[str] = (), recursive: bool = False, - ) -> Tuple[_T]: + ) -> Tuple[Class_Or_Func]: target_dir = self.__path_fix(src) private = _access_private() exclude_files = list(private.DEFAULT_EXCLUDES) @@ -211,7 +208,7 @@ def __load_resources( context = self.__context import_option = ImportOption(recursive, exclude_files, self.__strict) importable = ImportableFactory.get(target_dir, context, import_option) - mods: List[_T] = importable.import_resources() + mods: List[Class_Or_Func] = importable.import_resources() has_order_mods = [ mod for mod in mods From 8ee1001ec459ccfaef2e95e275136f2e185d195f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=AE=E5=9C=B0=E5=AE=8F=E6=A8=B9?= Date: Sat, 29 May 2021 17:36:42 +0900 Subject: [PATCH 12/14] Test mypy --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4942d10..40d7f3b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: rev: 3.9.2 hooks: - id: flake8 -# - repo: https://github.com/pre-commit/mirrors-mypy -# rev: v0.812 -# hooks: -# - id: mypy \ No newline at end of file + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.812 + hooks: + - id: mypy \ No newline at end of file From b985d9e0d44f586aec1c658ef509b24088597c2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=AE=E5=9C=B0=E5=AE=8F=E6=A8=B9?= Date: Sat, 29 May 2021 17:55:58 +0900 Subject: [PATCH 13/14] Add type hints --- autoload/_import.py | 2 +- autoload/decorator.py | 10 +++++++--- autoload/module_loader.py | 6 +++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/autoload/_import.py b/autoload/_import.py index 00ac237..50c898d 100644 --- a/autoload/_import.py +++ b/autoload/_import.py @@ -56,7 +56,7 @@ def import_resources(self) -> List[Class_Or_Func]: def get_base_name(self) -> str: return _exclude_ex(os_path.basename(self._path)) - def _load_children(self): + def _load_children(self) -> List["Importable"]: return [] diff --git a/autoload/decorator.py b/autoload/decorator.py index a3bf955..22978ff 100644 --- a/autoload/decorator.py +++ b/autoload/decorator.py @@ -1,10 +1,14 @@ __all__ = "load_config" -from typing import Optional +from typing import Callable, Optional +from autoload._globals import Class_Or_Func -def load_config(order: Optional[int] = None, load: bool = True): - def decorator(resource): + +def load_config( + order: Optional[int] = None, load: bool = True +) -> Callable[[Class_Or_Func], Class_Or_Func]: + def decorator(resource: Class_Or_Func): if order: setattr(resource, "_load_order", order) setattr(resource, "_load_flg", load) diff --git a/autoload/module_loader.py b/autoload/module_loader.py index f1155bd..080e3c9 100644 --- a/autoload/module_loader.py +++ b/autoload/module_loader.py @@ -90,7 +90,7 @@ def load_classes( recursive: bool = False, *args, **kwargs, - ) -> Tuple[Type]: + ) -> Tuple[Type, ...]: """Import Python package and return classes. :param src: Python package or module name. You can input relative path like '../example' based on 'base_path'. @@ -116,7 +116,7 @@ def load_functions( recursive: bool = False, *args, **kwargs, - ) -> Tuple[Callable]: + ) -> Tuple[Callable, ...]: """Import Python package and return functions. :param src: Python package or module name. You can input relative path like '../example' based on 'base_path'. @@ -193,7 +193,7 @@ def __load_resources( src: str, excludes: Iterable[str] = (), recursive: bool = False, - ) -> Tuple[Class_Or_Func]: + ) -> Tuple[Class_Or_Func, ...]: target_dir = self.__path_fix(src) private = _access_private() exclude_files = list(private.DEFAULT_EXCLUDES) From b18145efe16abcdb083a5205258d09c9a5f25b21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=AE=E5=9C=B0=E5=AE=8F=E6=A8=B9?= Date: Sat, 29 May 2021 20:03:04 +0900 Subject: [PATCH 14/14] Minor fix --- autoload/_import.py | 2 +- autoload/module_loader.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/autoload/_import.py b/autoload/_import.py index 50c898d..f45f649 100644 --- a/autoload/_import.py +++ b/autoload/_import.py @@ -75,7 +75,7 @@ def import_resources(self) -> List[Class_Or_Func]: for mod_name, mod in members: is_name_match = target_load_name == mod_name if hasattr(mod, _DECORATOR_ATTR): - if not getattr(mod, "_load_flg"): + if not getattr(mod, _DECORATOR_ATTR): continue if is_found: # High priority error diff --git a/autoload/module_loader.py b/autoload/module_loader.py index 080e3c9..ac7119b 100644 --- a/autoload/module_loader.py +++ b/autoload/module_loader.py @@ -142,7 +142,7 @@ def __path_fix(self, name: str) -> str: result_path = self.__base_path + name # example: /foo/bar/ if name.endswith("/"): - return result_path.rstrip("/") + return result_path[:-1] # example: /foo/bar return result_path if name.startswith("."): @@ -151,7 +151,7 @@ def __path_fix(self, name: str) -> str: result_path = self.__base_path + name[1:] # example: ./foo/ if name.endswith("/"): - return result_path.rstrip("/") + return result_path[:-1] # example: ./foo return result_path # example: .foo.bar @@ -173,7 +173,7 @@ def __path_fix(self, name: str) -> str: if path.startswith("/"): if path.endswith("/"): # example: ../foo/ - return result_base_path + path.rstrip("/") + return result_base_path + path[:-1] # example: ../foo return result_base_path + path # example: ..foo.bar