Skip to content

Commit

Permalink
🔖 v0.1.3
Browse files Browse the repository at this point in the history
  • Loading branch information
H2Sxxa committed Mar 16, 2024
1 parent dd43266 commit cae290a
Show file tree
Hide file tree
Showing 18 changed files with 159 additions and 92 deletions.
42 changes: 35 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Saleyo

Saleyo is a lightwight Python AOP framework, easy to use and integrate.
Saleyo is a lightwight scalable Python AOP framework, easy to use and integrate.

## Getting Start

Expand All @@ -12,6 +12,8 @@ pip install saleyo

### Declear a `Mixin` class

If you don't like decorators, you can pass arguments to operations and call the `mixin` method manually.

```python
from saleyo import Mixin

Expand All @@ -24,10 +26,11 @@ class MixinFoo:...

### Use `MixinOperation`

Here is a simple demo.

```python
from typing import Any
from saleyo import Mixin
from saleyo.operation import Accessor, OverWrite, Post, Pre, Intercept, InvokeEvent
from saleyo import Mixin, Accessor, OverWrite, Post, Pre, Intercept, InvokeEvent


class Foo:
Expand All @@ -51,7 +54,7 @@ class MixinFoo:
@Intercept.configure(target_name="demo")
@staticmethod
def intercept_demo(_: InvokeEvent):
return InvokeEvent.from_call(lambda: print("hello world"))
return InvokeEvent(lambda: print("hello world"))

# Will call before `demo` call
@Pre.configure(target_name="demo")
Expand Down Expand Up @@ -79,14 +82,39 @@ foo.demo()
>>> post hello world
```

### Custom ToolChain

'ToolChain' determines the ability to modify the class.

```python
from saleyo import Mixin, GCToolChain, Arguments, Pre


@Mixin(target=str, toolchain=GCToolChain)
class MixinStr:
@Pre.configure(target_name="format")
def pre_format(self, *args) -> Arguments[...]:
print(f"input args: {args}")
return Arguments(self, "saleyo")


print("hello world {}".format("python"))

>>> input args: ('python',)
>>> hello world saleyo
```


### Custom Operation

The default operations can't satify you? Why not try define a operation yourself!

```python
from typing import Any, Type
from saleyo.base.template import MixinOperation
from saleyo.base.toolchain import ToolChain
from saleyo import MixinOperation, ToolChain

class MyOperation(MixinOperation[Any]):
def mixin(self, target: Type, toolchain: ToolChain = ...) -> None:
...
```
```

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]
name = "saleyo"
version = "0.1.2"
description = "Saleyo is a lightwight Python AOP framework, easy to use and integrate."
version = "0.1.3"
description = "Saleyo is a lightwight scalable Python AOP framework, easy to use and integrate."
authors = [{ name = "H2Sxxa", email = "[email protected]" }]
dependencies = []
requires-python = ">=3.8"
Expand Down
25 changes: 21 additions & 4 deletions src/saleyo/__init__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
"""
Saleyo is a module to modify external python code in runtime.
The implements are in `mixin`.
The `operation` module defines some default `MixinOperation`.
If you want to call the method manually, you can try the `function` module.
The `base` module is used to extend your own `mixin` method.
If you want to use some decorators, please use the `decorator` module.
Don't know how to start? Please see the part of Basic Tutorial in README.
The `base` module is used to extend your own `mixin` method.
The two links below are available.
https://github.com/H2Sxxa/saleyo/blob/main/README.md
https://pypi.org/project/saleyo/
"""

from . import operation as operation
from . import base as base
from . import mixin as mixin
from .mixin import Mixin as Mixin
from .operation import Accessor as Accessor
from .operation import Processor as Processor
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 .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

__version__ = (0, 1, 3)
4 changes: 2 additions & 2 deletions src/saleyo/base/template.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from abc import ABC
from typing import Generic, Type
from typing import Any, Generic, Type

from .toolchain import ToolChain
from .typing import T
Expand All @@ -21,7 +21,7 @@ def __init__(self, argument: T, level=1) -> None:
self.argument = argument
self.level = level

def mixin(self, target: Type, toolchain: ToolChain = ToolChain()) -> None:
def mixin(self, target: Type[Any], toolchain: ToolChain = ToolChain()) -> None:
raise NotImplementedError(
f"Not Ready to use this Operation to modify '{target}' via '{toolchain}'"
)
48 changes: 20 additions & 28 deletions src/saleyo/base/toolchain.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
from dataclasses import dataclass
import ctypes as _ctypes
from ctypes import py_object as _py_object, POINTER as _POINTER, cast as _cast
from types import FunctionType
from typing import Any, Callable, Dict, Generic, Optional
from dataclasses import dataclass
from gc import get_referents as _get_referents

