diff --git a/README.md b/README.md index 63816ad..86ae1e0 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ class Foo: pass -@Mixin(target=Foo) +@Mixin(target = Foo) class MixinFoo: # Will add a varible named `__private` to Foo and it has the same address with `_Foo__private` private: Accessor[str] = Accessor("__private") @@ -112,10 +112,10 @@ The default operations can't satify you? Why not try define a operation yourself ```python from typing import Any from saleyo import MixinOperation, ToolChain -from saleyo.base.typing import MixinAble +from saleyo.base.typing import M -class MyOperation(MixinOperation[Any]): - def mixin(self, target: MixinAble, toolchain: ToolChain = ...) -> None: +class MyOperation(MixinOperation[Any, M]): + def mixin(self, target: M, toolchain: ToolChain = ...) -> None: ... ``` diff --git a/src/saleyo/base/template.py b/src/saleyo/base/template.py index 7c3d90f..9bba89f 100644 --- a/src/saleyo/base/template.py +++ b/src/saleyo/base/template.py @@ -1,17 +1,19 @@ from abc import ABC -from typing import Generic +from typing import Any, Generic from .toolchain import ToolChain -from .typing import T, MixinAble +from .typing import T, M -class MixinOperation(Generic[T], ABC): +class MixinOperation(Generic[T, M], ABC): """ The MixinOperation is the base of All Operation. The generic `MixinOperation` is the type of argument. - `level` will affect to the mixin order, default to `1`. + `level` will affect to the mixin order, default `1`. + + If you call the `MixinOperation` or call the subclass of this, it will call the `MixinOperation.argument` """ argument: T @@ -21,7 +23,9 @@ def __init__(self, argument: T, level=1) -> None: self.argument = argument self.level = level - def mixin(self, target: MixinAble, toolchain: ToolChain = ToolChain()) -> None: + def mixin(self, target: M, toolchain: ToolChain = ToolChain()) -> None: raise NotImplementedError( f"Not Ready to use this Operation to modify '{target}' via '{toolchain}'" ) + + def __call__(self, *args: Any, **kwargs: Any) -> Any: ... diff --git a/src/saleyo/base/typing.py b/src/saleyo/base/typing.py index fd5383c..6c51af3 100644 --- a/src/saleyo/base/typing.py +++ b/src/saleyo/base/typing.py @@ -17,6 +17,14 @@ `P` means `Params` """ +M = TypeVar("M", Type[Any], ModuleType, Any) +""" +These can be the target of mixin. + +Not recommend input Any. +""" + + # Alias NameSpace = Dict[str, Any] @@ -28,8 +36,3 @@ """ `IterableOrSingle[T]` is the alias of `Union[T, List[T]]` """ - -MixinAble = Union[Type[Any], ModuleType] -""" -These can be the target of mixin. -""" diff --git a/src/saleyo/mixin.py b/src/saleyo/mixin.py index 4d3e546..806e3cd 100644 --- a/src/saleyo/mixin.py +++ b/src/saleyo/mixin.py @@ -1,11 +1,11 @@ -from typing import Iterable, List +from typing import Generic, Iterable, List, Union from .base.template import MixinOperation from .base.toolchain import ToolChain -from .base.typing import T, IterableOrSingle, MixinAble +from .base.typing import M, T, IterableOrSingle -class Mixin: +class Mixin(Generic[M]): """ A `Mixin` Decorator is used to invoke all the `MixinOperation` in Mixin Class. @@ -14,13 +14,13 @@ class Mixin: Allow to have more than one target, but that's not recommended. """ - target: Iterable[MixinAble] + target: Iterable[M] toolchain: ToolChain reverse_level: bool def __init__( self, - target: IterableOrSingle[MixinAble], + target: IterableOrSingle[M], toolchain: ToolChain = ToolChain(), reverse_level: bool = False, ) -> None: @@ -139,5 +139,5 @@ def apply_from_operations( for target in self.target: operation.mixin(target=target, toolchain=self.toolchain) - def __call__(self, mixin: T) -> T: + def __call__(self, mixin: T) -> Union[M, T]: return self.apply_from_class(mixin=mixin) diff --git a/src/saleyo/operation/accessor.py b/src/saleyo/operation/accessor.py index 032ded1..4985e52 100644 --- a/src/saleyo/operation/accessor.py +++ b/src/saleyo/operation/accessor.py @@ -1,11 +1,11 @@ from typing import Generic, Optional from ..base.toolchain import ToolChain -from ..base.typing import T, MixinAble +from ..base.typing import T, M from ..base.template import MixinOperation -class Accessor(Generic[T], MixinOperation[str]): +class Accessor(Generic[T, M], MixinOperation[str, M]): """ Want to access and modify some private varibles or methods? Try use `Accessor`! @@ -34,7 +34,7 @@ def configure(level: int = 1): private=True, ) - def mixin(self, target: MixinAble, toolchain: ToolChain = ToolChain()) -> None: + def mixin(self, target: M, toolchain: ToolChain = ToolChain()) -> None: self._inner = toolchain.tool_getattr( target, f"_{target.__name__}{self.argument}" if self._private else self.argument, diff --git a/src/saleyo/operation/ancestor.py b/src/saleyo/operation/ancestor.py index b133554..099ff51 100644 --- a/src/saleyo/operation/ancestor.py +++ b/src/saleyo/operation/ancestor.py @@ -1,14 +1,15 @@ from typing import Any, Type from saleyo.base.template import MixinOperation from saleyo.base.toolchain import ToolChain +from saleyo.base.typing import M -class Ancestor(MixinOperation[Type[Any]]): +class Ancestor(MixinOperation[Type[Any], M]): """ Ancestor will add the `argument` to `target.__bases__`. - + If `reverse`, the `argument` will add to the head of `target.__bases__`. - + Don't try to use it with external code and `module`, it may crash. """ diff --git a/src/saleyo/operation/hook.py b/src/saleyo/operation/hook.py index b383066..b948b7f 100644 --- a/src/saleyo/operation/hook.py +++ b/src/saleyo/operation/hook.py @@ -1,11 +1,11 @@ from typing import Any, Callable, Optional, Union -from ..base.typing import P, RT, T, MixinAble +from ..base.typing import M, P, RT, T from ..base.toolchain import ToolChain, Arguments from ..base.template import MixinOperation -class Post(MixinOperation[Callable[[T], Optional[RT]]]): +class Post(MixinOperation[Callable[[T], Optional[RT]], M]): """ `Post` will call after the target method, and the callable should be decorated as `@staticmethod` and have one argument to receive the result of target method. @@ -34,7 +34,7 @@ def configure( level=level, ) - def mixin(self, target: MixinAble, toolchain: ToolChain = ToolChain()) -> None: + def mixin(self, target: M, toolchain: ToolChain = ToolChain()) -> None: target_name = ( self.target_name if self.target_name is not None else self.argument.__name__ ) @@ -50,7 +50,7 @@ def post(*args, **kwargs) -> Union[T, RT]: return toolchain.tool_setattr(target, target_name, post) -class Pre(MixinOperation[Callable[P, Optional[Arguments[P]]]]): +class Pre(MixinOperation[Callable[P, Optional[Arguments[P]]], M]): """ `Pre` will call before the target method, and the callable should be decorated as `@staticmethod` and have `*args,**kwargs` to receive the arguments of target method. @@ -79,7 +79,7 @@ def configure( level=level, ) - def mixin(self, target: MixinAble, toolchain: ToolChain = ToolChain()) -> None: + def mixin(self, target: M, toolchain: ToolChain = ToolChain()) -> None: target_name = ( self.target_name if self.target_name is not None else self.argument.__name__ ) diff --git a/src/saleyo/operation/intercept.py b/src/saleyo/operation/intercept.py index 907f914..3a6d3c4 100644 --- a/src/saleyo/operation/intercept.py +++ b/src/saleyo/operation/intercept.py @@ -1,4 +1,4 @@ -from ..base.typing import MixinAble +from ..base.typing import M from ..base.toolchain import InvokeEvent, ToolChain from ..base.template import MixinOperation @@ -10,7 +10,7 @@ _B = InvokeEvent[_PB, Any] -class Intercept(Generic[_PA, _PB], MixinOperation[Callable[[_A[_PA]], _B[_PB]]]): +class Intercept(Generic[_PA, _PB, M], MixinOperation[Callable[[_A[_PA]], _B[_PB]], M]): """ The `Intercept` allow you to intercept the arguments before invoking target method. @@ -34,7 +34,7 @@ def __init__( def configure( level: int = 1, target_name: Optional[str] = None, - ) -> Callable[[Callable[[_A[_PA]], _B[_PB]]], "Intercept[_PA, _PB]"]: + ) -> Callable[[Callable[[_A[_PA]], _B[_PB]]], "Intercept[_PA, _PB, M]"]: return lambda argument: Intercept( argument=argument, level=level, @@ -43,7 +43,7 @@ def configure( def mixin( self, - target: MixinAble, + target: M, toolchain: ToolChain = ToolChain(), ) -> None: target_name = ( diff --git a/src/saleyo/operation/modify.py b/src/saleyo/operation/modify.py index 7b03d0e..92ec778 100644 --- a/src/saleyo/operation/modify.py +++ b/src/saleyo/operation/modify.py @@ -1,11 +1,11 @@ from typing import Any -from ..base.typing import MixinAble +from ..base.typing import M from ..base.toolchain import ToolChain from ..base.template import MixinOperation -class ReName(MixinOperation[str]): +class ReName(MixinOperation[str, Any]): """ Rename the target name. """ @@ -16,22 +16,22 @@ def __init__(self, old: str, new: str, level=1) -> None: super().__init__(old, level) self.new = new - def mixin(self, target: MixinAble, toolchain: ToolChain = ToolChain()) -> None: + def mixin(self, target: M, toolchain: ToolChain = ToolChain()) -> None: old = toolchain.tool_getattr(target, self.argument) toolchain.tool_delattr(target, self.argument) return toolchain.tool_setattr(target, self.new, old) -class Del(MixinOperation[str]): +class Del(MixinOperation[str, M]): """ Delete something named `argument` this from target """ - def mixin(self, target: MixinAble, toolchain: ToolChain = ToolChain()) -> None: + def mixin(self, target: M, toolchain: ToolChain = ToolChain()) -> None: return toolchain.tool_delattr(target, self.argument) -class Alias(MixinOperation[str]): +class Alias(MixinOperation[str, M]): """will copy the `argument` attribute to `alias`""" alias: str @@ -40,12 +40,15 @@ def __init__(self, argument: str, alias: str, level=1) -> None: super().__init__(argument, level) self.alias = alias - def mixin(self, target: MixinAble, toolchain: ToolChain = ToolChain()) -> None: + def mixin(self, target: M, toolchain: ToolChain = ToolChain()) -> None: return toolchain.tool_setattr( target, self.alias, toolchain.tool_getattr(target, self.argument) ) -class Insert(MixinOperation[Any]): - def mixin(self, target: MixinAble, toolchain: ToolChain = ToolChain()) -> None: +class Insert(MixinOperation[Any, M]): + """Will cover target when target exists.""" + + def mixin(self, target: M, toolchain: ToolChain = ToolChain()) -> None: return toolchain.tool_setattr(target, self.argument.__name__, self.argument) + diff --git a/src/saleyo/operation/overwrite.py b/src/saleyo/operation/overwrite.py index 319d690..db85dbe 100644 --- a/src/saleyo/operation/overwrite.py +++ b/src/saleyo/operation/overwrite.py @@ -1,11 +1,11 @@ from typing import Callable, Optional -from ..base.typing import MixinAble +from ..base.typing import M from ..base.toolchain import ToolChain from ..base.template import MixinOperation -class OverWrite(MixinOperation[Callable]): +class OverWrite(MixinOperation[Callable, M]): """ OverWrite is rude and it will cover the target method. @@ -33,7 +33,7 @@ def configure( target_name=target_name, ) - def mixin(self, target: MixinAble, toolchain: ToolChain = ToolChain()) -> None: + def mixin(self, target: M, toolchain: ToolChain = ToolChain()) -> None: target_name = ( self.argument.__name__ if self.target_name is None else self.target_name ) diff --git a/src/saleyo/operation/processor.py b/src/saleyo/operation/processor.py index de7b424..5ab707f 100644 --- a/src/saleyo/operation/processor.py +++ b/src/saleyo/operation/processor.py @@ -2,12 +2,12 @@ from types import ModuleType from typing import Callable, Optional -from ..base.typing import MixinAble, NameSpace +from ..base.typing import M, NameSpace from ..base.toolchain import Container, ToolChain from ..base.template import MixinOperation -class Processor(MixinOperation[Callable[[str], str]]): +class Processor(MixinOperation[Callable[[str], str], M]): """ If you want to get the soure code of a method and use `split` and `replace` to modify and redefine it,Try `Processor`. @@ -55,7 +55,7 @@ def configure( def mixin( self, - target: MixinAble, + target: M, toolchain: ToolChain = ToolChain(), ) -> None: target_name = ( diff --git a/tests/misc_test.py b/tests/misc_test.py index 879dc9a..26fdc20 100644 --- a/tests/misc_test.py +++ b/tests/misc_test.py @@ -1,4 +1,19 @@ -class a(object): - pass +from saleyo import Mixin, Insert, ReName -print(a.__base__) \ No newline at end of file + +class Foo: + def hello(self): + print("hello world") + + +@Mixin(target=Foo) +class MixinFoo: + goodbye = ReName("hello", "goodbye") + + @Insert + def helloworld(self): # type: ignore + self.goodbye() + + +foo: MixinFoo = Foo() +foo.helloworld()