Skip to content

Commit be84e05

Browse files
Expand typing to tests. (#387)
1 parent d62543b commit be84e05

File tree

6 files changed

+96
-31
lines changed

6 files changed

+96
-31
lines changed

.mypy.ini

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[mypy]
2+
files = aiohttp_jinja2, tests
3+
check_untyped_defs = True
4+
follow_imports_for_stubs = True
5+
disallow_any_decorated = True
6+
disallow_any_generics = True
7+
disallow_incomplete_defs = True
8+
disallow_subclassing_any = True
9+
disallow_untyped_calls = True
10+
disallow_untyped_decorators = True
11+
disallow_untyped_defs = True
12+
implicit_reexport = False
13+
no_implicit_optional = True
14+
show_error_codes = True
15+
strict_equality = True
16+
warn_incomplete_stub = True
17+
warn_redundant_casts = True
18+
warn_unreachable = True
19+
warn_unused_ignores = True
20+
disallow_any_unimported = True
21+
warn_return_any = True
22+
23+
[mypy-tests.*]
24+
disallow_untyped_defs = False

aiohttp_jinja2/__init__.py

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
import functools
3+
import sys
34
import warnings
45
from typing import (
56
Any,
@@ -9,7 +10,6 @@
910
Iterable,
1011
Mapping,
1112
Optional,
12-
Type,
1313
Union,
1414
cast,
1515
overload,
@@ -31,10 +31,6 @@
3131
APP_KEY = "aiohttp_jinja2_environment"
3232
REQUEST_CONTEXT_KEY = "aiohttp_jinja2_context"
3333

34-
_SimpleHandler = Callable[[web.Request], Awaitable[web.StreamResponse]]
35-
_MethodHandler = Callable[[Any, web.Request], Awaitable[web.StreamResponse]]
36-
_ViewHandler = Callable[[Type[AbstractView]], Awaitable[web.StreamResponse]]
37-
_HandlerType = Union[_SimpleHandler, _MethodHandler, _ViewHandler]
3834
_TemplateReturnType = Awaitable[Union[web.StreamResponse, Mapping[str, Any]]]
3935
_SimpleTemplateHandler = Callable[[web.Request], _TemplateReturnType]
4036
_MethodTemplateHandler = Callable[[Any, web.Request], _TemplateReturnType]
@@ -43,12 +39,36 @@
4339
_SimpleTemplateHandler, _MethodTemplateHandler, _ViewTemplateHandler
4440
]
4541

42+
_ContextProcessor = Callable[[web.Request], Awaitable[Dict[str, Any]]]
43+
44+
if sys.version_info >= (3, 8):
45+
from typing import Protocol
46+
47+
class _TemplateWrapped(Protocol):
48+
@overload
49+
async def __call__(self, request: web.Request) -> web.StreamResponse:
50+
...
51+
52+
@overload
53+
async def __call__(self, view: AbstractView) -> web.StreamResponse:
54+
...
55+
56+
@overload
57+
async def __call__(
58+
self, _self: Any, request: web.Request
59+
) -> web.StreamResponse:
60+
...
61+
62+
63+
else:
64+
_TemplateWrapped = Callable[..., web.StreamResponse]
65+
4666

4767
def setup(
4868
app: web.Application,
4969
*args: Any,
5070
app_key: str = APP_KEY,
51-
context_processors: Iterable[Callable[[web.Request], Dict[str, Any]]] = (),
71+
context_processors: Iterable[_ContextProcessor] = (),
5272
filters: Optional[Filters] = None,
5373
default_helpers: bool = True,
5474
**kwargs: Any,
@@ -109,7 +129,7 @@ def render_string(
109129
def render_template(
110130
template_name: str,
111131
request: web.Request,
112-
context: Mapping[str, Any],
132+
context: Optional[Mapping[str, Any]],
113133
*,
114134
app_key: str = APP_KEY,
115135
encoding: str = "utf-8",
@@ -131,8 +151,8 @@ def template(
131151
app_key: str = APP_KEY,
132152
encoding: str = "utf-8",
133153
status: int = 200,
134-
) -> Callable[[_TemplateHandler], _HandlerType]:
135-
def wrapper(func: _TemplateHandler) -> _HandlerType:
154+
) -> Callable[[_TemplateHandler], _TemplateWrapped]:
155+
def wrapper(func: _TemplateHandler) -> _TemplateWrapped:
136156
@overload
137157
async def wrapped(request: web.Request) -> web.StreamResponse:
138158
...
@@ -151,7 +171,7 @@ async def wrapped(*args: Any) -> web.StreamResponse:
151171
coro = func
152172
else:
153173
warnings.warn(
154-
"Bare functions are deprecated, " "use async ones",
174+
"Bare functions are deprecated, use async ones",
155175
DeprecationWarning,
156176
)
157177
coro = asyncio.coroutine(func)

aiohttp_jinja2/helpers.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,40 @@
22
useful context functions, see
33
http://jinja.pocoo.org/docs/dev/api/#jinja2.contextfunction
44
"""
5-
from typing import Any, Dict, cast
5+
import sys
6+
from typing import Any, Dict, Optional, Union
67

78
import jinja2
89
from aiohttp import web
910
from yarl import URL
1011

12+
if sys.version_info >= (3, 8):
13+
from typing import TypedDict
14+
15+
class _Context(TypedDict, total=False):
16+
app: web.Application
17+
18+
19+
else:
20+
_Context = Dict[str, Any]
21+
1122

1223
@jinja2.contextfunction
13-
def url_for(context: Dict[str, Any], __route_name: str, **parts: Any) -> URL:
24+
def url_for(
25+
context: _Context,
26+
__route_name: str,
27+
query_: Optional[Dict[str, str]] = None,
28+
**parts: Union[str, int]
29+
) -> URL:
1430
"""Filter for generating urls.
1531
1632
Usage: {{ url('the-view-name') }} might become "/path/to/view" or
1733
{{ url('item-details', id=123, query={'active': 'true'}) }}
1834
might become "/items/1?active=true".
1935
"""
20-
app = cast(web.Application, context["app"])
21-
22-
query = None
23-
if "query_" in parts:
24-
query = parts.pop("query_")
36+
app = context["app"]
2537

38+
parts_clean: Dict[str, str] = {}
2639
for key in parts:
2740
val = parts[key]
2841
if isinstance(val, str):
@@ -37,16 +50,16 @@ def url_for(context: Dict[str, Any], __route_name: str, **parts: Any) -> URL:
3750
"argument value should be str or int, "
3851
"got {} -> [{}] {!r}".format(key, type(val), val)
3952
)
40-
parts[key] = val
53+
parts_clean[key] = val
4154

42-
url = app.router[__route_name].url_for(**parts)
43-
if query:
44-
url = url.with_query(query)
55+
url = app.router[__route_name].url_for(**parts_clean)
56+
if query_:
57+
url = url.with_query(query_)
4558
return url
4659

4760

4861
@jinja2.contextfunction
49-
def static_url(context: Dict[str, Any], static_file_path: str) -> str:
62+
def static_url(context: _Context, static_file_path: str) -> str:
5063
"""Filter for generating urls for static files.
5164
5265
NOTE: you'll need

tests/__init__.py

Whitespace-only changes.

tests/test_context_processors.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Dict, Union
2+
13
import jinja2
24
from aiohttp import web
35

@@ -17,7 +19,7 @@ async def func(request):
1719
),
1820
)
1921

20-
async def processor(request):
22+
async def processor(request: web.Request) -> Dict[str, Union[str, int]]:
2123
return {"foo": 1, "bar": "should be overwriten"}
2224

2325
app["aiohttp_jinja2_context_processors"] = (

tests/test_simple_renderer.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1+
from typing import Awaitable, Callable, Dict, TypeVar
2+
13
import jinja2
24
import pytest
35
from aiohttp import web
46
from aiohttp.test_utils import make_mocked_request
57

68
import aiohttp_jinja2
79

10+
_T = TypeVar("_T")
11+
812

913
async def test_func(aiohttp_client):
1014
@aiohttp_jinja2.template("tmpl.jinja2")
11-
async def func(request):
15+
async def func(request: web.Request) -> Dict[str, str]:
1216
return {"head": "HEAD", "text": "text"}
1317

1418
template = "<html><body><h1>{{head}}</h1>{{text}}</body></html>"
@@ -28,7 +32,7 @@ async def func(request):
2832
async def test_render_class_based_view(aiohttp_client):
2933
class MyView(web.View):
3034
@aiohttp_jinja2.template("tmpl.jinja2")
31-
async def get(self):
35+
async def get(self) -> Dict[str, str]:
3236
return {"head": "HEAD", "text": "text"}
3337

3438
template = "<html><body><h1>{{head}}</h1>{{text}}</body></html>"
@@ -92,7 +96,7 @@ async def func(request):
9296

9397

9498
async def test_render_not_initialized():
95-
async def func(request):
99+
async def func(request: web.Request) -> web.Response:
96100
return aiohttp_jinja2.render_template("template", request, {})
97101

98102
app = web.Application()
@@ -178,7 +182,7 @@ async def func(request):
178182

179183

180184
async def test_template_not_found():
181-
async def func(request):
185+
async def func(request: web.Request) -> web.Response:
182186
return aiohttp_jinja2.render_template("template", request, {})
183187

184188
app = web.Application()
@@ -277,18 +281,20 @@ async def func(request):
277281

278282

279283
async def test_render_bare_funcs_deprecated(aiohttp_client):
280-
def wrapper(func):
281-
async def wrapped(request):
284+
def wrapper(
285+
func: Callable[[web.Request], Awaitable[_T]]
286+
) -> Callable[[web.Request], Awaitable[_T]]:
287+
async def wrapped(request: web.Request) -> _T:
282288
with pytest.warns(
283289
DeprecationWarning, match="Bare functions are deprecated"
284290
):
285291
return await func(request)
286292

287293
return wrapped
288294

289-
@wrapper
295+
@wrapper # type: ignore[arg-type] # Deprecated functionality
290296
@aiohttp_jinja2.template("tmpl.jinja2")
291-
def func(request):
297+
def func(request: web.Request) -> Dict[str, str]:
292298
return {"text": "OK"}
293299

294300
app = web.Application()

0 commit comments

Comments
 (0)