From 6b42cee2151f16e2fd7c7fc6b5ba0547d045e7ba Mon Sep 17 00:00:00 2001 From: H2Sxxa Date: Fri, 24 May 2024 14:25:00 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Ruff=20Lint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- ruff.toml | 15 ++++++++++ src/saleyo/__init__.py | 27 +++++++++-------- src/saleyo/base/__init__.py | 2 +- src/saleyo/base/template.py | 11 +++---- src/saleyo/base/toolchain.py | 31 +++++++++++++------- src/saleyo/base/typing.py | 1 - src/saleyo/decorator/__init__.py | 6 ++++ src/saleyo/decorator/ancestor.py | 45 +++++++++++++++++++++++++++++ src/saleyo/{ => decorator}/mixin.py | 35 +++++++++++++--------- src/saleyo/operation/__init__.py | 19 +++++++----- src/saleyo/operation/accessor.py | 32 ++++++++++++-------- src/saleyo/operation/ancestor.py | 41 -------------------------- src/saleyo/operation/hook.py | 20 ++++++++----- src/saleyo/operation/intercept.py | 9 +++--- src/saleyo/operation/modify.py | 12 ++++---- src/saleyo/operation/overwrite.py | 9 +++--- src/saleyo/operation/processor.py | 13 +++++---- tests/__init__.py | 4 +-- tests/ans.py | 21 ++++++++++++++ tests/custom.py | 5 ++-- tests/demo.py | 6 ++-- tests/gc_test.py | 2 +- tests/intercept.py | 2 +- tests/misc_test.py | 2 +- tests/test_module_b.py | 4 +-- 26 files changed, 231 insertions(+), 145 deletions(-) create mode 100644 ruff.toml create mode 100644 src/saleyo/decorator/__init__.py create mode 100644 src/saleyo/decorator/ancestor.py rename src/saleyo/{ => decorator}/mixin.py (82%) delete mode 100644 src/saleyo/operation/ancestor.py create mode 100644 tests/ans.py diff --git a/pyproject.toml b/pyproject.toml index 607b850..ff9f9e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "saleyo" -version = "1.1.1" +version = "1.1.2" description = "Saleyo is a lightwight scalable Python AOP framework, easy to use and integrate." authors = [{ name = "H2Sxxa", email = "h2sxxa0w0@gmail.com" }] dependencies = [] diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..8ad3282 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,15 @@ +[lint] +select = [ + # pycodestyle + "E", + # Pyflakes + "F", + # pyupgrade + "UP", + # flake8-bugbear + "B", + # flake8-simplify + "SIM", + # isort + "I", +] diff --git a/src/saleyo/__init__.py b/src/saleyo/__init__.py index 9693914..75c62d0 100644 --- a/src/saleyo/__init__.py +++ b/src/saleyo/__init__.py @@ -15,24 +15,23 @@ """ from . import base as base -from . import mixin as mixin from . import operation as operation -from .mixin import Mixin as Mixin +from .base.template import MixinOperation as MixinOperation +from .base.toolchain import Arguments as Arguments +from .base.toolchain import CPyToolChain as CPyToolChain +from .base.toolchain import GCToolChain as GCToolChain +from .base.toolchain import InvokeEvent as InvokeEvent +from .base.toolchain import ToolChain as ToolChain +from .decorator import Ancestor as Ancestor +from .decorator import Mixin as Mixin from .operation import Accessor as Accessor +from .operation import Alias as Alias +from .operation import Del as Del from .operation import FunctionAccessor as FunctionAccessor -from .operation import Processor as Processor +from .operation import Insert as Insert from .operation import Intercept as Intercept from .operation import OverWrite as OverWrite -from .operation import Pre as Pre from .operation import Post as Post -from .operation import Del as Del +from .operation import Pre as Pre +from .operation import Processor as Processor from .operation import ReName as ReName -from .operation import Alias as Alias -from .operation import Ancestor as Ancestor -from .operation import Insert as Insert -from .base.toolchain import ToolChain as ToolChain -from .base.toolchain import Arguments as Arguments -from .base.toolchain import InvokeEvent as InvokeEvent -from .base.toolchain import CPyToolChain as CPyToolChain -from .base.toolchain import GCToolChain as GCToolChain -from .base.template import MixinOperation as MixinOperation diff --git a/src/saleyo/base/__init__.py b/src/saleyo/base/__init__.py index 90722a1..b3d6a75 100644 --- a/src/saleyo/base/__init__.py +++ b/src/saleyo/base/__init__.py @@ -1,3 +1,3 @@ -from . import typing as typing from . import template as template from . import toolchain as toolchain +from . import typing as typing diff --git a/src/saleyo/base/template.py b/src/saleyo/base/template.py index 2773e56..ad1ee6b 100644 --- a/src/saleyo/base/template.py +++ b/src/saleyo/base/template.py @@ -1,8 +1,8 @@ from abc import ABC from typing import Any, Generic -from .toolchain import ToolChain -from .typing import T, M +from .toolchain import DefaultToolChain, ToolChain +from .typing import M, T class MixinOperation(Generic[T], ABC): @@ -12,8 +12,9 @@ class MixinOperation(Generic[T], ABC): The generic `MixinOperation` is the type of argument. `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` + + If you call the `MixinOperation` or call the subclass of this, + it will call the `MixinOperation.argument` """ argument: T @@ -23,7 +24,7 @@ def __init__(self, argument: T, level=1) -> None: self.argument = argument self.level = level - def mixin(self, target: M, toolchain: ToolChain = ToolChain()) -> None: + def mixin(self, target: M, toolchain: ToolChain = DefaultToolChain) -> None: raise NotImplementedError( f"Not Ready to use this Operation to modify '{target}' via '{toolchain}'" ) diff --git a/src/saleyo/base/toolchain.py b/src/saleyo/base/toolchain.py index 2d0267b..1d22441 100644 --- a/src/saleyo/base/toolchain.py +++ b/src/saleyo/base/toolchain.py @@ -1,10 +1,12 @@ -from ctypes import py_object as _py_object, POINTER as _POINTER, cast as _cast -from types import FunctionType +from ctypes import POINTER as _POINTER +from ctypes import cast as _cast +from ctypes import py_object as _py_object from dataclasses import dataclass from gc import get_referents as _get_referents +from types import FunctionType from typing import Any, Callable, Dict, Generic, Optional -from ..base.typing import P, T, NameSpace +from ..base.typing import NameSpace, P, T @dataclass @@ -21,6 +23,8 @@ class ToolChain: tool_delattr: Callable[[Any, str], None] = delattr +DefaultToolChain = ToolChain() + GCToolChain = ToolChain( tool_getattr=lambda _object, _name: _get_referents(_object.__dict__)[0][_name], tool_hasattr=lambda _object, _name: _name in _get_referents(_object.__dict__)[0], @@ -32,9 +36,11 @@ class ToolChain: ), ) """ -GC ToolChain use the `get_referents` functions in `gc` and it can modify some special class. +GC ToolChain use the `get_referents` functions in `gc` and +it can modify some special class. -Notice: There is no guarantee that it can modify any class, and this method is rude and dangerous, avoid using it in produce environment. +Notice: There is no guarantee that it can modify any class, +and this method is rude and dangerous, avoid using it in produce environment. """ @@ -53,15 +59,18 @@ def _cpy_get_dict(_object: Any) -> Dict[str, Any]: tool_delattr=lambda _object, _name: _cpy_get_dict(_object).__delitem__(_name), ) """ -`CPyToolChain` use the `ctypes` to modify class, it's dangerous than other default toolchains. +`CPyToolChain` use the `ctypes` to modify class, +it's dangerous than other default toolchains. -Notice: There is no guarantee that it can modify any class, and this method is rude and dangerous, avoid using it in produce environment. +Notice: There is no guarantee that it can modify any class, +and this method is rude and dangerous, avoid using it in produce environment. """ class Container: """ - Container is A Class to keep a namespace and use this namespace to define function / variable / ... + Container is A Class to keep a namespace and + use this namespace to define function / variable / ... """ environment: NameSpace @@ -90,7 +99,8 @@ def define_function( class Arguments(Generic[P]): """ - `Argument` is used to call function, the Generic `P` is the params of target function. + `Argument` is used to call function, + the Generic `P` is the params of target function. """ def __init__(self, *args: P.args, **kwargs: P.kwargs) -> None: @@ -103,7 +113,8 @@ def __str__(self) -> str: class InvokeEvent(Generic[P, T]): """ - A `InvokeEvent` includes the target function and the arguments to call this functions. + A `InvokeEvent` includes the target function and the arguments to + call this functions. """ target: Callable[P, T] diff --git a/src/saleyo/base/typing.py b/src/saleyo/base/typing.py index ab9bf33..231b562 100644 --- a/src/saleyo/base/typing.py +++ b/src/saleyo/base/typing.py @@ -1,7 +1,6 @@ from types import ModuleType from typing import Any, Dict, Iterable, ParamSpec, Type, TypeVar, Union - # Generic RT = TypeVar("RT") diff --git a/src/saleyo/decorator/__init__.py b/src/saleyo/decorator/__init__.py new file mode 100644 index 0000000..a226e50 --- /dev/null +++ b/src/saleyo/decorator/__init__.py @@ -0,0 +1,6 @@ +""" +The `decorator` should be used to decorate a class. +""" + +from .ancestor import Ancestor as Ancestor +from .mixin import Mixin as Mixin diff --git a/src/saleyo/decorator/ancestor.py b/src/saleyo/decorator/ancestor.py new file mode 100644 index 0000000..1728ba8 --- /dev/null +++ b/src/saleyo/decorator/ancestor.py @@ -0,0 +1,45 @@ +from typing import Any, Generic, Type + +from ..base.toolchain import DefaultToolChain, ToolChain +from ..base.typing import T + + +class Ancestor(Generic[T]): + """ + Ancestor will add the `ancestor_class` to `target.__bases__`. + (Please see `__call__` method) + + Please ensure the `target.__bases__` is not `(object,)`, this decorator requires + the target has at least one super class. + + If `reverse`, the `argument` will add to the head of `target.__bases__`. + + Don't try to use it with external code and `module`, it will crash. + """ + + reverse: bool + target: Type[Any] + toolchain: ToolChain + + def __init__( + self, target: Type[Any], toolchain: ToolChain = DefaultToolChain, reverse=False + ) -> None: + self.reverse = reverse + self.target = target + self.toolchain = toolchain + + def __call__(self, ancestor_class: Type[T]) -> Type[T]: + assert self.target.__bases__ != (object,) + + self.toolchain.tool_setattr( + self.target, + "__bases__", + (ancestor_class, *self.toolchain.tool_getattr(self.target, "__bases__")) + if self.reverse + else ( + *self.toolchain.tool_getattr(self.target, "__bases__"), + ancestor_class, + ), + ) + + return ancestor_class diff --git a/src/saleyo/mixin.py b/src/saleyo/decorator/mixin.py similarity index 82% rename from src/saleyo/mixin.py rename to src/saleyo/decorator/mixin.py index 45a82e1..624bc03 100644 --- a/src/saleyo/mixin.py +++ b/src/saleyo/decorator/mixin.py @@ -1,8 +1,8 @@ from typing import Generic, Iterable, List, Union -from .base.template import MixinOperation -from .base.toolchain import ToolChain -from .base.typing import M, T, IterableOrSingle +from ..base.template import MixinOperation +from ..base.toolchain import DefaultToolChain, ToolChain +from ..base.typing import IterableOrSingle, M, T class Mixin(Generic[M]): @@ -11,7 +11,9 @@ class Mixin(Generic[M]): If the target is a special class, you should custom the toolchain yourself. - It is recommend to use `assert isinstance(self, )` at the head of operation functions, although there may be some performance cost, but it is worth it in most conditions. + It is recommend to use `assert isinstance(self, )` at the head of + operation functions, although there may be some performance cost, + but it is worth it in most conditions. Allow to have more than one target, but that's not recommended. """ @@ -23,7 +25,7 @@ class Mixin(Generic[M]): def __init__( self, target: IterableOrSingle[M], - toolchain: ToolChain = ToolChain(), + toolchain: ToolChain = DefaultToolChain, reverse_level: bool = False, ) -> None: self.target = target if isinstance(target, Iterable) else [target] @@ -33,18 +35,20 @@ def __init__( @staticmethod def from_name( target: IterableOrSingle[str], - toolchain: ToolChain = ToolChain(), + toolchain: ToolChain = DefaultToolChain, reverse_level: bool = False, qualname: bool = False, ) -> "Mixin": """ - Will find target classes from `object.__subclasses__()` from class name or qualname. + Will find target classes via `object.__subclasses__()`. - 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. + 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. + 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] @@ -64,21 +68,24 @@ def from_name( def from_regex( pattern: str, pattern_flags: int = 0, - toolchain: ToolChain = ToolChain(), + toolchain: ToolChain = DefaultToolChain, reverse_level: bool = False, qualname: bool = False, full_match: bool = False, ) -> "Mixin": """ - Will use regex pattern to find target classes from the `object.__subclasses__()`. + 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`. + 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. + 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. + The method may takes lots of time when there are a whole lot classes, + recommand to use `@Mixin()` directly if you can. """ import re diff --git a/src/saleyo/operation/__init__.py b/src/saleyo/operation/__init__.py index 803fd50..bd1ef59 100644 --- a/src/saleyo/operation/__init__.py +++ b/src/saleyo/operation/__init__.py @@ -1,12 +1,15 @@ +""" +The `operations` always inner a class and call by `@Mixin` decorator. +""" + from .accessor import Accessor as Accessor -from .accessor import FunctionAccessor as FunctionAccessor -from .overwrite import OverWrite as OverWrite -from .processor import Processor as Processor +from .accessor import FunctionAccessor as FunctionAccessor +from .hook import Post as Post +from .hook import Pre as Pre from .intercept import Intercept as Intercept -from .ancestor import Ancestor as Ancestor -from .modify import ReName as ReName -from .modify import Del as Del from .modify import Alias as Alias +from .modify import Del as Del from .modify import Insert as Insert -from .hook import Pre as Pre -from .hook import Post as Post +from .modify import ReName as ReName +from .overwrite import OverWrite as OverWrite +from .processor import Processor as Processor diff --git a/src/saleyo/operation/accessor.py b/src/saleyo/operation/accessor.py index 63a3bf3..cae104f 100644 --- a/src/saleyo/operation/accessor.py +++ b/src/saleyo/operation/accessor.py @@ -1,21 +1,25 @@ from typing import Callable, Generic, Optional -from ..base.toolchain import ToolChain -from ..base.typing import P, T, M from ..base.template import MixinOperation +from ..base.toolchain import DefaultToolChain, ToolChain +from ..base.typing import M, P, T class Accessor(Generic[T], MixinOperation[str]): """ - The Generic `T` is the type of target varible, if you want to visit a private function, try to use the subclass `FunctionAccessor`. + The Generic `T` is the type of target varible, + if you want to visit a private function, try to use the subclass `FunctionAccessor`. Notice: The value only available after invoking the `mixin` method. - If the `private` is `True`, will add target class name (like `_Foo`) to the prefix to argument, if the target is complex, you can set `private` to `False` and provide the true name by yourself. + If the `private` is `True`, will add target class name (like `_Foo`) to the prefix + to argument, if the target is complex, + you can set `private` to `False` and provide the true name by yourself. - Also a variable named `argument` will add to target classes when `private` is `True`. + Also a variable named `argument` will add to target classes when `private` is `True` - If you use `@Mixin` and have more than one target classes, the `value` will always be the varible of latest target. + If you use `@Mixin` and have more than one target classes, the `value` will always + be the varible of latest target. """ _inner: Optional[T] @@ -26,7 +30,7 @@ def __init__(self, argument: str, level=1, private=True) -> None: self._inner = None self._private = private - def mixin(self, target: M, toolchain: ToolChain = ToolChain()) -> None: + def mixin(self, target: M, toolchain: ToolChain = DefaultToolChain) -> None: self._inner = toolchain.tool_getattr( target, f"_{target.__name__}{self.argument}" if self._private else self.argument, @@ -65,13 +69,17 @@ class FunctionAccessor(Generic[P, T], Accessor[Callable[P, T]]): It's recommend to provide the Generic `P` and `T`, it can be useful in `__call__`. - If the `private` is `True`, will add target class name (like `_Foo`) to the prefix to argument, if the target is complex, you can set `private` to `False` and provide the true name by yourself. + If the `private` is `True`, will add target class name (like `_Foo`) to the prefix + to argument, if the target is complex, you can set `private` to `False` and + provide the true name by yourself. - Also a variable named `argument` will add to target classes when `private` is `True`.s + Also a variable named `argument` will add to target classes when `private` is `True` - If you just call in operation functions, you can just use a simple variable with `Callable[P, T]` type. - - If you use `@Mixin` and have more than one target classes, the `value` will always be the varible of latest target. + If you just call in operation functions, you can just use a simple variable with + `Callable[P, T]` type. + + If you use `@Mixin` and have more than one target classes, the `value` will always + be the varible of latest target. ```python something: FunctionAccessor[[str], None] = FunctionAccessor("something") diff --git a/src/saleyo/operation/ancestor.py b/src/saleyo/operation/ancestor.py deleted file mode 100644 index bceff7b..0000000 --- a/src/saleyo/operation/ancestor.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Type - -from ..base.template import MixinOperation -from ..base.toolchain import ToolChain -from ..base.typing import M - - -class Ancestor(MixinOperation[Type[Any]]): - """ - 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. - """ - - reverse: bool - - def __init__(self, argument: Type[Any], level=1, reverse=False) -> None: - super().__init__(argument, level) - self.reverse = reverse - - @staticmethod - def configure( - level: int = 1, - reverse=False, - ): - return lambda argument: Ancestor( - argument, - level=level, - reverse=reverse, - ) - - def mixin(self, target: M, toolchain: ToolChain = ToolChain()) -> None: - return toolchain.tool_setattr( - target, - "__bases__", - (self.argument, *toolchain.tool_getattr(target, "__bases__")) - if self.reverse - else (*toolchain.tool_getattr(target, "__bases__"), self.argument), - ) diff --git a/src/saleyo/operation/hook.py b/src/saleyo/operation/hook.py index 1be0f09..5910504 100644 --- a/src/saleyo/operation/hook.py +++ b/src/saleyo/operation/hook.py @@ -1,15 +1,17 @@ from typing import Any, Callable, Optional, Union -from ..base.typing import M, P, RT, T -from ..base.toolchain import ToolChain, Arguments from ..base.template import MixinOperation +from ..base.toolchain import Arguments, DefaultToolChain, ToolChain +from ..base.typing import RT, M, P, T class Post(MixinOperation[Callable[[T], Optional[RT]]]): """ - `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. + `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. - If the `post` function return value is not `None`, it will replace the original result. + If the `post` function return value is not `None`, + it will replace the original result. """ target_name: Optional[str] @@ -34,7 +36,7 @@ def configure( level=level, ) - def mixin(self, target: M, toolchain: ToolChain = ToolChain()) -> None: + def mixin(self, target: M, toolchain: ToolChain = DefaultToolChain) -> None: target_name = ( self.target_name if self.target_name is not None else self.argument.__name__ ) @@ -54,9 +56,11 @@ def post(*args, **kwargs) -> Union[T, RT]: class Pre(MixinOperation[Callable[P, Optional[Arguments[P]]]]): """ - `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. + `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. - If the `pre` function return value is a `Aruguments`(not `None`), it will replace the original arguments. + If the `pre` function return value is a `Aruguments`(not `None`), + it will replace the original arguments. """ target_name: Optional[str] @@ -81,7 +85,7 @@ def configure( level=level, ) - def mixin(self, target: M, toolchain: ToolChain = ToolChain()) -> None: + def mixin(self, target: M, toolchain: ToolChain = DefaultToolChain) -> 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 fd4305c..ca63f34 100644 --- a/src/saleyo/operation/intercept.py +++ b/src/saleyo/operation/intercept.py @@ -1,8 +1,8 @@ from typing import Any, Callable, Generic, Optional, ParamSpec -from ..base.typing import M -from ..base.toolchain import InvokeEvent, ToolChain from ..base.template import MixinOperation +from ..base.toolchain import DefaultToolChain, InvokeEvent, ToolChain +from ..base.typing import M _PA = ParamSpec("_PA") _PB = ParamSpec("_PB") @@ -14,7 +14,8 @@ class Intercept(Generic[_PA, _PB], MixinOperation[Callable[[_A[_PA]], _B[_PB]]]) """ The `Intercept` allow you to intercept the arguments before invoking target method. - Then, you can handle thefse arguments in your own function and make a redirect to another function. + Then, you can handle thefse arguments in your own function and make a redirect to + another function. If you just want to modify the arguments or the result, please see `Pre` and `Post`. """ @@ -44,7 +45,7 @@ def configure( def mixin( self, target: M, - toolchain: ToolChain = ToolChain(), + toolchain: ToolChain = DefaultToolChain, ) -> None: target_name = ( self.target_name if self.target_name is not None else self.argument.__name__ diff --git a/src/saleyo/operation/modify.py b/src/saleyo/operation/modify.py index 43c767b..e506f21 100644 --- a/src/saleyo/operation/modify.py +++ b/src/saleyo/operation/modify.py @@ -1,8 +1,8 @@ from typing import Any -from ..base.typing import M -from ..base.toolchain import ToolChain from ..base.template import MixinOperation +from ..base.toolchain import DefaultToolChain, ToolChain +from ..base.typing import M class ReName(MixinOperation[str]): @@ -16,7 +16,7 @@ def __init__(self, old: str, new: str, level=1) -> None: super().__init__(old, level) self.new = new - def mixin(self, target: M, toolchain: ToolChain = ToolChain()) -> None: + def mixin(self, target: M, toolchain: ToolChain = DefaultToolChain) -> None: old = toolchain.tool_getattr(target, self.argument) toolchain.tool_delattr(target, self.argument) return toolchain.tool_setattr(target, self.new, old) @@ -27,7 +27,7 @@ class Del(MixinOperation[str]): Delete something named `argument` this from target """ - def mixin(self, target: M, toolchain: ToolChain = ToolChain()) -> None: + def mixin(self, target: M, toolchain: ToolChain = DefaultToolChain) -> None: return toolchain.tool_delattr(target, self.argument) @@ -40,7 +40,7 @@ def __init__(self, argument: str, alias: str, level=1) -> None: super().__init__(argument, level) self.alias = alias - def mixin(self, target: M, toolchain: ToolChain = ToolChain()) -> None: + def mixin(self, target: M, toolchain: ToolChain = DefaultToolChain) -> None: return toolchain.tool_setattr( target, self.alias, toolchain.tool_getattr(target, self.argument) ) @@ -49,5 +49,5 @@ def mixin(self, target: M, toolchain: ToolChain = ToolChain()) -> None: class Insert(MixinOperation[Any]): """Will cover target when target exists.""" - def mixin(self, target: M, toolchain: ToolChain = ToolChain()) -> None: + def mixin(self, target: M, toolchain: ToolChain = DefaultToolChain) -> 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 08834af..d5cbf8f 100644 --- a/src/saleyo/operation/overwrite.py +++ b/src/saleyo/operation/overwrite.py @@ -1,15 +1,16 @@ from typing import Callable, Optional -from ..base.typing import M -from ..base.toolchain import ToolChain from ..base.template import MixinOperation +from ..base.toolchain import DefaultToolChain, ToolChain +from ..base.typing import M class OverWrite(MixinOperation[Callable]): """ OverWrite is rude and it will cover the target method. - If the target method doesn't exist, overwrite will add overwrite method to target class. + If the target method doesn't exist, + overwrite will add overwrite method to target class. Try avoid using `OverWrite` with other `OverWrite`. """ @@ -33,7 +34,7 @@ def configure( target_name=target_name, ) - def mixin(self, target: M, toolchain: ToolChain = ToolChain()) -> None: + def mixin(self, target: M, toolchain: ToolChain = DefaultToolChain) -> 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 ac84116..af2032a 100644 --- a/src/saleyo/operation/processor.py +++ b/src/saleyo/operation/processor.py @@ -2,16 +2,19 @@ from types import ModuleType from typing import Callable, Optional -from ..base.typing import M, NameSpace -from ..base.toolchain import Container, ToolChain from ..base.template import MixinOperation +from ..base.toolchain import Container, DefaultToolChain, ToolChain +from ..base.typing import M, NameSpace class Processor(MixinOperation[Callable[[str], str]]): """ - If you want to get the soure code of a method and use `split` and `replace` to modify and redefine it,Try `Processor`. + If you want to get the soure code of a method and use `split` and + `replace` to modify and redefine it,Try `Processor`. - When you try to use this, please make sure you configure the correct module of your target, or you can use `extra_namespace` to supplement the missing things. + When you try to use this, please make sure you configure + the correct module of your target, or you can use `extra_namespace` to + supplement the missing things. Don't try to use it with external code, like the code of cpython, it will crash. """ @@ -56,7 +59,7 @@ def configure( def mixin( self, target: M, - toolchain: ToolChain = ToolChain(), + toolchain: ToolChain = DefaultToolChain, ) -> None: target_name = ( self.target_name if self.target_name is not None else self.argument.__name__ diff --git a/tests/__init__.py b/tests/__init__.py index b46b88e..67955e1 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,4 @@ -import intercept as intercept +import custom as custom import demo as demo import gc_test as gc_test -import custom as custom +import intercept as intercept diff --git a/tests/ans.py b/tests/ans.py new file mode 100644 index 0000000..e4fe86d --- /dev/null +++ b/tests/ans.py @@ -0,0 +1,21 @@ +from saleyo import Ancestor + + +class _A: + pass + + +class A(_A): + pass + + +@Ancestor(A) +class AnsA: + def hello(self): + print("Hello world!") + + +a = A() +assert isinstance(a, AnsA) + +a.hello() diff --git a/tests/custom.py b/tests/custom.py index 423782e..9ec67f4 100644 --- a/tests/custom.py +++ b/tests/custom.py @@ -1,7 +1,8 @@ from typing import Any, Type + from saleyo.base.template import MixinOperation from saleyo.base.toolchain import ToolChain + class MyOperation(MixinOperation[Any]): - def mixin(self, target: Type, toolchain: ToolChain = ...) -> None: - ... \ No newline at end of file + def mixin(self, target: Type, toolchain: ToolChain = ...) -> None: ... diff --git a/tests/demo.py b/tests/demo.py index e403ef0..1e0a64c 100644 --- a/tests/demo.py +++ b/tests/demo.py @@ -1,5 +1,6 @@ from typing import Any -from saleyo import Mixin, Accessor, OverWrite, Post, Pre, Intercept, InvokeEvent + +from saleyo import Accessor, Intercept, InvokeEvent, Mixin, OverWrite, Post, Pre class Foo: @@ -11,7 +12,8 @@ def demo(self): @Mixin(target=Foo) class MixinFoo: - # Will add a varible named `__private` to Foo and it has the same address with `_Foo__private` + # Will add a varible named `__private` to Foo and + # it has the same address with `_Foo__private` private: Accessor[str] = Accessor("__private") # Will Add the `func` to `Foo` diff --git a/tests/gc_test.py b/tests/gc_test.py index a244f24..994f7a9 100644 --- a/tests/gc_test.py +++ b/tests/gc_test.py @@ -1,4 +1,4 @@ -from saleyo import Mixin, GCToolChain, Arguments, Pre +from saleyo import Arguments, GCToolChain, Mixin, Pre @Mixin(target=str, toolchain=GCToolChain) diff --git a/tests/intercept.py b/tests/intercept.py index 4862f3a..a1a89e2 100644 --- a/tests/intercept.py +++ b/tests/intercept.py @@ -1,4 +1,4 @@ -from saleyo import Mixin, Intercept, Post, Pre, InvokeEvent +from saleyo import Intercept, InvokeEvent, Mixin, Post, Pre class Foo: diff --git a/tests/misc_test.py b/tests/misc_test.py index b064063..71ad0bc 100644 --- a/tests/misc_test.py +++ b/tests/misc_test.py @@ -1,4 +1,4 @@ -from saleyo import Mixin, Insert +from saleyo import Insert, Mixin from saleyo.operation.accessor import Accessor, FunctionAccessor diff --git a/tests/test_module_b.py b/tests/test_module_b.py index e4c812d..cc77f7b 100644 --- a/tests/test_module_b.py +++ b/tests/test_module_b.py @@ -1,6 +1,6 @@ -from saleyo.mixin import Mixin -from saleyo.operation.overwrite import OverWrite import test_module_a +from saleyo.decorator.mixin import Mixin +from saleyo.operation.overwrite import OverWrite @Mixin(test_module_a)