Skip to content

Commit 1a3342b

Browse files
authored
Merge pull request #1412 from pallets/typing
add type annotations
2 parents f418f71 + be15556 commit 1a3342b

27 files changed

+2469
-1517
lines changed

CHANGES.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Unreleased
99
- Bump MarkupSafe dependency to >=1.1.
1010
- Bump Babel optional dependency to >=2.1.
1111
- Remove code that was marked deprecated.
12+
- Add type hinting. :pr:`1412`
1213
- Use :pep:`451` API to load templates with
1314
:class:`~loaders.PackageLoader`. :issue:`1168`
1415
- Fix a bug that caused imported macros to not have access to the

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ graft docs
66
prune docs/_build
77
graft examples
88
graft tests
9+
include src/click/py.typed
910
global-exclude *.pyc

docs/examples/inline_gettext_extension.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def filter_stream(self, stream):
3030
pos = 0
3131
lineno = token.lineno
3232

33-
while 1:
33+
while True:
3434
if not paren_stack:
3535
match = _outside_re.search(token.value, pos)
3636
else:

setup.cfg

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,21 @@ per-file-ignores =
8686
files = src/jinja2
8787
python_version = 3.6
8888
disallow_subclassing_any = True
89-
# disallow_untyped_calls = True
90-
# disallow_untyped_defs = True
89+
disallow_untyped_calls = True
90+
disallow_untyped_defs = True
9191
disallow_incomplete_defs = True
9292
no_implicit_optional = True
9393
local_partial_types = True
94-
# no_implicit_reexport = True
94+
no_implicit_reexport = True
9595
strict_equality = True
9696
warn_redundant_casts = True
9797
warn_unused_configs = True
9898
warn_unused_ignores = True
9999
warn_return_any = True
100100
warn_unreachable = True
101+
102+
[mypy-jinja2.defaults]
103+
no_implicit_reexport = False
104+
105+
[mypy-markupsafe]
106+
no_implicit_reexport = False

src/jinja2/async_utils.py

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,26 @@
55
from .utils import _PassArg
66
from .utils import pass_eval_context
77

8-
if t.TYPE_CHECKING:
9-
V = t.TypeVar("V")
8+
V = t.TypeVar("V")
109

1110

12-
def async_variant(normal_func):
13-
def decorator(async_func):
11+
def async_variant(normal_func): # type: ignore
12+
def decorator(async_func): # type: ignore
1413
pass_arg = _PassArg.from_obj(normal_func)
1514
need_eval_context = pass_arg is None
1615

1716
if pass_arg is _PassArg.environment:
1817

19-
def is_async(args):
20-
return args[0].is_async
18+
def is_async(args: t.Any) -> bool:
19+
return t.cast(bool, args[0].is_async)
2120

2221
else:
2322

24-
def is_async(args):
25-
return args[0].environment.is_async
23+
def is_async(args: t.Any) -> bool:
24+
return t.cast(bool, args[0].environment.is_async)
2625

2726
@wraps(normal_func)
28-
def wrapper(*args, **kwargs):
27+
def wrapper(*args, **kwargs): # type: ignore
2928
b = is_async(args)
3029

3130
if need_eval_context:
@@ -45,32 +44,25 @@ def wrapper(*args, **kwargs):
4544
return decorator
4645

4746

48-
async def auto_await(value):
47+
async def auto_await(value: t.Union[t.Awaitable["V"], "V"]) -> "V":
4948
if inspect.isawaitable(value):
50-
return await value
49+
return await t.cast("t.Awaitable[V]", value)
5150

52-
return value
51+
return t.cast("V", value)
5352

5453

55-
async def auto_aiter(iterable):
54+
async def auto_aiter(
55+
iterable: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
56+
) -> "t.AsyncIterator[V]":
5657
if hasattr(iterable, "__aiter__"):
57-
async for item in iterable:
58+
async for item in t.cast("t.AsyncIterable[V]", iterable):
5859
yield item
5960
else:
60-
for item in iterable:
61+
for item in t.cast("t.Iterable[V]", iterable):
6162
yield item
6263

6364

6465
async def auto_to_list(
6566
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]",
66-
) -> "t.List[V]":
67-
seq = []
68-
69-
if hasattr(value, "__aiter__"):
70-
async for item in t.cast(t.AsyncIterable, value):
71-
seq.append(item)
72-
else:
73-
for item in t.cast(t.Iterable, value):
74-
seq.append(item)
75-
76-
return seq
67+
) -> t.List["V"]:
68+
return [x async for x in auto_aiter(value)]

