-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
26 changed files
with
1,684 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# EditorConfig is awesome: https://EditorConfig.org | ||
|
||
# top-most EditorConfig file | ||
root = true | ||
|
||
[*] | ||
indent_style = space | ||
indent_size = 4 | ||
end_of_line = lf | ||
charset = utf-8 | ||
trim_trailing_whitespace = false | ||
insert_final_newline = false | ||
|
||
[*.yaml] | ||
indent_size = 2 | ||
|
||
[{Makefile,**.mk}] | ||
# Use tabs for indentation (Makefiles require tabs) | ||
indent_style = tab |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
default_language_version: | ||
python: python3.10 | ||
|
||
repos: | ||
# native hints instead of `from typing` | List -> list | ||
- repo: https://github.com/sondrelg/pep585-upgrade | ||
rev: 'v1.0' # Version to check | ||
hooks: | ||
- id: upgrade-type-hints | ||
|
||
# Only for removing unused imports > Other staff done by Black | ||
- repo: https://github.com/myint/autoflake | ||
rev: "v1.4" # Version to check | ||
hooks: | ||
- id: autoflake | ||
args: | ||
- --in-place | ||
- --remove-all-unused-imports | ||
- --ignore-init-module-imports | ||
|
||
- repo: https://github.com/pycqa/isort | ||
rev: 5.12.0 | ||
hooks: | ||
- id: isort | ||
name: isort (python) | ||
args: ["--profile", "black"] | ||
|
||
- repo: https://github.com/ambv/black | ||
rev: 22.3.0 | ||
hooks: | ||
- id: black |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
publish: | ||
python3 -m build | ||
python3 -m twine upload --repository pypi dist/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,67 @@ | ||
# lato | ||
Python microframework for modular monoliths and loosely coupled application | ||
# Lato | ||
|
||
## Overview | ||
Lato is a Python microframework designed for building modular monoliths and loosely coupled applications. | ||
|
||
## Features | ||
|
||
- **Modularity**: Organize your application into smaller, independent modules for better maintainability. | ||
|
||
- **Flexibility**: Loosely couple your application components, making them easier to refactor and extend. | ||
|
||
- **Minimalistic**: Intuitive and lean API for rapid development without the bloat. | ||
|
||
- **Testability**: Easily test your application components in isolation. | ||
|
||
## Installation | ||
|
||
Install `lato` using pip: | ||
|
||
```bash | ||
pip install lato | ||
``` | ||
|
||
## Quickstart | ||
|
||
Here's a simple example to get you started: | ||
|
||
```python | ||
from lato import Application, TransactionContext | ||
from uuid import uuid4 | ||
|
||
|
||
class UserService: | ||
def create_user(self, email, password): | ||
... | ||
|
||
|
||
class EmailService: | ||
def send_welcome_email(self, email): | ||
... | ||
|
||
|
||
app = Application( | ||
name="Hello World", | ||
# dependencies | ||
user_service=UserService(), | ||
email_service=EmailService(), | ||
) | ||
|
||
|
||
def create_user_use_case(email, password, session_id, ctx: TransactionContext, user_service: UserService): | ||
# session_id, TransactionContext and UserService are automatically injected by `ctx.call` | ||
print("Session ID:", session_id) | ||
user_service.create_user(email, password) | ||
ctx.emit("user_created", email) | ||
|
||
|
||
@app.on("user_created") | ||
def on_user_created(email, email_service: EmailService): | ||
email_service.send_welcome_email(email) | ||
|
||
|
||
with app.transaction_context(session_id=uuid4()) as ctx: | ||
# session_id is transaction scoped dependency | ||
result = ctx.call(create_user_use_case, "[email protected]", "password") | ||
``` | ||
|
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from lato.application import Application | ||
from lato.application_module import ApplicationModule | ||
from lato.transaction_context import TransactionContext | ||
|
||
__all__ = [Application, ApplicationModule, TransactionContext] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
from typing import Any, Callable | ||
|
||
from lato.application_module import ApplicationModule | ||
from lato.dependency_provider import DependencyProvider | ||
from lato.transaction_context import TransactionContext | ||
|
||
|
||
class Application(ApplicationModule): | ||
dependency_provider_class = DependencyProvider | ||
|
||
def __init__(self, name=__name__, dependency_provider=None, **kwargs): | ||
super().__init__(name) | ||
self.dependency_provider = ( | ||
dependency_provider or self.dependency_provider_class(**kwargs) | ||
) | ||
self._on_enter_transaction_context = lambda ctx: None | ||
self._on_exit_transaction_context = lambda ctx, exception=None: None | ||
self._transaction_middlewares = [] | ||
|
||
def get_dependency(self, identifier: Any) -> Any: | ||
"""Get a dependency from the dependency provider""" | ||
return self.dependency_provider.get_dependency(identifier) | ||
|
||
def __getitem__(self, item) -> Any: | ||
return self.get_dependency(item) | ||
|
||
def call(self, func: Callable | str, *args, **kwargs): | ||
with self.transaction_context() as ctx: | ||
result = ctx.call(func, *args, **kwargs) | ||
return result | ||
|
||
def on_enter_transaction_context(self, func): | ||
""" | ||
Decorator for registering a function to be called when entering a transaction context | ||
:param func: | ||
:return: | ||
""" | ||
self._on_enter_transaction_context = func | ||
return func | ||
|
||
def on_exit_transaction_context(self, func): | ||
""" | ||
Decorator for registering a function to be called when exiting a transaction context | ||
:param func: | ||
:return: | ||
""" | ||
self._on_exit_transaction_context = func | ||
return func | ||
|
||
def transaction_middleware(self, middleware_func): | ||
""" | ||
Decorator for registering a middleware function to be called when executing a function in a transaction context | ||
:param middleware_func: | ||
:return: | ||
""" | ||
self._transaction_middlewares.insert(0, middleware_func) | ||
return middleware_func | ||
|
||
def transaction_context(self, **dependencies) -> TransactionContext: | ||
""" | ||
Creates a transaction context with the application dependencies | ||
:param dependencies: | ||
:return: | ||
""" | ||
dp = self.dependency_provider.copy(**dependencies) | ||
ctx = TransactionContext(dependency_provider=dp) | ||
ctx.configure( | ||
on_enter_transaction_context=self._on_enter_transaction_context, | ||
on_exit_transaction_context=self._on_exit_transaction_context, | ||
middlewares=self._transaction_middlewares, | ||
handlers_iterator=self.iterate_handlers_for, | ||
) | ||
return ctx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
from collections import defaultdict | ||
|
||
from utils import OrderedSet | ||
|
||
|
||
class ApplicationModule: | ||
def __init__(self, name: str): | ||
self.name: str = name | ||
self._handlers: dict[str, set[callable]] = defaultdict(OrderedSet) | ||
self._submodules: OrderedSet[ApplicationModule] = OrderedSet() | ||
|
||
def include_submodule(self, a_module): | ||
assert isinstance( | ||
a_module, ApplicationModule | ||
), f"Can only include {ApplicationModule} instances, got {a_module}" | ||
self._submodules.add(a_module) | ||
|
||
def handler(self, alias): | ||
""" | ||
Decorator for registering use cases by name | ||
""" | ||
if callable(alias): | ||
func = alias | ||
alias = func.__name__ | ||
assert len(self._handlers[alias]) == 0 | ||
self._handlers[alias].add(func) | ||
return func | ||
|
||
def decorator(func): | ||
""" | ||
Decorator for registering use cases by name | ||
""" | ||
assert len(self._handlers[alias]) == 0 | ||
self._handlers[alias].add(func) | ||
return func | ||
|
||
return decorator | ||
|
||
def iterate_handlers_for(self, alias: str): | ||
if alias in self._handlers: | ||
for handler in self._handlers[alias]: | ||
yield handler | ||
for submodule in self._submodules: | ||
try: | ||
yield from submodule.iterate_handlers_for(alias) | ||
except KeyError: | ||
pass | ||
|
||
def on(self, event_name): | ||
# TODO: add matcher parameter | ||
def decorator(func): | ||
""" | ||
Decorator for registering an event handler | ||
:param event_handler: | ||
:return: | ||
""" | ||
self._handlers[event_name].add(func) | ||
return func | ||
|
||
return decorator | ||
|
||
def __repr__(self): | ||
return f"<{self.name} {object.__repr__(self)}>" |
Oops, something went wrong.