Metadata-Version: 2.1
Name: steerage
Version: 2.0.0
Summary: Pluggable async storage backends for Python projects
Home-page: https://github.com/eykd/steerage/
License: BSD
Author: David Eyk
Author-email: david@worldsenoughstudios.com
Requires-Python: >=3.11,<4.0
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Plugins
Classifier: License :: OSI Approved :: BSD License
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Database :: Front-Ends
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Provides-Extra: postgresql
Provides-Extra: s3
Provides-Extra: sqlite
Requires-Dist: aioboto3 (>=12.2.0,<13.0.0) ; extra == "s3"
Requires-Dist: aiofiles (>=23.2.1,<24.0.0)
Requires-Dist: aiosqlite (>=0.19.0,<0.20.0) ; extra == "sqlite"
Requires-Dist: aiostream (>=0.5.2,<0.6.0)
Requires-Dist: asyncstdlib (>=3.12.0,<4.0.0)
Requires-Dist: convoke (>=2.0.1,<3.0.0)
Requires-Dist: funcy (>=2.0,<3.0)
Requires-Dist: httpx (>=0.26.0,<0.27.0)
Requires-Dist: pyrsistent (>=0.20.0,<0.21.0)
Requires-Dist: pytz (>=2023.3.post1,<2024.0)
Requires-Dist: sqlalchemy[asyncio] (>=2.0.25,<3.0.0) ; extra == "postgresql"
Project-URL: Documentation, https://eykd.github.io/steerage/latest/
Project-URL: Repository, https://github.com/eykd/steerage
Description-Content-Type: text/markdown

# Steerage

Pluggable async storage backends for Python projects.

This project implements the [Repository Pattern][repository] for data
access, using Pydantic 2.0 models for entity composition and
decomposition.

[repository]: https://martinfowler.com/eaaCatalog/repository.html


## Features

- One model, many possible storage backends: Start simple and add complexity as
  you need it.
- Use a fast, ephemeral in-memory database for tests, and a slow, reliable SQL
  database for production.
- One unified query interface: write most queries once, apply them to any
  backend.
- Simple environment-based configuration with [Convoke][convoke]
- 100% test coverage

[convoke]: https://eykd.github.io/convoke/latest/


## Installation

With Pip:

    pip install steerage


## Example

Let's create a simple blog engine. First, we'll want a Pydantic entity model:

``` python
from uuid import UUID
from datetime import datetime
from pydantic import BaseModel, Field
from pydantic.types import AwareDatetime

class Entry(BaseModel):
    id: UUID
    path: str
    title: str
    body: str

    created_at: AwareDatetime = Field(
        default_factory=lambda: datetime.utcnow().replace(tzinfo=timezone.utc)
    )
    published_at: AwareDatetime | None = Field(default=None)
```

Models are all fine and good, but we need to be able to store it
somewhere. Let's start simple, with an in-memory repository and a repository
factory:

``` python
from steerage.repositories.memdb import AbstractInMemoryRepository


class InMemoryEntryRepository(AbstractInMemoryRepository):
    table_name: str = "entries"
    entity_class = Entry


def get_entry_repository():
    return InMemoryEntityRepository()
```

Now, in the async request handler, we can easily create an entry:

``` python
async def create_entry(request: Request):
    form = await EntryForm.from_request(request)
    if form.is_valid():
        repo = get_entry_repository()
        entry = Entry.model_validate(form)
        await repo.insert(entry)
        return redirect('entry-admin')
```

... and retrieve an entry:

``` python
async def get_entry(request: Request, id: UUID):
    repo = get_entry_repository()
    entry = await repo.get(id)
    return render('entry.html', {'entry': entry})
```

This is great, but whenever we restart the server, we lose all our data!

Let's go a step up and add a more durable disk-based repository:

``` python
from convoke.configs import BaseConfig
from steerage.repositories.shelvedb import ShelveInMemoryRepository


class ShelveEntryRepository(AbstractShelveRepository):
    table_name: str = "entries"
    entity_class = Entry


def get_entry_repository():
    config = BaseConfig()
    if config.TESTING:
        return InMemoryEntityRepository()
    else:
        return ShelveEntityRepository()
```

Now we have a durable repository for development use, and an in-memory
repository for our tests.


## Contribute

- Source Code: https://github.com/eykd/steerage
- Issue Tracker: https://github.com/eykd/steerage/issues


## License

The project is licensed under the BSD 3-clause license.

