jararaca


Namejararaca JSON
Version 0.1.56 PyPI version JSON
download
home_pagehttps://github.com/LuscasLeo/jararaca
SummaryA simple and fast API framework for Python
upload_time2024-10-20 13:56:43
maintainerNone
docs_urlNone
authorLucas S
requires_python<4.0,>=3.11
licenseNone
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            <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"
}
        
Elapsed time: 0.53959s