Skip to content
This repository has been archived by the owner on Sep 12, 2023. It is now read-only.

feat(version): v0.2.0 #83

Merged
merged 7 commits into from
Nov 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
BUILD_NUMBER=
DEBUG=true
ENVIRONMENT=
LOG_LEVEL=INFO
NAME=my-starlite-app

# API
Expand Down Expand Up @@ -32,3 +31,13 @@ DB_POOL_MAX_OVERFLOW=10
DB_POOL_SIZE=5
DB_POOL_TIMEOUT=30
DB_URL=postgresql+asyncpg://postgres:[email protected]:5432/db

# Log
LOG_EXCLUDE_PATHS="\A(?!x)x"
LOG_OBFUSCATE_COOKIES=["session"]
LOG_OBFUSCATE_HEADERS=["Authorization", "X-API-KEY"]
LOG_REQUEST_FIELDS=["path","method","content_type","headers","cookies","query","path_params","body"]
LOG_RESPONSE_FIELDS=["status_code","cookies","headers","body",]
LOG_HTTP_EVENT="HTTP"
LOG_WORKER_EVENT="Worker"
LOG_LEVEL=20
1 change: 1 addition & 0 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ per-file-ignores =
src/starlite_saqlalchemy/dependencies.py:TC
src/starlite_saqlalchemy/health.py:TC
src/starlite_saqlalchemy/repository/filters.py:TC
src/starlite_saqlalchemy/settings.py:TC
src/starlite_saqlalchemy/users/controllers.py:TC
tests/*:SCS108,PT013
tests/integration/test_tests.py:TC002,SCS108
Expand Down
4 changes: 3 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
default_language_version:
python: python3.10
python: python3.11
default_stages:
- commit
- push
Expand Down Expand Up @@ -37,6 +37,8 @@ repos:
rev: v2.2.2
hooks:
- id: codespell
additional_dependencies:
- "tomli"
- repo: https://github.com/asottile/blacken-docs
rev: v1.12.1
hooks:
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Thanks for taking an interest in `starlite-saqlalchemy`!

Hopefully this document makes it easy for you to get started contributing to `starlite-saqlalchemy`,
if not, [let me know!](https://github.com/topsport-com-au/starlite-saqlalchemy/issues)
if not, [let us know!](https://github.com/topsport-com-au/starlite-saqlalchemy/issues)

## Workflow

Expand Down
80 changes: 80 additions & 0 deletions docs/async_worker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Async Worker

- Integrated asynchronous worker for processing background jobs.
- Pattern is built on [SAQ](https://github.com/tobymao/saq) ([why SAQ?](#why-saq))

## Service object integration

You can leverage the async worker without needing to know anything specific about the worker
implementation.

The generic [Service](reference/starlite_saqlalchemy/service/#starlite_saqlalchemy.service.Service)
object includes a method that allows you to enqueue a background task.

### Example

Let's add a background task that sends an email whenever a new `Author` is created.

```python
from typing import Any

from starlite_saqlalchemy import service
from starlite_saqlalchemy.repository.sqlalchemy import SQLAlchemyRepository

from domain.authors import Author, ReadDTO


class Repository(SQLAlchemyRepository[Author]):
model_type = Author


class Service(service.Service[Author]):
"""Author service object."""

repository_type = Repository

async def create(self, data: Author) -> Author:
created = await super().create(data)
await self.enqueue_background_task(
"send_author_created_email", raw_author=ReadDTO.from_orm(created).dict()
)
return created

async def send_author_created_email(self, raw_author: dict[str, Any]) -> None:
"""Logic here to send the email."""
```

## Don't block the event loop

It is important to remember that this worker runs on the same event loop as the application itself,
so be mindful that the operations you do in background tasks aren't blocking the loop.

If you need to do computationally heavy work in background tasks, a better pattern would be to use a
something like [Honcho](https://honcho.readthedocs.io/en/latest/) to start an SAQ worker in a
different process to the Starlite application, and run your app in a multicore environment.

## Why SAQ

I like that it leverages [`BLMOVE`](https://redis.io/commands/blmove/) instead of polling to wait
for jobs: see [Pattern: Reliable queue](https://redis.io/commands/lmove/).

SAQ also make a direct comparison to `ARQ` in their
[`README`](https://github.com/tobymao/saq/blob/master/README.md#comparison-to-arq), so I'll let that
speak for itself:

> SAQ is heavily inspired by [ARQ](https://github.com/samuelcolvin/arq) but has several
> enhancements.
>
> 1. Avoids polling by leveraging [BLMOVE](https://redis.io/commands/blmove) or
> [RPOPLPUSH](https://redis.io/commands/rpoplpush) and NOTIFY
> i. SAQ has much lower latency than ARQ, with delays of < 5ms. ARQ's default polling frequency
> is 0.5 seconds
> ii. SAQ is up to [8x faster](benchmarks) than ARQ
> 2. Web interface for monitoring queues and workers
> 3. Heartbeat monitor for abandoned jobs
> 4. More robust failure handling
> i. Storage of stack traces
> ii. Sweeping stuck jobs
> iii. Handling of cancelled jobs different from failed jobs (machine redeployments)
> 5. Before and after job hooks
> 6. Easily run multiple workers to leverage more cores
33 changes: 19 additions & 14 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,17 @@

Starlite, SQLAlchemy 2.0 and SAQ configuration plugin.

```py title="Simple Example"
--8<-- "examples/basic_example.py"
```

Configuration via environment.

```dotenv title="Example .env"
--8<-- ".env.example"
```

## Pattern

This is the pattern that this application encourages.

``` mermaid
sequenceDiagram
Client ->> Controller: Inbound request data
Controller ->> Service: Invoke service with data validated by DTO
Service ->> Repository: View or modify the collection
Repository ->> Service: Detached SQLAlchemy instance(s)
Service ->> Queue: Enqueue async callback
Service ->> Queue: Optionally enqueue an async callback
Service ->> Controller: Outbound data
Controller ->> Client: Serialize via DTO
Queue ->> Worker: Worker invoked
Expand All @@ -29,8 +21,21 @@ sequenceDiagram

- Request data is deserialized and validated by Starlite before it is received by controller.
- Controller invokes relevant service object method and waits for response.
- Service method handles business logic of the request and triggers an asynchronous callback.
- Service method handles business logic of the request and optionally triggers an asynchronous
callback.
- Service method returns to controller and response is made to client.
- Async worker makes callback to service object where any async tasks can be performed.
Depending on architecture, this may not even be the same instance of the application that handled
the request.
Depending on architecture, this may not be the same instance of the application that handled the
request.

## Usage Example

```py title="Simple Example"
--8<-- "examples/basic_example.py"
```

Configuration via environment.

```dotenv title="Example .env"
--8<-- ".env.example"
```
Loading