-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #13 from hiroki0525/develop
Develop
- Loading branch information
Showing
10 changed files
with
294 additions
and
182 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
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 Iterable, List | ||
|
||
from ._context import Context | ||
from ._globals import Class_Or_Func | ||
from .exception import LoaderStrictModeError | ||
|
||
|
||
def _is_module(name: str) -> bool: | ||
return name.endswith(".py") | ||
|
||
|
||
def _exclude_py(path: str) -> str: | ||
if _is_module(path): | ||
return path.replace(".py", "") | ||
return path | ||
|
||
|
||
def _exclude_ex(file_name: str) -> str: | ||
return os_path.splitext(file_name)[0] | ||
|
||
|
||
_DECORATOR_ATTR = "_load_flg" | ||
|
||
|
||
@dataclass(frozen=True) | ||
class ImportOption: | ||
recursive: bool = False | ||
excludes: Iterable[str] = () | ||
is_strict: bool = False | ||
|
||
|
||
class Importable(ABC): | ||
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 | ||
] | ||
|
||
@abstractmethod | ||
def import_resources(self) -> List[Class_Or_Func]: | ||
raise Exception("'import_resources' method is not defined.") | ||
|
||
def get_base_name(self) -> str: | ||
return _exclude_ex(os_path.basename(self._path)) | ||
|
||
def _load_children(self) -> List["Importable"]: | ||
return [] | ||
|
||
|
||
class _Module(Importable): | ||
def import_resources(self) -> List[Class_Or_Func]: | ||
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, _DECORATOR_ATTR): | ||
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 import_resources(self) -> List[Class_Or_Func]: | ||
return [ | ||
resource | ||
for child in self._children | ||
for resource in child.import_resources() | ||
] | ||
|
||
def _load_children(self) -> List[Importable]: | ||
path = self._path | ||
option = self._option | ||
children = [] | ||
for file_or_dir in listdir(path): | ||
if file_or_dir in option.excludes: | ||
continue | ||
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 | ||
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 | ||
|
||
|
||
class ImportableFactory: | ||
@staticmethod | ||
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: | ||
sys_path.append(fixed_path) | ||
if is_dir: | ||
return _Package(path, *args, **kwargs) | ||
return _Module(path, *args, **kwargs) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.