src/jinja2/bccache.py

Lines changed: 68 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,22 @@
1313
import stat
1414
import sys
1515
import tempfile
16+
import typing as t
1617
from hashlib import sha1
1718
from io import BytesIO
19+
from types import CodeType
20+
21+
if t.TYPE_CHECKING:
22+
import typing_extensions as te
23+
from .environment import Environment
24+
25+
class _MemcachedClient(te.Protocol):
26+
def get(self, key: str) -> bytes:
27+
...
28+
29+
def set(self, key: str, value: bytes, timeout: t.Optional[int] = None) -> None:
30+
...
1831

19-
from .utils import open_if_exists
2032

2133
bc_version = 5
2234
# Magic bytes to identify Jinja bytecode cache files. Contains the
@@ -38,17 +50,17 @@ class Bucket:
3850
cache subclasses don't have to care about cache invalidation.
3951
"""
4052

41-
def __init__(self, environment, key, checksum):
53+
def __init__(self, environment: "Environment", key: str, checksum: str) -> None:
4254
self.environment = environment
4355
self.key = key
4456
self.checksum = checksum
4557
self.reset()
4658

47-
def reset(self):
59+
def reset(self) -> None:
4860
"""Resets the bucket (unloads the bytecode)."""
49-
self.code = None
61+
self.code: t.Optional[CodeType] = None
5062

51-
def load_bytecode(self, f):
63+
def load_bytecode(self, f: t.BinaryIO) -> None:
5264
"""Loads bytecode from a file or file like object."""
5365
# make sure the magic header is correct
5466
magic = f.read(len(bc_magic))
@@ -67,20 +79,20 @@ def load_bytecode(self, f):
6779
self.reset()
6880
return
6981

70-
def write_bytecode(self, f):
82+
def write_bytecode(self, f: t.BinaryIO) -> None:
7183
"""Dump the bytecode into the file or file like object passed."""
7284
if self.code is None:
7385
raise TypeError("can't write empty bucket")
7486
f.write(bc_magic)
7587
pickle.dump(self.checksum, f, 2)
7688
marshal.dump(self.code, f)
7789

78-
def bytecode_from_string(self, string):
79-
"""Load bytecode from a string."""
90+
def bytecode_from_string(self, string: bytes) -> None:
91+
"""Load bytecode from bytes."""
8092
self.load_bytecode(BytesIO(string))
8193

82-
def bytecode_to_string(self):
83-
"""Return the bytecode as string."""
94+
def bytecode_to_string(self) -> bytes:
95+
"""Return the bytecode as bytes."""
8496
out = BytesIO()
8597
self.write_bytecode(out)
8698
return out.getvalue()
@@ -115,41 +127,48 @@ def dump_bytecode(self, bucket):
115127
Jinja.
116128
"""
117129

118-
def load_bytecode(self, bucket):
130+
def load_bytecode(self, bucket: Bucket) -> None:
119131
"""Subclasses have to override this method to load bytecode into a
120132
bucket. If they are not able to find code in the cache for the
121133
bucket, it must not do anything.
122134
"""
123135
raise NotImplementedError()
124136

125-
def dump_bytecode(self, bucket):
137+
def dump_bytecode(self, bucket: Bucket) -> None:
126138
"""Subclasses have to override this method to write the bytecode
127139
from a bucket back to the cache. If it unable to do so it must not
128140
fail silently but raise an exception.
129141
"""
130142
raise NotImplementedError()
131143

132-
def clear(self):
144+
def clear(self) -> None:
133145
"""Clears the cache. This method is not used by Jinja but should be
134146
implemented to allow applications to clear the bytecode cache used
135147
by a particular environment.
136148
"""
137149

138-
def get_cache_key(self, name, filename=None):
150+
def get_cache_key(
151+
self, name: str, filename: t.Optional[t.Union[str]] = None
152+
) -> str:
139153
"""Returns the unique hash key for this template name."""
140154
hash = sha1(name.encode("utf-8"))
155+
141156
if filename is not None:
142-
filename = "|" + filename
143-
if isinstance(filename, str):
144-
filename = filename.encode("utf-8")
145-
hash.update(filename)
157+
hash.update(f"|{filename}".encode("utf-8"))
158+
146159
return hash.hexdigest()
147160

148-
def get_source_checksum(self, source):
161+
def get_source_checksum(self, source: str) -> str:
149162
"""Returns a checksum for the source."""
150163
return sha1(source.encode("utf-8")).hexdigest()
151164

