summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDavid Lord <davidism@gmail.com>2022-04-28 07:08:26 -0700
committerDavid Lord <davidism@gmail.com>2022-04-28 07:08:26 -0700
commit52843b5cbf635b37a82ac0b6c901921a8ee076ff (patch)
tree4be5fc09ae1eccdabd8a9f1e2012d57ecaa53aef /src
parenta0dd7753d082f8fe6c3225bb3e51de0bbd6fb7e8 (diff)
parent8efee35092404ba67ede8316566be4f430e7b61d (diff)
downloadjinja2-52843b5cbf635b37a82ac0b6c901921a8ee076ff.tar.gz
Merge branch '3.1.x'
Diffstat (limited to 'src')
-rw-r--r--src/jinja2/bccache.py54
-rw-r--r--src/jinja2/filters.py6
2 files changed, 51 insertions, 9 deletions
diff --git a/src/jinja2/bccache.py b/src/jinja2/bccache.py
index 3bb61b7..d0ddf56 100644
--- a/src/jinja2/bccache.py
+++ b/src/jinja2/bccache.py
@@ -79,7 +79,7 @@ class Bucket:
self.reset()
return
- def write_bytecode(self, f: t.BinaryIO) -> None:
+ def write_bytecode(self, f: t.IO[bytes]) -> None:
"""Dump the bytecode into the file or file like object passed."""
if self.code is None:
raise TypeError("can't write empty bucket")
@@ -262,13 +262,55 @@ class FileSystemBytecodeCache(BytecodeCache):
def load_bytecode(self, bucket: Bucket) -> None:
filename = self._get_cache_filename(bucket)
- if os.path.exists(filename):
- with open(filename, "rb") as f:
- bucket.load_bytecode(f)
+ # Don't test for existence before opening the file, since the
+ # file could disappear after the test before the open.
+ try:
+ f = open(filename, "rb")
+ except (FileNotFoundError, IsADirectoryError, PermissionError):
+ # PermissionError can occur on Windows when an operation is
+ # in progress, such as calling clear().
+ return
+
+ with f:
+ bucket.load_bytecode(f)
def dump_bytecode(self, bucket: Bucket) -> None:
- with open(self._get_cache_filename(bucket), "wb") as f:
- bucket.write_bytecode(f)
+ # Write to a temporary file, then rename to the real name after
+ # writing. This avoids another process reading the file before
+ # it is fully written.
+ name = self._get_cache_filename(bucket)
+ f = tempfile.NamedTemporaryFile(
+ mode="wb",
+ dir=os.path.dirname(name),
+ prefix=os.path.basename(name),
+ suffix=".tmp",
+ delete=False,
+ )
+
+ def remove_silent() -> None:
+ try:
+ os.remove(f.name)
+ except OSError:
+ # Another process may have called clear(). On Windows,
+ # another program may be holding the file open.
+ pass
+
+ try:
+ with f:
+ bucket.write_bytecode(f)
+ except BaseException:
+ remove_silent()
+ raise
+
+ try:
+ os.replace(f.name, name)
+ except OSError:
+ # Another process may have called clear(). On Windows,
+ # another program may be holding the file open.
+ remove_silent()
+ except BaseException:
+ remove_silent()
+ raise
def clear(self) -> None:
# imported lazily here because google app-engine doesn't support
diff --git a/src/jinja2/filters.py b/src/jinja2/filters.py
index 7e09709..ed07c4c 100644
--- a/src/jinja2/filters.py
+++ b/src/jinja2/filters.py
@@ -1286,13 +1286,13 @@ def sync_do_sum(
Total: {{ items|sum(attribute='price') }}
.. versionchanged:: 2.6
- The `attribute` parameter was added to allow suming up over
- attributes. Also the `start` parameter was moved on to the right.
+ The ``attribute`` parameter was added to allow summing up over
+ attributes. Also the ``start`` parameter was moved on to the right.
"""
if attribute is not None:
iterable = map(make_attrgetter(environment, attribute), iterable)
- return sum(iterable, start)
+ return sum(iterable, start) # type: ignore[no-any-return, call-overload]
@async_variant(sync_do_sum) # type: ignore