Anywise let you write your application anywise
Project description
Anywise
Anywise is a framework for decoupling the business logic of your application from infrastructures.
This allows you to use the same code to handle message from various message sources, web api, message queue, AWS lambda, etc.
Source Code: https://github.com/raceychan/anywise
Documentation: On its way here...
Rationale
- promote best practices and enterprise architecture in python
- isolating bussiness logic from input ports, allowing one app for web api, kafka, flink, etc.
- let you write less code than other wise
Install
pip install anywise
Quck Start
Anywise integrates ididi for dependency injection. define your dependency after the message parameter, they will be resolved when you send a command or publish an event.
from anywise import Anywise, handler_registry, inject
class UserCommand: ...
class CreateUser(UserCommand): ...
class UserEvent: ...
class UserCreated(UserEvent): ...
registry = MessageRegistry(command_base=UserCommand, event_base= UserEvent)
@registry
async def create_user(
command: CreateUser,
anywise: Anywise,
service: UserService = inject(user_service_factory)
):
await service.create_user(command.username, command.user_email)
await anywise.publish(UserCreated(command.username, command.user_email))
@registry
async def notify_user(event: UserCreated, service: EmailSender):
await service.send_greeting(command.user_email)
# at your client code
async def main():
anywise = AnyWise()
anywise.include(user_registry)
result = await anywise.send(CreateUser())
Tutorial
register command handler / event listeners with MessageRegistry
registry = MessageRegistry(UserCommand)
registry(hanlder_func)
use MessageRegistry to decorate / register a function or a class as handlers of a command.
when a function is registered, anywise will can through its signature, if any param is annotated as a subclass of the base command type, it will be registered as a handler of the command.
when a class is registered, anywise will scan through its pulic methods, then repeat the steps to functions.
use Guard to intercept command handling.
from anywise import AnyWise, GuardRegistry, handler_registry
user_registry = MessageRegistry(command_base=UserCommand)
@user_registry
async def handler_create(create_user: CreateUser, context: dict[str, ty.Any]):
assert context["processed_by"]
return "done"
@user_registry
async def handler_update(update_user: UpdateUser, context: dict[str, ty.Any]):
return "done"
Guard that guard for a base command will handle all subcommand of the base command
@user_registry.pre_handle
async def mark(command: UserCommand, context: dict[str, ty.Any]) -> None:
if not context.get("processed_by"):
context["processed_by"] = ["1"]
else:
context["processed_by"].append("1")
in this case, mark will be called before handler_update or handler_create gets called.
a handler can also handle multiple command type
@user_registry
async def handle_multi(command: CreateUser | UpdateUser, context: dict[str, ty.Any]):
...
in this case, handle_multi will handle either CreateUser or UpdateUser
Advanced user-defined Guard
You might define a more advanced stateful guard by inheriting from BaseGuard
Example:
class LogginGuard(BaseGuard):
_next_guard: GuardFunc
def __init__(self, logger: ty.Any):
super().__init__()
self._logger = logger
async def __call__(self, message: object, context: dict[str, object]):
if (request_id := context.get("request_id")) is None:
context["request_id"] = request_id = str(uuid4())
with logger.contextualize(request_id=request_id):
try:
response = await self._next_guard(message, context)
except Exception as exc:
logger.error(exc)
else:
logger.success(
f"Logging request: {request_id}, got response `{response}`"
)
return response
user_registry.add_guard([UserCommand], LogginGuard(logger=logger))
Features
- builtin dependency injection
- handler guards
- framework integration
- remote handler
Current limitations
- currently
Anywise.senddoes not provide accurate typing information, but annotated as returntyping.AnyThis have no runtime effect, but is a good to have feature. It is expected to be solved before anywise v1.0.0
FAQ
On its way here...
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file anywise-0.1.3.tar.gz.
File metadata
- Download URL: anywise-0.1.3.tar.gz
- Upload date:
- Size: 51.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.5.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
20bbd970e4b719f4beb8faf73d7686b5beedccec7d6e1574b7a351c863b5db46
|
|
| MD5 |
d7e6477a6ac49b736e0d17732b1db6c5
|
|
| BLAKE2b-256 |
391bcb35c92384d1452eefc8029a174b3361bd9153ab7db3bc3daf5b79f01113
|
File details
Details for the file anywise-0.1.3-py3-none-any.whl.
File metadata
- Download URL: anywise-0.1.3-py3-none-any.whl
- Upload date:
- Size: 19.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.5.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1d498d26fbd6731c51a9e9aec1d1c86fc69a31718ea0197d9f09d230fe369d56
|
|
| MD5 |
a7aa159addc2e23a854de663d09e4a07
|
|
| BLAKE2b-256 |
40f4c0efe9b1eed06004e64513b7f100ab1531ef0e71ec7d20be2feb8eb29c85
|