Skip to content

Commit

Permalink
Feature/extensions (#38)
Browse files Browse the repository at this point in the history
* add support for extensions

* change jinja support to extension
  • Loading branch information
livioribeiro authored Feb 8, 2024
1 parent 8937ea7 commit a39fd7b
Show file tree
Hide file tree
Showing 92 changed files with 838 additions and 566 deletions.
32 changes: 23 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,35 @@ on:
- pyproject.toml
- 'src/**'
- 'tests/**'
pull_request:
branches:
- main

env:
POSTGRES_DB: test_db

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
version:
- "3.11"
- "3.12"
version: ["3.11", "3.12"]
container: python:${{ matrix.version }}
services:
postgres:
image: postgres:16.1-alpine
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.version }}
- run: pip install pipx
- run: pipx install poetry
- run: pip install pipx && pipx install poetry
- run: pip install asyncpg
- run: poetry install --with test --extras jinja --extras sqlalchemy --no-interaction
- run: poetry run pytest
- env:
POSTGRES_URL: postgresql+asyncpg://postgres:postgres@postgres:5342/${{ POSTGRES_DB }}
run: poetry run pytest
63 changes: 63 additions & 0 deletions docs/extensions/jinja.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Jinja

This extension offers support for Jinja templates.

## Usage

To use jinja templates, first install the `jinja` extra:

```shell
pip install selva[jinja]
```

Then activate the extension:

=== "configuration/settings.yaml"
```yaml
extensions:
- selva.ext.templates.jinja
```

## Configuration

Jinja can be configured through the `settings.yaml`. For example, to activate extensions:

=== "configuration/settings.yaml"

```yaml
templates:
jinja:
extensions:
- jinja2.ext.i18n
- jinja2.ext.debug
```

Full list of settings:

```yaml
templates:
jinja:
block_start_string: ""
block_end_string: ""
variable_start_string: ""
variable_end_string: ""
comment_start_string: ""
comment_end_string: ""
line_statement_prefix: ""
line_comment_prefix: ""
trim_blocks: true
lstrip_blocks: true
newline_sequence: "\n" # or "\r\n" or "\r"
keep_trailing_newline: true
extensions:
- extension1
- extensions2
optimized: true
undefined: "" # dotted path to python class
finalize: "" # dotted path to python function
autoescape: "" # dotted path to python function
loader: "" # dotted path to python object
cache_size: 1
auto_reload: true
bytecode_cache: "" # dotted path to python object
```
38 changes: 35 additions & 3 deletions docs/extensions/overview.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,39 @@
# Overview

Selva provides extensions to integrate other libraries and funcionality
Extensions are python packages that provide additional functionality or integrate
external libraries into the framework.

Current provided extensions are:
Current builtin extensions are:

- [SQLAlchemy](../extensions/sqlalchemy.md)
- [SQLAlchemy](./sqlalchemy.md)
- [Jinja](./jinja.md)

## Activating extensions

Extensions need to be activated in `settings.yaml`, in the `extensions` property:

```yaml
extensions:
- selva.ext.data.sqlalchemy
- selva.ext.templates.jinja
```
## Creating extensions
An extension is a python package or module that contains a function named `selva_extension`
with arguments `selva.di.Container` and `selva.configuration.Settings`. It is called
during the startup phase of the application and may also be a coroutine.

```python
from selva.configuration import Settings
from selva.di import Container
# (1)
def selva_extension(container: Container, settings: Settings):
pass
```

1. `selva_extension` can also be `async`.

The function can then access values in the settings object, register new services,
retrieve the router service to register new routes, etc.
8 changes: 5 additions & 3 deletions docs/extensions/sqlalchemy.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ injection context.
Install SQLAlchemy python package and the database driver:

```shell
pip install sqlalchemy aiosqlite aiomysql psycopg oracledb
pip install selva[sqlalchemy] aiosqlite aiomysql psycopg oracledb
```

