Skip to content

Commit

Permalink
✨ make call operation more flexible and add some Generic Type
Browse files Browse the repository at this point in the history
  • Loading branch information
H2Sxxa committed Apr 24, 2024
1 parent 214a448 commit 83d7d8a
Show file tree
Hide file tree
Showing 12 changed files with 79 additions and 53 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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:
...
```

14 changes: 9 additions & 5 deletions src/saleyo/base/template.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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: ...
13 changes: 8 additions & 5 deletions src/saleyo/base/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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.
"""
12 changes: 6 additions & 6 deletions src/saleyo/mixin.py
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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:
Expand Down Expand Up @@ -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)
6 changes: 3 additions & 3 deletions src/saleyo/operation/accessor.py
Original file line number Diff line number Diff line change
@@ -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`!
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 4 additions & 3 deletions src/saleyo/operation/ancestor.py
Original file line number Diff line number Diff line change
@@ -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.
"""

Expand Down
10 changes: 5 additions & 5 deletions src/saleyo/operation/hook.py
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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__
)
Expand All @@ -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.
Expand Down Expand Up @@ -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__
)
Expand Down
8 changes: 4 additions & 4 deletions src/saleyo/operation/intercept.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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.
Expand All @@ -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,
Expand All @@ -43,7 +43,7 @@ def configure(

def mixin(
self,
target: MixinAble,
target: M,
toolchain: ToolChain = ToolChain(),
) -> None:
target_name = (
Expand Down
21 changes: 12 additions & 9 deletions src/saleyo/operation/modify.py
Original file line number Diff line number Diff line change
@@ -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.
"""
Expand All @@ -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
Expand All @@ -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)

6 changes: 3 additions & 3 deletions src/saleyo/operation/overwrite.py
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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
)
Expand Down
6 changes: 3 additions & 3 deletions src/saleyo/operation/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -55,7 +55,7 @@ def configure(

def mixin(
self,
target: MixinAble,
target: M,
toolchain: ToolChain = ToolChain(),
) -> None:
target_name = (
Expand Down
21 changes: 18 additions & 3 deletions tests/misc_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
class a(object):
pass
from saleyo import Mixin, Insert, ReName

print(a.__base__)

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()

0 comments on commit 83d7d8a

Please sign in to comment.