Skip to content

Commit

Permalink
finalize extensiosn
Browse files Browse the repository at this point in the history
  • Loading branch information
livioribeiro committed Feb 8, 2024
1 parent 4a4a6f8 commit 030a076
Show file tree
Hide file tree
Showing 45 changed files with 190 additions and 99 deletions.
34 changes: 32 additions & 2 deletions docs/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,41 @@ Selva offers configuration options for templates.

```yaml
templates:
backend: "selva.ext.templates.jinja" # (1)
backend: "jinja" # (1)
paths: # (2)
["resources/templates"]
```
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.
2. Paths that will be used to look for templates.
## 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
# ...
```
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
1 change: 1 addition & 0 deletions examples/templates/jinja/configuration/settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ extensions:

templates:
jinja:
optimized: true
extensions:
- jinja2.ext.i18n
- jinja2.ext.debug
2 changes: 1 addition & 1 deletion examples/templates/jinja/resources/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<body>
<h1>{{ heading }}</h1>
<pre>
{% debug %}
{%- debug %}
</pre>
</body>
</html>
13 changes: 7 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,13 @@ uvicorn = { version = "^0.23", extras = ["standard"] }
optional = true

[tool.poetry.group.test.dependencies]
pytest = "^8.0"
pytest-asyncio = "^0.23"
pytest-cov = "^4.1"
coverage = { version = "^7.4", extras = ["toml"] }
pytest = "^8"
# TODO: remove "allow-prereleases" once pytest-asyncio supports pytest 8
pytest-asyncio = { version = "^0.23", allow-prereleases = true }
pytest-cov = "^4"
coverage = { version = "^7", extras = ["toml"] }
httpx = "^0.26"
aiosqlite = "0.19"
aiosqlite = "^0.19"

[tool.poetry.group.lint]
optional = true
Expand Down Expand Up @@ -91,7 +92,7 @@ ignore_missing_imports = true

[tool.black]
line-length = 88
target-version = ['py310']
target-version = ["py311", "py312"]
extend-exclude = "^/tests/configuration/invalid_settings/configuration/settings\\.py"

[build-system]
Expand Down
2 changes: 1 addition & 1 deletion src/selva/configuration/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def get_settings_for_profile(profile: str = None) -> dict[str, Any]:
settings_file_path = settings_file_path.absolute()

try:
logger.debug("settings loaded from {}", settings_file_path)
logger.info("settings loaded from {}", settings_file_path)
yaml = YAML(typ="safe")
return yaml.load(settings_file_path) or {}
except FileNotFoundError:
Expand Down
19 changes: 11 additions & 8 deletions src/selva/ext/templates/jinja/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@
from selva.di.container import Container
from selva.web.templates import Template

from loguru import logger


def selva_extension(container: Container, settings: Settings):
async def selva_extension(container: Container, settings: Settings):
backend = settings.templates.backend

if backend and backend != __package__:
if backend and backend != "jinja":
return

if not backend and container.has(Template):
raise ValueError("Template backend already registered. Please define `templates.backend` property")
if not backend and (current := await container.get(Template, optional=True)):
cls = current.__class__
current_class = f"{cls.__module__}.{cls.__qualname__}"

raise ValueError(
f"Template backend already registered with '{current_class}'. "
"Please define `templates.backend` property."
)

from . import service
container.scan(service)
container.scan(f"{__package__}.service")
4 changes: 2 additions & 2 deletions src/selva/web/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ async def _handle_request(self, scope, receive, send):
):
logger.trace(
"Handling exception with handler {}.{}",
handler.__module__,
handler.__qualname__,
handler.__class__.__module__,
handler.__class__.__qualname__,
)
await handler.handle_exception(request, err)
else:
Expand Down
2 changes: 0 additions & 2 deletions src/selva/web/converter/from_request_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ async def from_request(
_metadata=None,
) -> PydanticModel:
if request.method not in (HTTPMethod.POST, HTTPMethod.PUT, HTTPMethod.PATCH):
# TODO: improve error
raise TypeError(
"Pydantic model parameter on method that does not accept body"
)
Expand Down Expand Up @@ -48,7 +47,6 @@ async def from_request(
_metadata=None,
) -> list[PydanticModel]:
if request.method not in (HTTPMethod.POST, HTTPMethod.PUT, HTTPMethod.PATCH):
# TODO: improve error
raise TypeError("Pydantic parameter on method that does not accept body")

if "application/json" in request.content_type:
Expand Down
2 changes: 0 additions & 2 deletions tests/configuration/test_environment.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import pytest

from selva.configuration.environment import (
parse_settings_from_env,
replace_variables_recursive,
Expand Down
3 changes: 1 addition & 2 deletions tests/configuration/test_settings.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import logging
import os
from pathlib import Path

import pytest
Expand Down Expand Up @@ -259,7 +258,7 @@ def test_non_existent_env_var_should_fail(monkeypatch):

with pytest.raises(
ValueError,
match=f"DOES_NOT_EXIST environment variable is not defined and does not contain a default value",
match="DOES_NOT_EXIST environment variable is not defined and does not contain a default value",
):
_get_settings_nocache()

Expand Down
File renamed without changes.
2 changes: 0 additions & 2 deletions tests/di/test_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
from selva.di.container import Container
from selva.di.inject import Inject

from .fixtures import ioc


class Service1:
pass
Expand Down
2 changes: 0 additions & 2 deletions tests/di/test_define_instance.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from selva.di.container import Container

from .fixtures import ioc


class Service:
pass
Expand Down
2 changes: 0 additions & 2 deletions tests/di/test_dependency_complex_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
from selva.di.decorator import service
from selva.di.inject import Inject

from .fixtures import ioc


@service
class Service1:
Expand Down
2 changes: 0 additions & 2 deletions tests/di/test_dependency_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
from selva.di.container import Container
from selva.di.inject import Inject

from .fixtures import ioc


class Service1:
service2: Annotated["Service2", Inject]
Expand Down
2 changes: 0 additions & 2 deletions tests/di/test_dependency_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
from selva.di.container import Container
from selva.di.inject import Inject

from .fixtures import ioc


class DependentService:
pass
Expand Down
2 changes: 0 additions & 2 deletions tests/di/test_generics.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
from selva.di.error import TypeVarInGenericServiceError
from selva.di.inject import Inject

from .fixtures import ioc

T = TypeVar("T")


Expand Down
2 changes: 0 additions & 2 deletions tests/di/test_interceptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

from selva.di.container import Container

from .fixtures import ioc


class TestInterceptor:
async def intercept(self, instance: Any, _service_type: type):
Expand Down
2 changes: 0 additions & 2 deletions tests/di/test_iter_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
from selva.di.container import Container
from selva.di.error import ServiceNotFoundError

from .fixtures import ioc


class Interface:
pass
Expand Down
2 changes: 0 additions & 2 deletions tests/di/test_lifecycle_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
from selva.di.container import Container
from selva.di.inject import Inject

from .fixtures import ioc


class Dependency:
pass
Expand Down
2 changes: 0 additions & 2 deletions tests/di/test_named_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
from selva.di.error import ServiceAlreadyRegisteredError, ServiceNotFoundError
from selva.di.inject import Inject

from .fixtures import ioc


class DependentService:
pass
Expand Down
2 changes: 0 additions & 2 deletions tests/di/test_non_injectable.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
from selva.di.container import Container
from selva.di.error import NonInjectableTypeError

from .fixtures import ioc


def test_non_injectable_type_should_fail(ioc: Container):
obj = ()
Expand Down
2 changes: 0 additions & 2 deletions tests/di/test_register_decorated_class.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from selva.di.container import Container
from selva.di.decorator import service

from .fixtures import ioc


@service
class Service:
Expand Down
2 changes: 0 additions & 2 deletions tests/di/test_scan_package.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from selva.di.container import Container

from .fixtures import ioc


async def test_scan_package_by_module(ioc: Container):
from .services import scan_package as module
Expand Down
2 changes: 0 additions & 2 deletions tests/di/test_service_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
from selva.di.error import ServiceAlreadyRegisteredError
from selva.di.inject import Inject

from .fixtures import ioc


class Service1:
pass
Expand Down
2 changes: 0 additions & 2 deletions tests/di/test_service_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
from selva.di.container import Container
from selva.di.error import FactoryMissingReturnTypeError, ServiceAlreadyRegisteredError

from .fixtures import ioc


class Service1:
pass
Expand Down
4 changes: 1 addition & 3 deletions tests/di/test_service_generator.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import pytest

from selva.di.container import Container
from selva.di.error import FactoryMissingReturnTypeError, ServiceAlreadyRegisteredError

from .fixtures import ioc
from selva.di.error import FactoryMissingReturnTypeError


class Service1:
Expand Down
4 changes: 1 addition & 3 deletions tests/di/test_service_generator_async.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import pytest

from selva.di.container import Container
from selva.di.error import FactoryMissingReturnTypeError, ServiceAlreadyRegisteredError

from .fixtures import ioc
from selva.di.error import FactoryMissingReturnTypeError


class Service1:
Expand Down
2 changes: 0 additions & 2 deletions tests/di/test_unknown_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
from selva.di.container import Container
from selva.di.error import ServiceNotFoundError

from .fixtures import ioc


class Service:
pass
Expand Down
21 changes: 21 additions & 0 deletions tests/ext/data/sqlalchemy/application_named.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from typing import Annotated

from asgikit.responses import respond_text
from sqlalchemy import text
from sqlalchemy.ext.asyncio import async_sessionmaker

from selva.di import Inject
from selva.web import controller, get


@controller
class Controller:
sessionmaker: Annotated[async_sessionmaker, Inject(name="other")]

@get
async def index(self, request):
async with self.sessionmaker() as session:
result = await session.execute(text("select sqlite_version()"))
version = result.first()[0]

await respond_text(request.response, version)
Loading

0 comments on commit 030a076

Please sign in to comment.