Define the configuration properties for the database:
Expand All @@ -32,8 +32,8 @@ Define the configuration properties for the database:
url: "oracle+oracledb_async://user:pass@localhost/?service_name=XEPDB1"
```

1. "default" connection will be registered without name
2. Will be registered as "postgres"
1. "default" connection will be registered without a name
2. Will be registered with the name "postgres"

=== "application/service.py"
```python
Expand Down Expand Up @@ -111,6 +111,8 @@ data:
default:
url: ""
options: # (1)
connect_args:
arg: value
echo: false
echo_pool: false
enable_from_linting: false
Expand Down
79 changes: 42 additions & 37 deletions docs/templates.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
Selva offers support for Jinja templates.
# Templates

To use templates, first install the `jinja` extra:
Selva offers support for rendering html responses from templates.

```shell
pip install selva[jinja]
```
## Usage

First install the [Jinja extension](./extensions/jinja.md).

Template files are located in the `resources/templates directory`:
By default, template files are located in the `resources/templates directory`:

```
project/
Expand Down Expand Up @@ -67,40 +67,45 @@ the result.

## Configuration

Jinja can be configured through the `settings.yaml`. For exmaple, to activate extensions:
Selva offers configuration options for templates.

```yaml
templates:
jinja:
extensions:
- jinja2.ext.i18n
- jinja2.ext.debug
backend: "jinja" # (1)
paths: # (2)
["resources/templates"]
```
Full settings list:
1. If there are more extensions that provide templates, the backend property can
be used to choose which one to use.
2. Paths that will be used to look for templates.
```yaml
templates:
jinja:
block_start_string:
block_end_string:
variable_start_string:
variable_end_string:
comment_start_string:
comment_end_string:
line_statement_prefix:
line_comment_prefix:
trim_blocks:
lstrip_blocks:
newline_sequence:
keep_trailing_newline:
extensions:
optimized:
undefined:
finalize:
autoescape:
loader:
cache_size:
auto_reload:
bytecode_cache:
```
## Extensions providing templates
If you are writing an extension that provides a `selva.web.templates.Template` implementation,
make sure to check whether the value of configuration property `templates.backend`
matches with your extension's `__package__` or no other implementation has been
registered yet.

For example, the function `selva_extension` in your extension could be implemented
like the following:

=== "my/extension/__init__.py"

```python
from selva.configuration.settings import Settings
from selva.di.container import Container
from selva.web.templates import Template
def selva_extension(container: Container, settings: Settings):
backend = settings.templates.backend
if backend and backend != __package__:
return
if not backend and container.has(Template):
return
# ...
```
File renamed without changes.
2 changes: 2 additions & 0 deletions examples/background_tasks/configuration/settings.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
logging:
root: INFO
2 changes: 1 addition & 1 deletion examples/database/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
databases[aiosqlite]==0.6.0
databases[aiosqlite]==0.8.0
2 changes: 2 additions & 0 deletions examples/htmx/configuration/settings.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
extensions:
- selva.ext.templates.jinja
30 changes: 0 additions & 30 deletions examples/sqlalchemy/application.py

This file was deleted.

File renamed without changes.
28 changes: 28 additions & 0 deletions examples/sqlalchemy/application/controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from typing import Annotated

from asgikit.responses import respond_json
from selva.di import Inject
from selva.web import controller, get

from .service import DefautDBService, OtherDBService


@controller
class Controller:
default_db_service: Annotated[DefautDBService, Inject]
other_db_service: Annotated[OtherDBService, Inject]

@get
async def index(self, request):
db_version = await self.default_db_service.db_version()
model = await self.default_db_service.get_model()
dto = {
"id": model.id,
"name": model.name,
}
await respond_json(request.response, {"db_version": db_version, "model": dto})

@get("other")
async def other(self, request):
db_version = await self.other_db_service.db_version()
await respond_json(request.response, {"db_version": db_version})
15 changes: 15 additions & 0 deletions examples/sqlalchemy/application/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import DeclarativeBase


class Base(DeclarativeBase):
pass


class MyModel(Base):
__tablename__ = 'my_model'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(length=100))

def __repr__(self):
return f"<MyModel(id={self.id}, name={self.name})>"
Loading

0 comments on commit a39fd7b

Please sign in to comment.