152-
def get_bucket(self, environment, name, filename, source):
165+
def get_bucket(
166+
self,
167+
environment: "Environment",
168+
name: str,
169+
filename: t.Optional[str],
170+
source: str,
171+
) -> Bucket:
153172
"""Return a cache bucket for the given template. All arguments are
154173
mandatory but filename may be `None`.
155174
"""
@@ -159,7 +178,7 @@ def get_bucket(self, environment, name, filename, source):
159178
self.load_bytecode(bucket)
160179
return bucket
161180

162-
def set_bucket(self, bucket):
181+
def set_bucket(self, bucket: Bucket) -> None:
163182
"""Put the bucket into the cache."""
164183
self.dump_bytecode(bucket)
165184

@@ -182,14 +201,16 @@ class FileSystemBytecodeCache(BytecodeCache):
182201
This bytecode cache supports clearing of the cache using the clear method.
183202
"""
184203

185-
def __init__(self, directory=None, pattern="__jinja2_%s.cache"):
204+
def __init__(
205+
self, directory: t.Optional[str] = None, pattern: str = "__jinja2_%s.cache"
206+
) -> None:
186207
if directory is None:
187208
directory = self._get_default_cache_dir()
188209
self.directory = directory
189210
self.pattern = pattern
190211

191-
def _get_default_cache_dir(self):
192-
def _unsafe_dir():
212+
def _get_default_cache_dir(self) -> str:
213+
def _unsafe_dir() -> t.NoReturn:
193214
raise RuntimeError(
194215
"Cannot determine safe temp directory. You "
195216
"need to explicitly provide one."
@@ -235,25 +256,21 @@ def _unsafe_dir():
235256

236257
return actual_dir
237258

238-
def _get_cache_filename(self, bucket):
259+
def _get_cache_filename(self, bucket: Bucket) -> str:
239260
return os.path.join(self.directory, self.pattern % (bucket.key,))
240261

241-
def load_bytecode(self, bucket):
242-
f = open_if_exists(self._get_cache_filename(bucket), "rb")
243-
if f is not None:
244-
try:
262+
def load_bytecode(self, bucket: Bucket) -> None:
263+
filename = self._get_cache_filename(bucket)
264+
265+
if os.path.exists(filename):
266+
with open(filename, "rb") as f:
245267
bucket.load_bytecode(f)
246-
finally:
247-
f.close()
248268

249-
def dump_bytecode(self, bucket):
250-
f = open(self._get_cache_filename(bucket), "wb")
251-
try:
269+
def dump_bytecode(self, bucket: Bucket) -> None:
270+
with open(self._get_cache_filename(bucket), "wb") as f:
252271
bucket.write_bytecode(f)
253-
finally:
254-
f.close()
255272

256-
def clear(self):
273+
def clear(self) -> None:
257274
# imported lazily here because google app-engine doesn't support
258275
# write access on the file system and the function does not exist
259276
# normally.
@@ -314,32 +331,34 @@ class MemcachedBytecodeCache(BytecodeCache):
314331

315332
def __init__(
316333
self,
317-
client,
318-
prefix="jinja2/bytecode/",
319-
timeout=None,
320-
ignore_memcache_errors=True,
334+
client: "_MemcachedClient",
335+
prefix: str = "jinja2/bytecode/",
336+
timeout: t.Optional[int] = None,
337+
ignore_memcache_errors: bool = True,
321338
):
322339
self.client = client
323340
self.prefix = prefix
324341
self.timeout = timeout
325342
self.ignore_memcache_errors = ignore_memcache_errors
326343

327-
def load_bytecode(self, bucket):
344+
def load_bytecode(self, bucket: Bucket) -> None:
328345
try:
329346
code = self.client.get(self.prefix + bucket.key)
330347
except Exception:
331348
if not self.ignore_memcache_errors:
332349
raise
333-
code = None
334-
if code is not None:
350+
else:
335351
bucket.bytecode_from_string(code)
336352

337-
def dump_bytecode(self, bucket):
338-
args = (self.prefix + bucket.key, bucket.bytecode_to_string())
339-
if self.timeout is not None:
340-
args += (self.timeout,)
353+
def dump_bytecode(self, bucket: Bucket) -> None:
354+
key = self.prefix + bucket.key
355+
value = bucket.bytecode_to_string()
356+
341357
try:
342-
self.client.set(*args)
358+
if self.timeout is not None:
359+
self.client.set(key, value, self.timeout)
360+
else:
361+
self.client.set(key, value)
343362
except Exception:
344363
if not self.ignore_memcache_errors:
345364
raise

0 commit comments

Comments
 (0)