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

Commit

Permalink
docs: restructures and adds to documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
peterschutt committed Nov 7, 2022
1 parent bec1a52 commit 2f8875c
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 15 deletions.
2 changes: 1 addition & 1 deletion docs/async_worker.md
Expand Up @@ -8,7 +8,7 @@
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)
The generic [Service](../reference/starlite_saqlalchemy/service/#starlite_saqlalchemy.service.Service)
object includes a method that allows you to enqueue a background task.

### Example
Expand Down
7 changes: 7 additions & 0 deletions docs/config.md
@@ -0,0 +1,7 @@
# Configuring the application

Configuration is via environment. Here's an example `.env`:

```dotenv title="Example .env"
--8<-- ".env.example"
```
117 changes: 117 additions & 0 deletions docs/dto.md
@@ -0,0 +1,117 @@
# DTOs

- pydantic models generated from SQLAlchemy declarative models.
- DTOs have a purpose, read or write.
- Model attributes can have a mode, read-only or private.

## What are DTOs?

DTO stands for "Data Transfer Object". They are the filter through which data is accepted into, and
output from the application.

## DTO Factory

`starlite-saqlalchemy` includes
[`dto.factory()`](../reference/starlite_saqlalchemy/dto/#starlite_saqlalchemy.dto.factory)
which automatically creates [pydantic](https://pydantic-docs.helpmanual.io/) models from
[SQLAlchemy 2.0 ORM](https://docs.sqlalchemy.org/en/20/orm/) models.

### Creating a SQLAlchemy ORM model

If you are new to SQLAlchemy, I cannot recommend their docs enough. Start at
[the beginning](https://docs.sqlalchemy.org/en/20/orm/quickstart.html), and follow along until you
are comfortable. I won't even try to compete with the quality and depth of information that can be
found there - a credit to everyone who has contributed to that project over the years.

### Configuring generated DTOs

#### DTO Purpose

The `dto.Purpose` enum tells the factory if the purpose of the DTO is parse data submitted by the
client for updating or "writing" to a resource, or if it is to serialize data to be transmitted back
to, or "read" by the client.

For example, the DTO objects generated by the factory may differ due to the intended purpose of the
DTO:

```python
from starlite_saqlalchemy import dto

from domain.users import User


ReadDTO = dto.factory("UserReadDTO", model=User, purpose=dto.Purpose.READ)
WriteDTO = dto.factory("UserWriteDTO", model=User, purpose=dto.Purpose.WRITE)
```

#### DTO Mode

We use the `info` parameter to `mapped_column()` to guide `dto.factory()`.

The [dto.Mode](../reference/starlite_saqlalchemy/dto/#Mode) enumeration is used to indicate on the
SQLAlchemy ORM model, whether properties should always be private, or read-only.

Take this model, for example:

```python
from datetime import datetime

from sqlalchemy.orm import mapped_column
from starlite_saqlalchemy import dto, orm


class User(orm.Base):
name: str
password_hash: str = mapped_column(info={"dto": dto.Mode.PRIVATE})
updated_at: datetime = mapped_column(info={"dto": dto.Mode.READ_ONLY})


ReadDTO = dto.factory("UserReadDTO", model=User, purpose=dto.Purpose.READ)
WriteDTO = dto.factory("UserWriteDTO", model=User, purpose=dto.Purpose.WRITE)
```

Both `ReadDTO` and `WriteDTO` are pydantic models that have a `name` attribute.

Neither `ReadDTO` or `WriteDTO` have a `password_hash` attribute - this is the side effect of
marking the column with `dto.Mode.PRIVATE`. Columns that are marked private will never be included
in any generated DTO model, meaning that in the context of the application, they are unable to be
read or modified by the client.

`ReadDTO` has an `updated_at` field, while `WriteDTO` does not. This is the side effect of marking
the column with `dto.Mode.READ_ONLY` - these fields will only be included in DTOs generated for
`dto.Purpose.READ` and make sense for fields that have internally generated values.

The following class is pretty much the same as
[`orm.Base`](../reference/starlite_saqlalchemy/orm/#starlite_saqlalchemy.orm.Base) - the bundled
SQLAlchemy base class that comes with `starlite-saqlalchemy`.

```python
from datetime import datetime
from uuid import UUID, uuid4

from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

from starlite_saqlalchemy import dto


class Base(DeclarativeBase):
id: Mapped[UUID] = mapped_column(
default=uuid4, primary_key=True, info={"dto": dto.Mode.READ_ONLY}
)
"""Primary key column."""
created: Mapped[datetime] = mapped_column(
default=datetime.now, info={"dto": dto.Mode.READ_ONLY}
)
"""Date/time of instance creation."""
updated: Mapped[datetime] = mapped_column(
default=datetime.now, info={"dto": dto.Mode.READ_ONLY}
)
```

Notice that all these fields are marked as `dto.Mode.READ_ONLY`. This means that they are unable to
be modified by clients, even if they include values for them in the payloads to `POST`/`PUT`/`PATCH`
routes.

You can inherit from `orm.Base` to create your SQLAlchemy models, but you don't have to. You can
choose to subclass `orm.Base` or roll your own base class altogether. `dto.factory()` will still
work as advertised.
50 changes: 40 additions & 10 deletions docs/index.md
@@ -1,10 +1,27 @@
# starlite-saqlalchemy

Starlite, SQLAlchemy 2.0 and SAQ configuration plugin.
An API application pattern standing on the shoulders of:

- [Starlite](https://starlite-api.github.io/starlite/): "...a light, opinionated and flexible ASGI
API framework built on top of pydantic".
- [SQLAlchemy 2.0](https://docs.sqlalchemy.org/en/20/): "The Python SQL Toolkit and Object
Relational Mapper".
- [SAQ](https://github.com/tobymao/saq): "...a simple and performant job queueing framework built on
top of asyncio and redis".
- [Structlog](https://www.structlog.org/en/stable/): "...makes logging in Python faster, less
painful, and more powerful".

## Usage Example

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

Check out the [Usage](config/) section to see everything that is enabled by the framework!

## Pattern

This is the pattern that this application encourages.
This is the pattern encouraged by this framework:

``` mermaid
sequenceDiagram
Expand All @@ -28,14 +45,27 @@ sequenceDiagram
Depending on architecture, this may not be the same instance of the application that handled the
request.

## Usage Example
## Motivation

```py title="Simple Example"
--8<-- "examples/basic_example.py"
```
A modern, production-ready API application has a lot of components. Starlite, the backbone of this
library, exposes a plethora of features and functionality that requires some amount of boilerplate
and configuration that must be carried from one application implementation to the next.

Configuration via environment.
`starlite-saqlalchemy` is an example of how Starlite's `on_app_init` hook can be utilized to build
application configuration libraries that support streamlining the application development process.

```dotenv title="Example .env"
--8<-- ".env.example"
```
However, this library intends to be not only an example, but also an opinionated resource to support
the efficient, and consistent rollout of production ready API applications built on top of Starlite.

Use this library if the stack and design decisions suit your taste. If there are improvements or
generalizations that could be made to the library to support your use case, we'd love to hear about
them. Open [an issue](https://github.com/topsport-com-au/starlite-saqlalchemy/issues) or start
[a discussion](https://github.com/topsport-com-au/starlite-saqlalchemy/discussions).

## Backward compatibility and releases

This project follows semantic versioning, and we use
[semantic releases](https://python-semantic-release.readthedocs.io/en/latest/) in our toolchain.
This means that bug fixes and new features will find there way into a release as soon they hit the
main branch, and if we break something, we'll bump the major version number. However, until we hit
v1.0, there will be breaking changes between minor versions, but v1.0 is close!
5 changes: 3 additions & 2 deletions docs/logging.md
Expand Up @@ -107,7 +107,7 @@ as [PII](https://en.wikipedia.org/wiki/Personal_data) and secrets.
Thankfully, we have mechanisms to ensure that this type of data is excluded from our logs!

Our
[LogSettings](/reference/starlite_saqlalchemy/settings/#starlite_saqlalchemy.settings.LogSettings)
[LogSettings](../reference/starlite_saqlalchemy/settings/#starlite_saqlalchemy.settings.LogSettings)
object provides a host of options that allow you to customize log output. This exposes the following
environment variables:

Expand Down Expand Up @@ -218,7 +218,8 @@ contextvars for the job.
If logging configuration is enabled, we use this SAQ `Worker` hook to extract the configured `Job`
attributes and inject them into the log, and emit the log event. The attributes that are logged for
each `Job` can be configured in
[`LogSettings`](/reference/starlite_saqlalchemy/settings/#starlite_saqlalchemy.settings.LogSettings.JOB_FIELDS).
[`LogSettings`](../reference/starlite_saqlalchemy/settings/#starlite_saqlalchemy.settings.
LogSettings.JOB_FIELDS).

If the `Job.error` attribute is truthy, we log at `ERROR` severity, otherwise log at `INFO`.

Expand Down
7 changes: 5 additions & 2 deletions mkdocs.yml
Expand Up @@ -2,8 +2,11 @@ site_name: starlite-saqlalchemy
repo_url: https://github.com/topsport-com-au/starlite-saqlalchemy
nav:
- index.md
- Async Worker: async_worker.md
- Logging: logging.md
- Usage:
- Configuration: config.md
- DTOs: dto.md
- Async Worker: async_worker.md
- Logging: logging.md
- Reference: reference/
watch:
- src/starlite_saqlalchemy
Expand Down

0 comments on commit 2f8875c

Please sign in to comment.