Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
H2Sxxa committed Mar 15, 2024
0 parents commit 9528b19
Show file tree
Hide file tree
Showing 20 changed files with 587 additions and 0 deletions.
162 changes: 162 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm-project.org/#use-with-ide
.pdm.toml
.pdm-python
.pdm-build/

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"python.analysis.typeCheckingMode": "basic",
"python.analysis.autoImportCompletions": true
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# saleyo
8 changes: 8 additions & 0 deletions pdm.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[project]
name = "saleyo"
version = "0.1.0"
description = "A Package to do mixin in Python"
authors = [
{name = "H2Sxxa", email = "h2sxxa0w0@gmail.com"},
]
dependencies = []
requires-python = ">=3.11"
readme = "README.md"
license = {text = "MIT"}

[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"


[tool.pdm]
distribution = true
20 changes: 20 additions & 0 deletions src/saleyo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""
Saleyo is A module to modify external python code in runtime.
The implements are in `mixin`.
If you want to call the method manually, you can try the `function` module.
If you want to use some decorators, please use the `decorator` module.
The `base` module is used to extend your own `mixin` method.
"""

from . import decorator, function, mixin, base

__all__ = [
"decorator",
"function",
"mixin",
"base",
]
7 changes: 7 additions & 0 deletions src/saleyo/base/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from . import typing, template, toolchain

__all__ = [
"typing",
"template",
"toolchain",
]
23 changes: 23 additions & 0 deletions src/saleyo/base/template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from abc import ABC
from typing import Generic, Type

from .toolchain import ToolChain
from .typing import T


class MixinOperation(Generic[T], ABC):
"""
The MixinOperation is the base of All Operation.
The generic `MixinOperation` is the type of argument
"""

argument: T

def __init__(self, argument: T) -> None:
self.argument = argument

def mixin(self, target: Type, toolchain: ToolChain = ToolChain()) -> None:
raise NotImplementedError(
f"Not Ready to use this Operation to modify '{target}' via '{toolchain}'"
)
69 changes: 69 additions & 0 deletions src/saleyo/base/toolchain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from dataclasses import dataclass
from inspect import signature
from types import FunctionType
from typing import Any, Callable, Dict, Generic, Optional


from ..base.typing import T, NameSpace


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

tool_getattr: Callable[[Any, str], Any] = getattr
tool_setattr: Callable[[Any, str, Any], None] = setattr
tool_hasattr: Callable[[Any, str], bool] = hasattr


class Container:
"""
Container is A Class to keep a namespace and use this namespace to define function / variable / ...
"""

environment: NameSpace

def __init__(self, *namespace: Optional[NameSpace]) -> None:
self.environment = {
k: v for _ in namespace if _ is not None for k, v in _.items()
}

def exec(self, source: str) -> Dict[str, Any]:
exec(source, self.environment)
return self.environment

def define_function(
self, function_name: str, source: str, indent: Optional[int] = None
) -> FunctionType:
if indent is None:
indent = len(source[: source.find(source.lstrip()[0])])

return self.exec(
source="\n".join(
[line.removeprefix(" " * indent) for line in source.splitlines()]
)
)[function_name]


@dataclass
class InvokeEvent(Generic[T]):
target: Callable[..., T]
argument: Dict[str, Any]

@staticmethod
def from_call(target: Callable[..., Any], *args, **kwargs) -> "InvokeEvent":
argument = {}
function_parameters = signature(target).parameters
arg_names = list(function_parameters.keys())
argument.update({k: v.default for k, v in function_parameters.items()})
for arg in args:
argument[arg_names.pop(0)] = arg
argument.update(kwargs)
return InvokeEvent(target=target, argument=argument)

def invoke(self) -> T:
return self.target(**self.argument)
6 changes: 6 additions & 0 deletions src/saleyo/base/typing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from typing import Any, Dict, List, Type, TypeVar, Union

Target = Union[Type, List[Type]]
NameSpace = Dict[str, Any]
RT = TypeVar("RT")
T = TypeVar("T")
Empty file added src/saleyo/decorator.py
Empty file.
Empty file added src/saleyo/function.py
Empty file.
13 changes: 13 additions & 0 deletions src/saleyo/mixin/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from .mixin import Mixin
from .accessor import Accessor
from .overwrite import OverWrite
from .processor import Processor
from .intercept import Intercept

__all__ = [
"Mixin",
"Accessor",
"OverWrite",
"Processor",
"Intercept",
]
35 changes: 35 additions & 0 deletions src/saleyo/mixin/accessor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from typing import Generic, Optional, Type

from ..base.toolchain import ToolChain
from ..base.typing import T
from ..base.template import MixinOperation


class Accessor(Generic[T], MixinOperation[str]):
"""
Want to access and modify some private varibles or methods? Try use `Accessor`!
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 mixin(self, target: Type, toolchain: ToolChain = ToolChain()) -> None:
self._inner = toolchain.tool_getattr(
target,
f"_{target.__name__}{self.argument}",
)
return toolchain.tool_setattr(
target,
self.argument,
self._inner,
)

@property
def value(self) -> Optional[T]:
"""
Will be None Call Before The `mixin` method call.
"""
return self._inner
13 changes: 13 additions & 0 deletions src/saleyo/mixin/inject.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from types import MethodType
from typing import Type
from saleyo.base.template import MixinOperation
from saleyo.base.toolchain import ToolChain


class Inject(MixinOperation[MethodType]):
def mixin(
self,
target: Type,
toolchain: ToolChain = ToolChain(),
) -> None:
return super().mixin(target, toolchain)
Loading

0 comments on commit 9528b19

Please sign in to comment.