Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use AppKey #534

Merged
merged 12 commits into from
Nov 18, 2023
53 changes: 31 additions & 22 deletions aiohttp_jinja2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,45 @@
Awaitable,
Callable,
Dict,
Final,
Mapping,
Optional,
Protocol,
Sequence,
Tuple,
TypeVar,
Union,
cast,
overload,
)

import jinja2
from aiohttp import web
from aiohttp.abc import AbstractView

from .helpers import GLOBAL_HELPERS
from .helpers import GLOBAL_HELPERS, static_root_key
from .typedefs import Filters

__version__ = "1.5.1"

__all__ = ("setup", "get_env", "render_template", "render_string", "template")


APP_CONTEXT_PROCESSORS_KEY = "aiohttp_jinja2_context_processors"
APP_KEY = "aiohttp_jinja2_environment"
REQUEST_CONTEXT_KEY = "aiohttp_jinja2_context"
__all__ = (
"get_env",
"render_string",
"render_template",
"setup",
"static_root_key",
"template",
)

_TemplateReturnType = Awaitable[Union[web.StreamResponse, Mapping[str, Any]]]
_SimpleTemplateHandler = Callable[[web.Request], _TemplateReturnType]
_ContextProcessor = Callable[[web.Request], Awaitable[Dict[str, Any]]]

APP_CONTEXT_PROCESSORS_KEY: Final = web.AppKey[Sequence[_ContextProcessor]](
"APP_CONTEXT_PROCESSORS_KEY"
)
APP_KEY: Final = web.AppKey[jinja2.Environment]("APP_KEY")
REQUEST_CONTEXT_KEY: Final = "aiohttp_jinja2_context"

_T = TypeVar("_T")
_AbstractView = TypeVar("_AbstractView", bound=AbstractView)

