diff options
author | David Lord <davidism@gmail.com> | 2022-04-28 07:08:26 -0700 |
---|---|---|
committer | David Lord <davidism@gmail.com> | 2022-04-28 07:08:26 -0700 |
commit | 52843b5cbf635b37a82ac0b6c901921a8ee076ff (patch) | |
tree | 4be5fc09ae1eccdabd8a9f1e2012d57ecaa53aef /src | |
parent | a0dd7753d082f8fe6c3225bb3e51de0bbd6fb7e8 (diff) | |
parent | 8efee35092404ba67ede8316566be4f430e7b61d (diff) | |
download | jinja2-52843b5cbf635b37a82ac0b6c901921a8ee076ff.tar.gz |
Merge branch '3.1.x'
Diffstat (limited to 'src')
-rw-r--r-- | src/jinja2/bccache.py | 54 | ||||
-rw-r--r-- | src/jinja2/filters.py | 6 |
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 |