Skip to content

Commit

Permalink
Merge pull request #14 from hiroki0525/feature/global-setting
Browse files Browse the repository at this point in the history
Feature/global setting
  • Loading branch information
hiroki0525 authored Jun 6, 2021
2 parents 96b17c0 + e5c6924 commit 073d8bb
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 21 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,23 @@ loader = ModuleLoader('/user/local/src/custom')

About strict parameter, please see [here](#NOTE) .

You can also create global setting.
```python
from autoload import ModuleLoader
import os

# global setting
ModuleLoader.set_setting(base_path=os.getcwd(), strict=True)

loader_a = ModuleLoader()
loader_b = ModuleLoader()

print(loader_a.base_path)
# -> /Users/user1/abc
print(loader_b.base_path)
# -> /Users/user1/abc
```

### Methods
#### load_classes
```
Expand Down
3 changes: 2 additions & 1 deletion autoload/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from .decorator import load_config
from .module_loader import ModuleLoader
from .module_loader import ModuleLoader, ModuleLoaderSetting

__all__ = (
"load_config",
"ModuleLoader",
"ModuleLoaderSetting",
)
5 changes: 4 additions & 1 deletion autoload/_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@ class ImportOption:
is_strict: bool = False


default_option = ImportOption()


class Importable(ABC):
def __init__(
self, path: str, context: Context, option: ImportOption = ImportOption()
self, path: str, context: Context, option: ImportOption = default_option
):
self._path = _exclude_py(path)
self._context = context
Expand Down
66 changes: 49 additions & 17 deletions autoload/module_loader.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import inspect
import warnings
from dataclasses import dataclass
from os import path as os_path
from typing import Callable, Iterable, List, Optional, Tuple, Type
from typing import Callable, ClassVar, Iterable, List, Optional, Tuple, Type

from ._context import Context, ContextFactory
from ._globals import Class_Or_Func, LoadType
from ._import import ImportableFactory, ImportOption

__all__ = "ModuleLoader"
__all__ = ("ModuleLoader", "ModuleLoaderSetting")


class __Private:
Expand Down Expand Up @@ -48,40 +49,63 @@ def _access_private():
return __Private


@dataclass(frozen=True)
class ModuleLoaderSetting:
base_path: Optional[str] = None
strict: bool = False


class ModuleLoader:
def __init__(self, base_path: Optional[str] = None, strict: bool = False):
_setting: ClassVar[ModuleLoaderSetting] = ModuleLoaderSetting()

@classmethod
def get_setting(cls) -> ModuleLoaderSetting:
return cls._setting

@classmethod
def set_setting(cls, base_path: Optional[str] = None, strict: bool = False) -> None:
cls._setting = ModuleLoaderSetting(base_path, strict)

def __init__(self, base_path: Optional[str] = None, strict: Optional[bool] = None):
"""initialize
:param base_path: Base path for import.
Defaults to the path where this object was initialized.
:param strict: If strict is True,
ModuleLoader strictly try to load a class or function object
per a Python module on a basis of its name.
"""
self.__base_path: str = _access_private().init_base_url(base_path)
self.__context: Context = ContextFactory.get(LoadType.clazz)
self.__strict: bool = strict
setting = ModuleLoader._setting
global_base_path, global_strict = setting.base_path, setting.strict
self.__base_path: str = (
_access_private().init_base_url(base_path)
if global_base_path is None
else global_base_path
)
self.__strict: bool = global_strict if strict is None else strict

@property
def base_path(self) -> str:
return self.__base_path

@property
def strict(self) -> bool:
return self.__strict

def load_class(self, file_name: str) -> Type:
"""Import Python module and return class.
:param file_name: Python file name (Module name).
You can input relative path like '../example' based on 'base_path'.
:return: class object defined in the Python file (Module) according to rules.
"""
self.__context = ContextFactory.get(LoadType.clazz)
return self.__load_resource(file_name)
return self.__load_resource(file_name, ContextFactory.get(LoadType.clazz))

def load_function(self, file_name: str) -> Callable:
"""Import Python module and return function.
:param file_name: Python file name (module name).
You can input relative path like '../example' based on 'base_path'.
:return: function object defined in the Python file (Module) according to rules.
"""
self.__context = ContextFactory.get(LoadType.func)
return self.__load_resource(file_name)
return self.__load_resource(file_name, ContextFactory.get(LoadType.func))

def load_classes(
self,
Expand All @@ -106,8 +130,12 @@ def load_classes(
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)
return self.__load_resources(
src,
excludes=excludes,
recursive=recursive,
context=ContextFactory.get(LoadType.clazz),
)

def load_functions(
self,
Expand All @@ -132,8 +160,12 @@ def load_functions(
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)
return self.__load_resources(
src,
excludes=excludes,
recursive=recursive,
context=ContextFactory.get(LoadType.func),
)

def __path_fix(self, name: str) -> str:
if not name or name == "." or name == "/" or name == "./":
Expand Down Expand Up @@ -183,14 +215,15 @@ def __path_fix(self, name: str) -> str:
path = "/".join(name.split("."))
return self.__base_path + "/" + path

def __load_resource(self, file_name: str) -> Class_Or_Func:
def __load_resource(self, file_name: str, context: Context) -> Class_Or_Func:
fix_path = self.__path_fix(file_name)
importable = ImportableFactory.get(fix_path, self.__context)
importable = ImportableFactory.get(fix_path, context)
return importable.import_resources()[0]

def __load_resources(
self,
src: str,
context: Context,
excludes: Iterable[str] = (),
recursive: bool = False,
) -> Tuple[Class_Or_Func, ...]:
Expand All @@ -205,7 +238,6 @@ def __load_resources(
if not isinstance(exclude, str):
raise TypeError("The contents of the excludes must all be strings")
exclude_files.append(exclude)
context = self.__context
import_option = ImportOption(recursive, exclude_files, self.__strict)
importable = ImportableFactory.get(target_dir, context, import_option)
mods: List[Class_Or_Func] = importable.import_resources()
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "autoload-module"
version = "1.5.0"
version = "1.6.0"
description = "Python Autoload Module"
authors = ["Hiroki Miyaji <[email protected]>"]
license = "MIT"
Expand Down
18 changes: 17 additions & 1 deletion tests/clazz/base/test_autoload_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,25 @@
class TestAutoLoadModule(unittest.TestCase):

def setUp(self):
print('setup')
self.loader = ModuleLoader()

def tearDown(self) -> None:
ModuleLoader.set_setting()

def test_global_setting(self):
default = self.loader
test_cases = (
((), (default.base_path, default.strict)),
(('/',), ('/', default.strict)),
((None, True), (default.base_path, True)),
(('/', False), ('/', False)),
)
for setting, expected in test_cases:
with self.subTest(setting=setting):
ModuleLoader.set_setting(*setting)
test_loader = ModuleLoader()
self.assertTupleEqual((test_loader.base_path, test_loader.strict), expected)

def test_initialize(self):
test_cases = (
(ModuleLoader('').base_path, '/'),
Expand Down

0 comments on commit 073d8bb

Please sign in to comment.