Expand Down Expand Up @@ -62,7 +70,7 @@ def __call__(
def setup(
app: web.Application,
*args: Any,
app_key: str = APP_KEY,
app_key: web.AppKey[jinja2.Environment] = APP_KEY,
context_processors: Sequence[_ContextProcessor] = (),
filters: Optional[Filters] = None,
default_helpers: bool = True,
Expand All @@ -84,23 +92,24 @@ def setup(
return env


def get_env(app: web.Application, *, app_key: str = APP_KEY) -> jinja2.Environment:
return cast(jinja2.Environment, app.get(app_key))
def get_env(
app: web.Application, *, app_key: web.AppKey[jinja2.Environment] = APP_KEY
) -> jinja2.Environment:
try:
return app[app_key]
except KeyError:
raise RuntimeError("aiohttp_jinja2.setup(...) must be called first.")


def _render_string(
template_name: str,
request: web.Request,
context: Mapping[str, Any],
app_key: str,
app_key: web.AppKey[jinja2.Environment],
) -> Tuple[jinja2.Template, Mapping[str, Any]]:
env = request.config_dict.get(app_key)
if env is None:
text = (
"Template engine is not initialized, "
"call aiohttp_jinja2.setup(..., app_key={}) first"
"".format(app_key)
)
text = "Template engine is not initialized, call aiohttp_jinja2.setup() first"
# in order to see meaningful exception message both: on console
# output and rendered page we add same message to *reason* and
# *text* arguments.
Expand All @@ -124,7 +133,7 @@ def render_string(
request: web.Request,
context: Mapping[str, Any],
*,
app_key: str = APP_KEY,
app_key: web.AppKey[jinja2.Environment] = APP_KEY,
) -> str:
template, context = _render_string(template_name, request, context, app_key)
return template.render(context)
Expand All @@ -135,7 +144,7 @@ async def render_string_async(
request: web.Request,
context: Mapping[str, Any],
*,
app_key: str = APP_KEY,
app_key: web.AppKey[jinja2.Environment] = APP_KEY,
) -> str:
template, context = _render_string(template_name, request, context, app_key)
return await template.render_async(context)
Expand All @@ -159,7 +168,7 @@ def render_template(
request: web.Request,
context: Optional[Mapping[str, Any]],
*,
app_key: str = APP_KEY,
app_key: web.AppKey[jinja2.Environment] = APP_KEY,
encoding: str = "utf-8",
status: int = 200,
) -> web.Response:
Expand All @@ -173,7 +182,7 @@ async def render_template_async(
request: web.Request,
context: Optional[Mapping[str, Any]],
*,
app_key: str = APP_KEY,
app_key: web.AppKey[jinja2.Environment] = APP_KEY,
encoding: str = "utf-8",
status: int = 200,
) -> web.Response:
Expand All @@ -187,7 +196,7 @@ async def render_template_async(
def template(
template_name: str,
*,
app_key: str = APP_KEY,
app_key: web.AppKey[jinja2.Environment] = APP_KEY,
encoding: str = "utf-8",
status: int = 200,
) -> _TemplateWrapper:
Expand Down
29 changes: 21 additions & 8 deletions aiohttp_jinja2/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
useful context functions, see
http://jinja.pocoo.org/docs/dev/api/#jinja2.contextfunction
"""
import warnings
from typing import Dict, Optional, TypedDict, Union

import jinja2
Expand All @@ -13,6 +14,9 @@ class _Context(TypedDict, total=False):
app: web.Application


static_root_key = web.AppKey("static_root_key", str)


@jinja2.pass_context
def url_for(
context: _Context,
Expand Down Expand Up @@ -55,21 +59,30 @@ def url_for(
def static_url(context: _Context, static_file_path: str) -> str:
"""Filter for generating urls for static files.

NOTE: you'll need
to set app['static_root_url'] to be used as the root for the urls returned.
NOTE: you'll need to set app[aiohttp_jinja2.static_root_key] to be used as the
root for the urls returned.

Usage: {{ static('styles.css') }} might become
"/static/styles.css" or "http://mycdn.example.com/styles.css"
"""
app = context["app"]
try:
static_url = app["static_root_url"]
static_url = app[static_root_key]
except KeyError:
raise RuntimeError(
"app does not define a static root url "
"'static_root_url', you need to set the url root "
"with app['static_root_url'] = '<static root>'."
) from None
try:
# TODO (aiohttp 3.10+): Remove this fallback
static_url = app["static_root_url"]
except KeyError:
raise RuntimeError(
"app does not define a static root url, you need to set the url root "
"with app[aiohttp_jinja2.static_root_key] = '<static root>'."
) from None
else:
warnings.warn(
"'static_root_url' is deprecated, use aiohttp_jinja2.static_root_key.",
category=DeprecationWarning,
stacklevel=2,
)
return "{}/{}".format(static_url.rstrip("/"), static_file_path.lstrip("/"))


Expand Down
9 changes: 4 additions & 5 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -201,14 +201,13 @@ This is useful as it would allow your static path to switch in
deployment or testing with just one line.

The ``static`` function has similar usage, except it requires you to
set ``static_root_url`` on the app
set ``app[aiohttp_jinja2.static_root_key]``.

.. code-block:: ruby
.. code-block:: python

app = web.Application()
aiohttp_jinja2.setup(app,
loader=jinja2.FileSystemLoader('/path/to/templates/folder'))
app['static_root_url'] = '/static'
aiohttp_jinja2.setup(app, loader=jinja2.FileSystemLoader("/path/to/templates/folder"))
app[aiohttp_jinja2.static_root_key] = "/static"

Then in the template::

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-e .
aiohttp==3.8.6
aiohttp==3.9.0b1
Dreamsorcerer marked this conversation as resolved.
Show resolved Hide resolved
alabaster>=0.6.2
coverage==7.2.7
jinja2==3.1.2
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,6 @@ def read(f):
license="Apache 2",
packages=["aiohttp_jinja2"],
python_requires=">=3.8",
install_requires=("aiohttp>=3.6.3", "jinja2>=3.0.0"),
install_requires=("aiohttp>=3.9.0b1", "jinja2>=3.0.0"),
Dreamsorcerer marked this conversation as resolved.
Show resolved Hide resolved
include_package_data=True,
)
9 changes: 5 additions & 4 deletions tests/test_context_processors.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Dict, Union
from typing import Dict, Tuple, Union

import jinja2
from aiohttp import web
Expand All @@ -22,10 +22,11 @@ async def func(request):
async def processor(request: web.Request) -> Dict[str, Union[str, int]]:
return {"foo": 1, "bar": "should be overwriten"}

app["aiohttp_jinja2_context_processors"] = (
f: Tuple[aiohttp_jinja2._ContextProcessor, ...] = (
aiohttp_jinja2.request_processor,
processor,
)
app[aiohttp_jinja2.APP_CONTEXT_PROCESSORS_KEY] = f

app.router.add_get("/", func)

Expand Down Expand Up @@ -56,7 +57,7 @@ async def func(request):
async def subprocessor(request):
return {"foo": 1, "bar": "should be overwriten"}

subapp["aiohttp_jinja2_context_processors"] = (
subapp[aiohttp_jinja2.APP_CONTEXT_PROCESSORS_KEY] = (
aiohttp_jinja2.request_processor,
subprocessor,
)
Expand All @@ -69,7 +70,7 @@ async def subprocessor(request):
async def processor(request):
return {"baz": 5}

app["aiohttp_jinja2_context_processors"] = (
app[aiohttp_jinja2.APP_CONTEXT_PROCESSORS_KEY] = (
aiohttp_jinja2.request_processor,
processor,
)
Expand Down
6 changes: 3 additions & 3 deletions tests/test_jinja_globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ async def index(request):
app, loader=jinja2.DictLoader({"tmpl.jinja2": "{{ static('whatever.js') }}"})
)

app["static_root_url"] = "/static"
app[aiohttp_jinja2.static_root_key] = "/static"
app.router.add_route("GET", "/", index)
client = await aiohttp_client(app)

Expand All @@ -153,7 +153,7 @@ async def index(request):

async def test_static_var_missing(aiohttp_client, caplog):
async def index(request):
with pytest.raises(RuntimeError, match="static_root_url"):
with pytest.raises(RuntimeError, match="static_root_key"):
aiohttp_jinja2.render_template("tmpl.jinja2", request, {})
return web.Response()

Expand All @@ -166,4 +166,4 @@ async def index(request):
client = await aiohttp_client(app)

resp = await client.get("/")
assert 200 == resp.status # static_root_url is not set
assert 200 == resp.status # static_root_key is not set
6 changes: 1 addition & 5 deletions tests/test_simple_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,7 @@ async def func(request: web.Request) -> web.Response:
app.router.add_route("GET", "/", func)

req = make_mocked_request("GET", "/", app=app)
msg = (
"Template engine is not initialized, "
"call aiohttp_jinja2.setup(..., app_key={}"
") first".format(aiohttp_jinja2.APP_KEY)
)
msg = "Template engine is not initialized, call aiohttp_jinja2.setup() first"

with pytest.raises(web.HTTPInternalServerError) as ctx:
await func(req)
Expand Down