diff --git a/src/saleyo/__init__.py b/src/saleyo/__init__.py index d147d2d..4df58dd 100644 --- a/src/saleyo/__init__.py +++ b/src/saleyo/__init__.py @@ -14,9 +14,9 @@ https://pypi.org/project/saleyo/ """ -from . import operation as operation from . import base as base from . import mixin as mixin +from . import operation as operation from .mixin import Mixin as Mixin from .operation import Accessor as Accessor from .operation import Processor as Processor diff --git a/src/saleyo/base/typing.py b/src/saleyo/base/typing.py index 0d3adc6..00b296d 100644 --- a/src/saleyo/base/typing.py +++ b/src/saleyo/base/typing.py @@ -1,13 +1,8 @@ -from typing import Any, Dict, List, ParamSpec, Type, TypeVar, Union +from typing import Any, Dict, Iterable, ParamSpec, TypeVar, Union + + +# Generic -Target = Union[Type[Any], List[Type[Any]]] -""" -`Target` is the target of `@Mixin`, it's the alias of `Union[Type[Any], List[Type[Any]]]` -""" -NameSpace = Dict[str, Any] -""" -`NameSpace` is the alias of `Dict[str, Any]` -""" RT = TypeVar("RT") """ `RT` means `Return Type` @@ -20,3 +15,15 @@ """ `P` means `Params` """ + +# Alias + +NameSpace = Dict[str, Any] +""" +`NameSpace` is the alias of `Dict[str, Any]` +""" + +IterableOrSingle = Union[T, Iterable[T]] +""" +`IterableOrSingle[T]` is the alias of `Union[T, List[T]]` +""" \ No newline at end of file diff --git a/src/saleyo/mixin.py b/src/saleyo/mixin.py index c4e7281..1ed5a23 100644 --- a/src/saleyo/mixin.py +++ b/src/saleyo/mixin.py @@ -1,8 +1,8 @@ -from typing import List +from typing import Any, Iterable, List, Type from .base.template import MixinOperation from .base.toolchain import ToolChain -from .base.typing import T, Target +from .base.typing import T, IterableOrSingle class Mixin: @@ -14,20 +14,91 @@ class Mixin: Allow to have more than one target, but that's not recommended. """ - target: Target + target: IterableOrSingle[Type[Any]] toolchain: ToolChain reverse_level: bool def __init__( self, - target: Target, + target: IterableOrSingle, toolchain: ToolChain = ToolChain(), reverse_level: bool = False, ) -> None: - self.target = target if isinstance(target, list) else [target] + self.target = target if isinstance(target, Iterable) else [target] self.toolchain = toolchain self.reverse_level = reverse_level + @staticmethod + def from_name( + target: IterableOrSingle[str], + toolchain: ToolChain = ToolChain(), + reverse_level: bool = False, + qualname: bool = False, + ) -> "Mixin": + """ + Will find target classes from `object.__subclasses__()` from class name or qualname. + + If want to find a class named `Foo`, default use the `Foo` to match, it will use `module.to.Foo` to match when `qualname` enabled. + + Please use this after the definition of target class. + + The method may takes lots of time when there are a whole lot classes, recommand to use `@Mixin()` directly if you can. + """ + + target = target if isinstance(target, Iterable) else [target] + + return Mixin( + list( + filter( + lambda clazz: clazz.__name__ in target + if qualname + else clazz.__qualname__ in target, + object.__subclasses__(), + ) + ), + toolchain=toolchain, + reverse_level=reverse_level, + ) + + @staticmethod + def from_regex( + pattern: str, + pattern_flags: int = 0, + toolchain: ToolChain = ToolChain(), + reverse_level: bool = False, + qualname: bool = False, + full_match: bool = False, + ) -> "Mixin": + """ + Will use regex pattern to find target classes from the `object.__subclasses__()`. + + The `pattern` will convert to a `Pattern[str]` via `re.complie` and you can provide flags in `pattern_flags`. + + If want to find a class named `Foo`, default use the `Foo` to match, it will use `module.to.Foo` to match when `qualname` enabled. + + Please use this after the definition of target class. + + The method may takes lots of time when there are a whole lot classes, recommand to use `@Mixin()` directly if you can. + """ + import re + + regex_pattern = re.compile(pattern=pattern, flags=pattern_flags) + matcher = regex_pattern.fullmatch if full_match else regex_pattern.match + + return Mixin( + list( + filter( + lambda clazz: matcher( + clazz.__name__ if qualname else clazz.__qualname__ + ) + is not None, + object.__subclasses__(), + ) + ), + toolchain=toolchain, + reverse_level=reverse_level, + ) + def collect(self, mixin: T) -> T: members: List[MixinOperation] = sorted( filter(