from typing import Any, Callable, Dict, Generic, Optional

from ..base.typing import P, RT, NameSpace


@dataclass
class ToolChain:
"""
The tool to do mixin.
The tool class to do mixin.
Default to use `getattr`/`setattr`/`hasattr`
Default to use `getattr`/`setattr`/`hasattr`.
"""

tool_getattr: Callable[[Any, str], Any] = getattr
Expand All @@ -36,8 +35,8 @@ class ToolChain:


def _cpy_get_dict(_object: Any) -> Dict[str, Any]:
return _ctypes.cast(
id(_object) + type(_object).__dictoffset__, _ctypes.POINTER(_ctypes.py_object)
return _cast(
id(_object) + type(_object).__dictoffset__, _POINTER(_py_object)
).contents.value


Expand Down Expand Up @@ -84,46 +83,39 @@ def define_function(
)[function_name]


class Arugument(Generic[P]):
class Arguments(Generic[P]):
"""
`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:
self.positional = args
self.keyword = kwargs
self.args = args
self.kwargs = kwargs

def __str__(self) -> str:
return f"Arugument(positional: {self.positional}, keyword: {self.keyword} )"
return f"Arugument(positional: {self.args}, keyword: {self.kwargs} )"


@dataclass
class InvokeEvent(Generic[P, RT]):
"""
A `InvokeEvent` includes the target function and the arguments to call this functions.
Recommend to use the static method `InvokeEvent.from_call` to get a `InvokeEvent`.
"""

target: Callable[P, RT]
argument: Arugument[P]
argument: Arguments[P]

@staticmethod
def from_call(
def __init__(
self,
target: Callable[P, RT],
*args: P.args,
**kwargs: P.kwargs,
) -> "InvokeEvent[P, RT]":
return InvokeEvent(
target=target,
argument=Arugument(
*args,
**kwargs,
),
)
) -> None:
super().__init__()
self.target = target
self.argument = Arguments(*args, **kwargs)

def invoke(self, target: Callable[P, RT]) -> RT:
return target(*self.argument.positional, **self.argument.keyword)
return target(*self.argument.args, **self.argument.kwargs)

def invoke_target(self) -> RT:
return self.target(*self.argument.positional, **self.argument.keyword)
return self.target(*self.argument.args, **self.argument.kwargs)
17 changes: 16 additions & 1 deletion src/saleyo/base/typing.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
from typing import Any, Dict, List, ParamSpec, Type, TypeVar, Union

Target = Union[Type, List[Type]]
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`
"""
T = TypeVar("T")
"""
`T` means `Type`
"""
P = ParamSpec("P")
"""
`P` means `Params`
"""
4 changes: 2 additions & 2 deletions src/saleyo/mixin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List, Type
from typing import List

from .base.template import MixinOperation
from .base.toolchain import ToolChain
Expand All @@ -14,7 +14,7 @@ class Mixin:
Allow to have more than one target, but that's not recommended.
"""

target: List[Type]
target: Target
toolchain: ToolChain
reverse_level: bool

Expand Down
5 changes: 3 additions & 2 deletions src/saleyo/operation/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .accessor import Accessor as Accessor
from .overwrite import OverWrite as OverWrite
from .processor import Processor as Processor
from .intercept import Intercept as Intercept, InvokeEvent as InvokeEvent
from .hook import Pre as Pre, Post as Post
from .intercept import Intercept as Intercept
from .hook import Pre as Pre
from .hook import Post as Post
10 changes: 8 additions & 2 deletions src/saleyo/operation/accessor.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Generic, Optional, Type
from typing import Any, Generic, Optional, Type

from ..base.toolchain import ToolChain
from ..base.typing import T
Expand All @@ -9,21 +9,27 @@ class Accessor(Generic[T], MixinOperation[str]):
"""
Want to access and modify some private varibles or methods? Try use `Accessor`!
The Generic is the type of target varible.
Notice: The value only available after invoking the `mixin` method.
If you use `@Mixin` and have more than one target classes, the `value` will always be the varible of latest target.
"""

_inner: Optional[T]

def __init__(self, argument: str, level=1) -> None:
super().__init__(argument, level)
self._inner = None

@staticmethod
def configure(level: int = 1):
return lambda argument: Accessor(
argument=argument,
level=level,
)

def mixin(self, target: Type, toolchain: ToolChain = ToolChain()) -> None:
def mixin(self, target: Type[Any], toolchain: ToolChain = ToolChain()) -> None:
self._inner = toolchain.tool_getattr(
target,
f"_{target.__name__}{self.argument}",
Expand Down
Loading

0 comments on commit cae290a

Please sign in to comment.