<img src="https://raw.githubusercontent.com/LuscasLeo/jararaca/main/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.jpeg" alt="README.md" width="250" float="right">
# Jararaca Microservice Framework
## Overview
Jararaca is a aio-first microservice framework that provides a set of tools to build and deploy microservices in a simple and clear way.
## Features
### Hexagonal Architecture
The framework is based on the hexagonal architecture, which allows you to separate the business logic from the infrastructure, making the code more testable and maintainable.
### Dependency Injection
The framework uses the dependency injection pattern to manage the dependencies between the components of the application.
```py
app = Microservice(
providers=[
ProviderSpec(
provide=Token(AuthConfig, "AUTH_CONFIG"),
use_value=AuthConfig(
secret="secret",
identity_refresh_token_expires_delta_seconds=60 * 60 * 24 * 30,
identity_token_expires_delta_seconds=60 * 60,
),
),
ProviderSpec(
provide=Token(AppConfig, "APP_CONFIG"),
use_factory=AppConfig.provider,
),
ProviderSpec(
provide=TokenBlackListService,
use_value=InMemoryTokenBlackListService(),
),
],
)
```
### Web Server Port
The framework provides a web server that listens on a specific port and routes the requests to the appropriate handler. It uses [FastAPI](https://fastapi.tiangolo.com/) as the web framework.
```py
@Delete("/{task_id}")
async def delete_task(self, task_id: TaskId) -> None:
await self.tasks_crud.delete_by_id(task_id)
await use_ws_manager().broadcast(("Task %s deleted" % task_id).encode())
```
### Message Bus
The framework provides a topic-based message bus that allows you to send messages between the components of the application. It uses [AIO Pika](https://aio-pika.readthedocs.io/) as the message broker worker and publisher.
```py
@IncomingHandler("task")
async def process_task(self, message: Message[Identifiable[TaskSchema]]) -> None:
name = generate_random_name()
now = asyncio.get_event_loop().time()
print("Processing task: ", name)
task = message.payload()
print("Received task: ", task)
await asyncio.sleep(random.randint(1, 5))
await use_publisher().publish(task, topic="task")
then = asyncio.get_event_loop().time()
print("Task Finished: ", name, " Time: ", then - now)
```
### Distributed Websocket
You can setup a room-based websocket server that allows you to send messages to a specific room or broadcast messages to all connected clients. All backend instances communicates with each other using a pub/sub mechanism (such as Redis).
```py
@WebSocketEndpoint("/ws")
async def ws_endpoint(self, websocket: WebSocket) -> None:
await websocket.accept()
counter.increment()
await use_ws_manager().add_websocket(websocket)
await use_ws_manager().join(["tasks"], websocket)
await use_ws_manager().broadcast(
("New Connection (%d) from %s" % (counter.count, self.hostname)).encode()
)
print("New Connection (%d)" % counter.count)
while True:
try:
await websocket.receive_text()
except WebSocketDisconnect:
counter.decrement()
await use_ws_manager().remove_websocket(websocket)
await use_ws_manager().broadcast(
(
"Connection Closed (%d) from %s"
% (counter.count, self.hostname)
).encode()
)
print("Connection Closed (%d)" % counter.count)
break
```
### Scheduled Routine
You can setup a scheduled routine that runs a specific task at a specific time or interval.
```py
...
@ScheduledAction("* * * * * */3", allow_overlap=False)
async def scheduled_task(self) -> None:
print("Scheduled Task at ", asyncio.get_event_loop().time())
print("sleeping")
await asyncio.sleep(5)
await use_publisher().publish(
message=Identifiable(
id=uuid4(),
data=TaskSchema(name=generate_random_name()),
),
topic="task",
)
```
### Observability
You can setup Observability Interceptors for logs, traces and metric collection with [OpenTelemetry](https://opentelemetry.io/docs)-based Protocols
```python
class HelloService:
def __init__(
self,
hello_rpc: Annotated[HelloRPC, Token(HelloRPC, "HELLO_RPC")],
):
self.hello_rpc = hello_rpc
@TracedFunc("ping") # Decorator for tracing
async def ping(self) -> HelloResponse:
return await self.hello_rpc.ping()
@TracedFunc("hello-service")
async def hello(
self,
gather: bool,
) -> HelloResponse:
now = asyncio.get_event_loop().time()
if gather:
await asyncio.gather(*[self.random_await(a) for a in range(10)])
else:
for a in range(10):
await self.random_await(a)
return HelloResponse(
message="Elapsed time: {}".format(asyncio.get_event_loop().time() - now)
)
@TracedFunc("random-await")
async def random_await(self, index: int) -> None:
logger.info("Random await %s", index, extra={"index": index})
await asyncio.sleep(random.randint(1, 3))
logger.info("Random await %s done", index, extra={"index": index})
```
## Installation
```bash
pip install jararaca
```
## Usage
### Create a Microservice
```python
# app.py
from jararaca import Microservice, create_http_server, create_messagebus_worker
from jararaca.presentation.http_microservice import HttpMicroservice
app = Microservice(
providers=[
ProviderSpec(
provide=Token[AppConfig],
use_factory=AppConfig.provider,
)
],
controllers=[TasksController],
interceptors=[
AIOSqlAlchemySessionInterceptor(
AIOSQAConfig(
connection_name="default",
url="sqlite+aiosqlite:///db.sqlite3",
)
),
],
)
# App for specific Http Configuration Context
http_app = HttpMicroservice(app)
web_app = create_http_server(app)
```
### Run as a Web Server
```bash
uvicorn app:web_app --reload
# or
jararaca server app:app
# or
jararaca server app:http_app
```
### Run as a Message Bus Worker
```bash
jararaca worker app:app
```
### Run as a scheduled routine
```bash
jararaca scheduler app:app
```
### Generate Typescript intefaces from microservice app controllers
```bash
jararaca gen-tsi app.main:app app.ts
```
### Documentation
Documentation is under construction [here](https://luscasleo.github.io/jararaca/).
Raw data
{
"_id": null,
"home_page": "https://github.com/LuscasLeo/jararaca",
"name": "jararaca",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.11",
"maintainer_email": null,
"keywords": null,
"author": "Lucas S",
"author_email": "me@luscasleo.dev",
"download_url": "https://files.pythonhosted.org/packages/f2/14/65d9e92e56da51f6548d7fdd7545a84844b7f1d549f71528070ab4b096c0/jararaca-0.1.56.tar.gz",
"platform": null,
"description": "<img src=\"https://raw.githubusercontent.com/LuscasLeo/jararaca/main/docs/assets/_f04774c9-7e05-4da4-8b17-8be23f6a1475.jpeg\" alt=\"README.md\" width=\"250\" float=\"right\">\n\n# Jararaca Microservice Framework\n\n## Overview\n\nJararaca is a aio-first microservice framework that provides a set of tools to build and deploy microservices in a simple and clear way.\n\n## Features\n\n### Hexagonal Architecture\n\nThe framework is based on the hexagonal architecture, which allows you to separate the business logic from the infrastructure, making the code more testable and maintainable.\n\n### Dependency Injection\n\nThe framework uses the dependency injection pattern to manage the dependencies between the components of the application.\n\n```py\n app = Microservice(\n providers=[\n ProviderSpec(\n provide=Token(AuthConfig, \"AUTH_CONFIG\"),\n use_value=AuthConfig(\n secret=\"secret\",\n identity_refresh_token_expires_delta_seconds=60 * 60 * 24 * 30,\n identity_token_expires_delta_seconds=60 * 60,\n ),\n ),\n ProviderSpec(\n provide=Token(AppConfig, \"APP_CONFIG\"),\n use_factory=AppConfig.provider,\n ),\n ProviderSpec(\n provide=TokenBlackListService,\n use_value=InMemoryTokenBlackListService(),\n ),\n ],\n )\n```\n\n### Web Server Port\n\nThe framework provides a web server that listens on a specific port and routes the requests to the appropriate handler. It uses [FastAPI](https://fastapi.tiangolo.com/) as the web framework.\n\n```py\n @Delete(\"/{task_id}\")\n async def delete_task(self, task_id: TaskId) -> None:\n await self.tasks_crud.delete_by_id(task_id)\n\n await use_ws_manager().broadcast((\"Task %s deleted\" % task_id).encode())\n```\n\n### Message Bus\n\nThe framework provides a topic-based message bus that allows you to send messages between the components of the application. It uses [AIO Pika](https://aio-pika.readthedocs.io/) as the message broker worker and publisher.\n\n```py\n @IncomingHandler(\"task\")\n async def process_task(self, message: Message[Identifiable[TaskSchema]]) -> None:\n name = generate_random_name()\n now = asyncio.get_event_loop().time()\n print(\"Processing task: \", name)\n\n task = message.payload()\n\n print(\"Received task: \", task)\n await asyncio.sleep(random.randint(1, 5))\n\n await use_publisher().publish(task, topic=\"task\")\n\n then = asyncio.get_event_loop().time()\n print(\"Task Finished: \", name, \" Time: \", then - now)\n```\n\n### Distributed Websocket\n\nYou can setup a room-based websocket server that allows you to send messages to a specific room or broadcast messages to all connected clients. All backend instances communicates with each other using a pub/sub mechanism (such as Redis).\n\n```py\n @WebSocketEndpoint(\"/ws\")\n async def ws_endpoint(self, websocket: WebSocket) -> None:\n await websocket.accept()\n counter.increment()\n await use_ws_manager().add_websocket(websocket)\n await use_ws_manager().join([\"tasks\"], websocket)\n await use_ws_manager().broadcast(\n (\"New Connection (%d) from %s\" % (counter.count, self.hostname)).encode()\n )\n\n print(\"New Connection (%d)\" % counter.count)\n\n while True:\n try:\n await websocket.receive_text()\n except WebSocketDisconnect:\n counter.decrement()\n await use_ws_manager().remove_websocket(websocket)\n\n await use_ws_manager().broadcast(\n (\n \"Connection Closed (%d) from %s\"\n % (counter.count, self.hostname)\n ).encode()\n )\n print(\"Connection Closed (%d)\" % counter.count)\n break\n```\n\n### Scheduled Routine\n\nYou can setup a scheduled routine that runs a specific task at a specific time or interval.\n\n```py\n...\n @ScheduledAction(\"* * * * * */3\", allow_overlap=False)\n async def scheduled_task(self) -> None:\n print(\"Scheduled Task at \", asyncio.get_event_loop().time())\n\n print(\"sleeping\")\n await asyncio.sleep(5)\n\n await use_publisher().publish(\n message=Identifiable(\n id=uuid4(),\n data=TaskSchema(name=generate_random_name()),\n ),\n topic=\"task\",\n )\n```\n\n### Observability\n\nYou can setup Observability Interceptors for logs, traces and metric collection with [OpenTelemetry](https://opentelemetry.io/docs)-based Protocols\n\n```python\nclass HelloService:\n def __init__(\n self,\n hello_rpc: Annotated[HelloRPC, Token(HelloRPC, \"HELLO_RPC\")],\n ):\n self.hello_rpc = hello_rpc\n\n @TracedFunc(\"ping\") # Decorator for tracing\n async def ping(self) -> HelloResponse:\n return await self.hello_rpc.ping()\n\n @TracedFunc(\"hello-service\")\n async def hello(\n self,\n gather: bool,\n ) -> HelloResponse:\n now = asyncio.get_event_loop().time()\n if gather:\n await asyncio.gather(*[self.random_await(a) for a in range(10)])\n else:\n for a in range(10):\n await self.random_await(a)\n return HelloResponse(\n message=\"Elapsed time: {}\".format(asyncio.get_event_loop().time() - now)\n )\n\n @TracedFunc(\"random-await\")\n async def random_await(self, index: int) -> None:\n logger.info(\"Random await %s\", index, extra={\"index\": index})\n await asyncio.sleep(random.randint(1, 3))\n logger.info(\"Random await %s done\", index, extra={\"index\": index})\n```\n\n## Installation\n\n```bash\npip install jararaca\n```\n\n## Usage\n\n### Create a Microservice\n\n```python\n# app.py\n\nfrom jararaca import Microservice, create_http_server, create_messagebus_worker\nfrom jararaca.presentation.http_microservice import HttpMicroservice\n\napp = Microservice(\n providers=[\n ProviderSpec(\n provide=Token[AppConfig],\n use_factory=AppConfig.provider,\n )\n ],\n controllers=[TasksController],\n interceptors=[\n AIOSqlAlchemySessionInterceptor(\n AIOSQAConfig(\n connection_name=\"default\",\n url=\"sqlite+aiosqlite:///db.sqlite3\",\n )\n ),\n ],\n)\n\n\n# App for specific Http Configuration Context\nhttp_app = HttpMicroservice(app)\n\nweb_app = create_http_server(app)\n\n```\n\n### Run as a Web Server\n\n```bash\nuvicorn app:web_app --reload\n# or\njararaca server app:app\n# or\njararaca server app:http_app\n\n```\n\n### Run as a Message Bus Worker\n\n```bash\njararaca worker app:app\n```\n\n### Run as a scheduled routine\n\n```bash\njararaca scheduler app:app\n```\n\n### Generate Typescript intefaces from microservice app controllers\n\n```bash\njararaca gen-tsi app.main:app app.ts\n```\n\n### Documentation\n\nDocumentation is under construction [here](https://luscasleo.github.io/jararaca/).\n",
"bugtrack_url": null,
"license": null,
"summary": "A simple and fast API framework for Python",
"version": "0.1.56",
"project_urls": {
"Homepage": "https://github.com/LuscasLeo/jararaca",
"Repository": "https://github.com/LuscasLeo/jararaca"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "4b477556dd53aa5fd5f58196c6b791a4a8f0c0fd655984fef2edaa6c50f60195",
"md5": "400fc3f1c1074709f3adece68958bfcd",
"sha256": "f090a5856720ddab8afc3ed4c0fb459034f11cef6fc2635714cc6e236ccab955"
},
"downloads": -1,
"filename": "jararaca-0.1.56-py3-none-any.whl",
"has_sig": false,
"md5_digest": "400fc3f1c1074709f3adece68958bfcd",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.11",
"size": 50422,
"upload_time": "2024-10-20T13:56:40",
"upload_time_iso_8601": "2024-10-20T13:56:40.690773Z",
"url": "https://files.pythonhosted.org/packages/4b/47/7556dd53aa5fd5f58196c6b791a4a8f0c0fd655984fef2edaa6c50f60195/jararaca-0.1.56-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "f21465d9e92e56da51f6548d7fdd7545a84844b7f1d549f71528070ab4b096c0",
"md5": "f18660a1ca04394083f8dc5e2bf10859",
"sha256": "a8b6e67cdeaa3fc1a673354bb57f9f8ab54a316b56a6fc7cd739b8935b57ea89"
},
"downloads": -1,
"filename": "jararaca-0.1.56.tar.gz",
"has_sig": false,
"md5_digest": "f18660a1ca04394083f8dc5e2bf10859",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.11",
"size": 311467,
"upload_time": "2024-10-20T13:56:43",
"upload_time_iso_8601": "2024-10-20T13:56:43.265096Z",
"url": "https://files.pythonhosted.org/packages/f2/14/65d9e92e56da51f6548d7fdd7545a84844b7f1d549f71528070ab4b096c0/jararaca-0.1.56.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-10-20 13:56:43",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "LuscasLeo",
"github_project": "jararaca",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "jararaca"
}