Skip to content

Commit

Permalink
add render and render_str method for Template
Browse files Browse the repository at this point in the history
  • Loading branch information
livioribeiro committed Dec 17, 2023
1 parent 11d7dbf commit 3d20183
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 14 deletions.
32 changes: 27 additions & 5 deletions src/selva/web/templates/jinja.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pathlib import Path
from typing import Annotated, Literal, Type, TypeVar

from asgikit.responses import Response, respond_text
from asgikit.responses import Response, respond_stream, respond_text
from jinja2 import (
BaseLoader,
BytecodeCache,
Expand Down Expand Up @@ -73,10 +73,32 @@ async def respond(
self,
response: Response,
template_name: str,
context: dict,
context: dict = None,
*,
status: HTTPStatus = HTTPStatus.OK,
content_type: str = None,
stream: bool = False,
):
response.content_type = "text/html"
context = context or {}

if content_type:
response.content_type = content_type
elif not response.content_type:
response.content_type = "text/html"

template = self.environment.get_template(template_name)

if stream:
render_stream = template.generate_async(context)
await respond_stream(response, render_stream, status=status)
else:
rendered = await template.render_async(context)
await respond_text(response, rendered, status=status)

async def render(self, template_name: str, context: dict) -> str:
template = self.environment.get_template(template_name)
rendered = await template.render_async(context)
await respond_text(response, rendered, status=status)
return await template.render_async(context)

async def render_str(self, source: str, context: dict) -> str:
template = self.environment.from_string(source)
return await template.render_async(context)
15 changes: 13 additions & 2 deletions src/selva/web/templates/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,18 @@ async def respond(
self,
response: Response,
template_name: str,
context: dict,
context: dict = None,
*,
status: HTTPStatus = HTTPStatus.OK,
content_type: str = None,
stream: bool = False,
):
pass
raise NotImplementedError()

@abstractmethod
async def render(self, template_name: str, context: dict) -> str:
raise NotImplementedError()

@abstractmethod
async def render_str(self, source: str, context: dict) -> str:
raise NotImplementedError()
57 changes: 57 additions & 0 deletions tests/web/templates/application.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from typing import Annotated

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


@controller
class Controller:
template: Annotated[Template, Inject]

@get("/render")
async def render(self, request):
await self.template.respond(
request.response,
"template.html",
{"variable": "Jinja"},
)

@get("/stream")
async def stream(self, request):
await self.template.respond(
request.response,
"template.html",
{"variable": "Jinja"},
stream=True,
)

@get("/define_content_type")
async def define_content_type(self, request):
await self.template.respond(
request.response,
"template.html",
{"variable": "Jinja"},
content_type="text/defined",
)

@get("/override_content_type")
async def override_content_type(self, request):
request.response.content_type = "text/plain"

await self.template.respond(
request.response,
"template.html",
{"variable": "Jinja"},
content_type="text/overriden",
)

@get("/content_type_from_response")
async def content_type_from_response(self, request):
request.response.content_type = "text/from_response"

await self.template.respond(
request.response,
"template.html",
{"variable": "Jinja"},
)
1 change: 1 addition & 0 deletions tests/web/templates/template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ variable }}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import jinja2
import pytest
from pydantic import ValidationError

from selva.web.templates.jinja import JinjaTemplateSettings

Expand Down Expand Up @@ -56,30 +57,31 @@ def test_autoescape_bool(value, expected):


def test_invalid_undefined_should_fail():
with pytest.raises(TypeError):
with pytest.raises(ValidationError):
JinjaTemplateSettings.model_validate({"undefined": 1})


def test_invalid_import_undefined_should_fail():
with pytest.raises(ImportError):
with pytest.raises(ValidationError):
JinjaTemplateSettings.model_validate({"undefined": "does.not.exist"})


def test_invalid_finalize_should_fail():
with pytest.raises(TypeError):
with pytest.raises(ValidationError):
JinjaTemplateSettings.model_validate({"finalize": 1})


def test_invalid_import_finalize_should_fail():
with pytest.raises(ImportError):
with pytest.raises(ValidationError):
JinjaTemplateSettings.model_validate({"finalize": "does.not.exist"})


def test_invalid_autoescape_should_fail():
with pytest.raises(TypeError):
JinjaTemplateSettings.model_validate({"autoescape": 1})
with pytest.raises(ValidationError):
model = JinjaTemplateSettings.model_validate({"autoescape": "invalid"})
repr(model)


def test_invalid_import_autoescape_should_fail():
with pytest.raises(ImportError):
with pytest.raises(ValidationError):
JinjaTemplateSettings.model_validate({"autoescape": "does.not.exist"})
23 changes: 23 additions & 0 deletions tests/web/templates/test_jinja_render.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from pathlib import Path

from selva.configuration.defaults import default_settings
from selva.configuration.settings import Settings
from selva.web.templates.jinja import JinjaTemplate


async def test_render_template():
path = str(Path(__file__).parent.absolute())
settings = Settings(default_settings | {"templates": {"jinja": {"path": path}}})

template = JinjaTemplate(settings)
template.initialize()
result = await template.render("template.html", {"variable": "Jinja"})
assert result == "Jinja"


async def test_render_str():
settings = Settings(default_settings)
template = JinjaTemplate(settings)
template.initialize()
result = await template.render_str("{{ variable }}", {"variable": "Jinja"})
assert result == "Jinja"
74 changes: 74 additions & 0 deletions tests/web/templates/test_jinja_response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from pathlib import Path

from httpx import AsyncClient

from selva.configuration.defaults import default_settings
from selva.configuration.settings import Settings
from selva.web.application import Selva

path = str(Path(__file__).parent.absolute())
settings = Settings(
default_settings
| {
"application": "tests.web.templates.application",
"templates": {"jinja": {"path": path}},
}
)


async def test_render():
app = Selva(settings)
await app._lifespan_startup()

client = AsyncClient(app=app)
response = await client.get("http://localhost:8000/render")

assert response.status_code == 200
assert response.text == "Jinja"
assert response.headers["Content-Length"] == str(len("Jinja"))
assert "text/html" in response.headers["Content-Type"]


async def test_stream():
app = Selva(settings)
await app._lifespan_startup()

client = AsyncClient(app=app)
response = await client.get("http://localhost:8000/stream")

assert response.status_code == 200
assert response.text == "Jinja"
assert "Content-Length" not in response.headers


async def test_define_content_type():
app = Selva(settings)
await app._lifespan_startup()

client = AsyncClient(app=app)
response = await client.get("http://localhost:8000/define_content_type")

assert response.status_code == 200
assert "text/defined" in response.headers["Content-Type"]


async def test_override_content_type():
app = Selva(settings)
await app._lifespan_startup()

client = AsyncClient(app=app)
response = await client.get("http://localhost:8000/override_content_type")

assert response.status_code == 200
assert "text/overriden" in response.headers["Content-Type"]


async def test_content_type_from_response():
app = Selva(settings)
await app._lifespan_startup()

client = AsyncClient(app=app)
response = await client.get("http://localhost:8000/content_type_from_response")

assert response.status_code == 200
assert "text/from_response" in response.headers["Content-Type"]

0 comments on commit 3d20183

Please sign in to comment.