Skip to content

Commit

Permalink
🔖 v0.1.1
Browse files Browse the repository at this point in the history
  • Loading branch information
H2Sxxa committed Mar 15, 2024
1 parent 7071144 commit 845086c
Show file tree
Hide file tree
Showing 13 changed files with 184 additions and 20 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:

jobs:
pypi-publish:
name: Test Lib & Publish
name: Publish
runs-on: ubuntu-latest
permissions:
id-token: write
Expand Down
93 changes: 92 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,92 @@
# saleyo
# Saleyo

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

## Getting Start

```sh
pip install saleyo
```

## Basic Tutorial

### Declear a `Mixin` class

```python
from saleyo import Mixin

class Foo:...


@Mixin(target = Foo)
class MixinFoo:...
```

### Use `MixinOperation`

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


class Foo:
__private = "private varible"

def demo(self):
pass


@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")

# Will Add the `func` to `Foo`
@OverWrite
def func(self):
print("hello saleyo")

# Will intercept the demo method and redirect to `lambda: print("hello world")`
@Intercept.configure(target_name="demo")
@staticmethod
def intercept_demo(_: InvokeEvent[None]):
return InvokeEvent.from_call(lambda: print("hello world"))

# Will call before `demo` call
@Pre.configure(target_name="demo")
def pre_demo(*arg):
print("pre hello world")

# Will call after `demo` call
@Post.configure(target_name="demo")
def post_demo(*arg):
print("post hello world")


foo: Any = (
Foo()
) # Add the typing hint to avoid the error message from some IDE plugins.

print(foo.__private) # Also `print(MixinFoo.private.value)`
foo.func()
foo.demo()

>>> private varible
>>> hello saleyo
>>> hello world
>>> post hello world
>>> pre hello world
```

### Custom Operation

```python
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:
...
```
12 changes: 5 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
[project]
name = "saleyo"
version = "0.1.0"
description = "A Package to do mixin in Python"
authors = [
{name = "H2Sxxa", email = "[email protected]"},
]
version = "0.1.1"
description = "Saleyo is a lightwight Python AOP framework, easy to use and integrate."
authors = [{ name = "H2Sxxa", email = "[email protected]" }]
dependencies = []
requires-python = ">=3.11"
requires-python = ">=3.8"
readme = "README.md"
license = {text = "MIT"}
license = { text = "MIT" }

[build-system]
requires = ["pdm-backend"]
Expand Down
6 changes: 6 additions & 0 deletions src/saleyo/base/toolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ def define_function(

@dataclass
class InvokeEvent(Generic[T]):
"""
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[..., T]
argument: Dict[str, Any]

Expand Down
16 changes: 9 additions & 7 deletions src/saleyo/mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,24 @@

class Mixin:
"""
A `Mixin` Decorator is used to invoke all the `MixinOperation` in Mixin Class
A `Mixin` Decorator is used to invoke all the `MixinOperation` in Mixin Class.
If the target is a special class, you should custom the toolchain yourself.
Allow to have more than one target, but that's not recommended.
"""

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

def __init__(
self,
target_class: Target,
target: Target,
toolchain: ToolChain = ToolChain(),
reverse_level: bool = False,
) -> None:
self.target_class = (
target_class if isinstance(target_class, list) else [target_class]
)
self.target = target if isinstance(target, list) else [target]
self.toolchain = toolchain
self.reverse_level = reverse_level

Expand All @@ -40,7 +42,7 @@ def collect(self, mixin: T) -> T:
)

for member in members:
for target in self.target_class:
for target in self.target:
member.mixin(target=target, toolchain=self.toolchain)
return mixin

Expand Down
7 changes: 7 additions & 0 deletions src/saleyo/operation/hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@


class Post(MixinOperation[Callable[[T], 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.
"""

target_name: Optional[str]

def __init__(
Expand Down Expand Up @@ -43,6 +47,9 @@ def post(*args, **kwargs):


class Pre(MixinOperation[Callable[..., RT]]):
"""
`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.
"""
target_name: Optional[str]

def __init__(
Expand Down
2 changes: 1 addition & 1 deletion src/saleyo/operation/intercept.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class Intercept(
"""
The `Intercept` allow you to intercept the arguments before invoking target method.
Then, you can handle these arguments in your own functions.
Then, you can handle these arguments in your own function.
"""

target_name: Optional[str]
Expand Down
8 changes: 6 additions & 2 deletions src/saleyo/operation/overwrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@
from ..base.template import MixinOperation


class OverWrite(MixinOperation[MethodType]):
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.
Try avoid using `OverWrite` with other `OverWrite`.
"""

target_name: Optional[str]

def __init__(
self,
argument: MethodType,
argument: Callable,
target_name: Optional[str] = None,
) -> None:
super().__init__(argument)
Expand Down
2 changes: 2 additions & 0 deletions src/saleyo/operation/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
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`.
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.
"""

module: Optional[ModuleType]
Expand Down
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
import intercept as intercept
import demo as demo
7 changes: 7 additions & 0 deletions tests/custom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
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:
...
46 changes: 46 additions & 0 deletions tests/demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from typing import Any
from saleyo import Mixin
from saleyo.operation import Accessor, OverWrite, Post, Pre, Intercept, InvokeEvent


class Foo:
__private = "private varible"

def demo(self):
pass


@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")

# Will Add the `func` to `Foo`
@OverWrite
def func(self):
print("hello saleyo")

# Will intercept the demo method and redirect to `lambda: print("hello world")`
@Intercept.configure(target_name="demo")
@staticmethod
def intercept_demo(_: InvokeEvent[None]):
return InvokeEvent.from_call(lambda: print("hello world"))

# Will call before `demo` call
@Pre.configure(target_name="demo")
def pre_demo(*arg):
print("pre hello world")

# Will call after `demo` call
@Post.configure(target_name="demo")
def post_demo(*arg):
print("post hello world")


foo: Any = (
Foo()
) # Add the typing hint to avoid the error message from some IDE plugins.

print(foo.__private) # Also `print(MixinFoo.private.value)`
foo.func()
foo.demo()
2 changes: 1 addition & 1 deletion tests/intercept.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def demo(self):
print("goodbye~")


@Mixin(target_class=Foo)
@Mixin(target=Foo)
class MixinFoo:
@Intercept.configure(target_name="demo")
@staticmethod
Expand Down

0 comments on commit 845086c

Please sign in to comment.