1e31aef6aSopenharmony_ci"""The optional bytecode cache system. This is useful if you have very
2e31aef6aSopenharmony_cicomplex template situations and the compilation of all those templates
3e31aef6aSopenharmony_cislows down your application too much.
4e31aef6aSopenharmony_ci
5e31aef6aSopenharmony_ciSituations where this is useful are often forking web applications that
6e31aef6aSopenharmony_ciare initialized on the first request.
7e31aef6aSopenharmony_ci"""
8e31aef6aSopenharmony_ciimport errno
9e31aef6aSopenharmony_ciimport fnmatch
10e31aef6aSopenharmony_ciimport marshal
11e31aef6aSopenharmony_ciimport os
12e31aef6aSopenharmony_ciimport pickle
13e31aef6aSopenharmony_ciimport stat
14e31aef6aSopenharmony_ciimport sys
15e31aef6aSopenharmony_ciimport tempfile
16e31aef6aSopenharmony_ciimport typing as t
17e31aef6aSopenharmony_cifrom hashlib import sha1
18e31aef6aSopenharmony_cifrom io import BytesIO
19e31aef6aSopenharmony_cifrom types import CodeType
20e31aef6aSopenharmony_ci
21e31aef6aSopenharmony_ciif t.TYPE_CHECKING:
22e31aef6aSopenharmony_ci    import typing_extensions as te
23e31aef6aSopenharmony_ci    from .environment import Environment
24e31aef6aSopenharmony_ci
25e31aef6aSopenharmony_ci    class _MemcachedClient(te.Protocol):
26e31aef6aSopenharmony_ci        def get(self, key: str) -> bytes:
27e31aef6aSopenharmony_ci            ...
28e31aef6aSopenharmony_ci
29e31aef6aSopenharmony_ci        def set(self, key: str, value: bytes, timeout: t.Optional[int] = None) -> None:
30e31aef6aSopenharmony_ci            ...
31e31aef6aSopenharmony_ci
32e31aef6aSopenharmony_ci
33e31aef6aSopenharmony_cibc_version = 5
34e31aef6aSopenharmony_ci# Magic bytes to identify Jinja bytecode cache files. Contains the
35e31aef6aSopenharmony_ci# Python major and minor version to avoid loading incompatible bytecode
36e31aef6aSopenharmony_ci# if a project upgrades its Python version.
37e31aef6aSopenharmony_cibc_magic = (
38e31aef6aSopenharmony_ci    b"j2"
39e31aef6aSopenharmony_ci    + pickle.dumps(bc_version, 2)
40e31aef6aSopenharmony_ci    + pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1], 2)
41e31aef6aSopenharmony_ci)
42e31aef6aSopenharmony_ci
43e31aef6aSopenharmony_ci
44e31aef6aSopenharmony_ciclass Bucket:
45e31aef6aSopenharmony_ci    """Buckets are used to store the bytecode for one template.  It's created
46e31aef6aSopenharmony_ci    and initialized by the bytecode cache and passed to the loading functions.
47e31aef6aSopenharmony_ci
48e31aef6aSopenharmony_ci    The buckets get an internal checksum from the cache assigned and use this
49e31aef6aSopenharmony_ci    to automatically reject outdated cache material.  Individual bytecode
50e31aef6aSopenharmony_ci    cache subclasses don't have to care about cache invalidation.
51e31aef6aSopenharmony_ci    """
52e31aef6aSopenharmony_ci
53e31aef6aSopenharmony_ci    def __init__(self, environment: "Environment", key: str, checksum: str) -> None:
54e31aef6aSopenharmony_ci        self.environment = environment
55e31aef6aSopenharmony_ci        self.key = key
56e31aef6aSopenharmony_ci        self.checksum = checksum
57e31aef6aSopenharmony_ci        self.reset()
58e31aef6aSopenharmony_ci
59e31aef6aSopenharmony_ci    def reset(self) -> None:
60e31aef6aSopenharmony_ci        """Resets the bucket (unloads the bytecode)."""
61e31aef6aSopenharmony_ci        self.code: t.Optional[CodeType] = None
62e31aef6aSopenharmony_ci
63e31aef6aSopenharmony_ci    def load_bytecode(self, f: t.BinaryIO) -> None:
64e31aef6aSopenharmony_ci        """Loads bytecode from a file or file like object."""
65e31aef6aSopenharmony_ci        # make sure the magic header is correct
66e31aef6aSopenharmony_ci        magic = f.read(len(bc_magic))
67e31aef6aSopenharmony_ci        if magic != bc_magic:
68e31aef6aSopenharmony_ci            self.reset()
69e31aef6aSopenharmony_ci            return
70e31aef6aSopenharmony_ci        # the source code of the file changed, we need to reload
71e31aef6aSopenharmony_ci        checksum = pickle.load(f)
72e31aef6aSopenharmony_ci        if self.checksum != checksum:
73e31aef6aSopenharmony_ci            self.reset()
74e31aef6aSopenharmony_ci            return
75e31aef6aSopenharmony_ci        # if marshal_load fails then we need to reload
76e31aef6aSopenharmony_ci        try:
77e31aef6aSopenharmony_ci            self.code = marshal.load(f)
78e31aef6aSopenharmony_ci        except (EOFError, ValueError, TypeError):
79e31aef6aSopenharmony_ci            self.reset()
80e31aef6aSopenharmony_ci            return
81e31aef6aSopenharmony_ci
82e31aef6aSopenharmony_ci    def write_bytecode(self, f: t.IO[bytes]) -> None:
83e31aef6aSopenharmony_ci        """Dump the bytecode into the file or file like object passed."""
84e31aef6aSopenharmony_ci        if self.code is None:
85e31aef6aSopenharmony_ci            raise TypeError("can't write empty bucket")
86e31aef6aSopenharmony_ci        f.write(bc_magic)
87e31aef6aSopenharmony_ci        pickle.dump(self.checksum, f, 2)
88e31aef6aSopenharmony_ci        marshal.dump(self.code, f)
89e31aef6aSopenharmony_ci
90e31aef6aSopenharmony_ci    def bytecode_from_string(self, string: bytes) -> None:
91e31aef6aSopenharmony_ci        """Load bytecode from bytes."""
92e31aef6aSopenharmony_ci        self.load_bytecode(BytesIO(string))
93e31aef6aSopenharmony_ci
94e31aef6aSopenharmony_ci    def bytecode_to_string(self) -> bytes:
95e31aef6aSopenharmony_ci        """Return the bytecode as bytes."""
96e31aef6aSopenharmony_ci        out = BytesIO()
97e31aef6aSopenharmony_ci        self.write_bytecode(out)
98e31aef6aSopenharmony_ci        return out.getvalue()
99e31aef6aSopenharmony_ci
100e31aef6aSopenharmony_ci
101e31aef6aSopenharmony_ciclass BytecodeCache:
102e31aef6aSopenharmony_ci    """To implement your own bytecode cache you have to subclass this class
103e31aef6aSopenharmony_ci    and override :meth:`load_bytecode` and :meth:`dump_bytecode`.  Both of
104e31aef6aSopenharmony_ci    these methods are passed a :class:`~jinja2.bccache.Bucket`.
105e31aef6aSopenharmony_ci
106e31aef6aSopenharmony_ci    A very basic bytecode cache that saves the bytecode on the file system::
107e31aef6aSopenharmony_ci
108e31aef6aSopenharmony_ci        from os import path
109e31aef6aSopenharmony_ci
110e31aef6aSopenharmony_ci        class MyCache(BytecodeCache):
111e31aef6aSopenharmony_ci
112e31aef6aSopenharmony_ci            def __init__(self, directory):
113e31aef6aSopenharmony_ci                self.directory = directory
114e31aef6aSopenharmony_ci
115e31aef6aSopenharmony_ci            def load_bytecode(self, bucket):
116e31aef6aSopenharmony_ci                filename = path.join(self.directory, bucket.key)
117e31aef6aSopenharmony_ci                if path.exists(filename):
118e31aef6aSopenharmony_ci                    with open(filename, 'rb') as f:
119e31aef6aSopenharmony_ci                        bucket.load_bytecode(f)
120e31aef6aSopenharmony_ci
121e31aef6aSopenharmony_ci            def dump_bytecode(self, bucket):
122e31aef6aSopenharmony_ci                filename = path.join(self.directory, bucket.key)
123e31aef6aSopenharmony_ci                with open(filename, 'wb') as f:
124e31aef6aSopenharmony_ci                    bucket.write_bytecode(f)
125e31aef6aSopenharmony_ci
126e31aef6aSopenharmony_ci    A more advanced version of a filesystem based bytecode cache is part of
127e31aef6aSopenharmony_ci    Jinja.
128e31aef6aSopenharmony_ci    """
129e31aef6aSopenharmony_ci
130e31aef6aSopenharmony_ci    def load_bytecode(self, bucket: Bucket) -> None:
131e31aef6aSopenharmony_ci        """Subclasses have to override this method to load bytecode into a
132e31aef6aSopenharmony_ci        bucket.  If they are not able to find code in the cache for the
133e31aef6aSopenharmony_ci        bucket, it must not do anything.
134e31aef6aSopenharmony_ci        """
135e31aef6aSopenharmony_ci        raise NotImplementedError()
136e31aef6aSopenharmony_ci
137e31aef6aSopenharmony_ci    def dump_bytecode(self, bucket: Bucket) -> None:
138e31aef6aSopenharmony_ci        """Subclasses have to override this method to write the bytecode
139e31aef6aSopenharmony_ci        from a bucket back to the cache.  If it unable to do so it must not
140e31aef6aSopenharmony_ci        fail silently but raise an exception.
141e31aef6aSopenharmony_ci        """
142e31aef6aSopenharmony_ci        raise NotImplementedError()
143e31aef6aSopenharmony_ci
144e31aef6aSopenharmony_ci    def clear(self) -> None:
145e31aef6aSopenharmony_ci        """Clears the cache.  This method is not used by Jinja but should be
146e31aef6aSopenharmony_ci        implemented to allow applications to clear the bytecode cache used
147e31aef6aSopenharmony_ci        by a particular environment.
148e31aef6aSopenharmony_ci        """
149e31aef6aSopenharmony_ci
150e31aef6aSopenharmony_ci    def get_cache_key(
151e31aef6aSopenharmony_ci        self, name: str, filename: t.Optional[t.Union[str]] = None
152e31aef6aSopenharmony_ci    ) -> str:
153e31aef6aSopenharmony_ci        """Returns the unique hash key for this template name."""
154e31aef6aSopenharmony_ci        hash = sha1(name.encode("utf-8"))
155e31aef6aSopenharmony_ci
156e31aef6aSopenharmony_ci        if filename is not None:
157e31aef6aSopenharmony_ci            hash.update(f"|{filename}".encode())
158e31aef6aSopenharmony_ci
159e31aef6aSopenharmony_ci        return hash.hexdigest()
160e31aef6aSopenharmony_ci
161e31aef6aSopenharmony_ci    def get_source_checksum(self, source: str) -> str:
162e31aef6aSopenharmony_ci        """Returns a checksum for the source."""
163e31aef6aSopenharmony_ci        return sha1(source.encode("utf-8")).hexdigest()
164e31aef6aSopenharmony_ci
165e31aef6aSopenharmony_ci    def get_bucket(
166e31aef6aSopenharmony_ci        self,
167e31aef6aSopenharmony_ci        environment: "Environment",
168e31aef6aSopenharmony_ci        name: str,
169e31aef6aSopenharmony_ci        filename: t.Optional[str],
170e31aef6aSopenharmony_ci        source: str,
171e31aef6aSopenharmony_ci    ) -> Bucket:
172e31aef6aSopenharmony_ci        """Return a cache bucket for the given template.  All arguments are
173e31aef6aSopenharmony_ci        mandatory but filename may be `None`.
174e31aef6aSopenharmony_ci        """
175e31aef6aSopenharmony_ci        key = self.get_cache_key(name, filename)
176e31aef6aSopenharmony_ci        checksum = self.get_source_checksum(source)
177e31aef6aSopenharmony_ci        bucket = Bucket(environment, key, checksum)
178e31aef6aSopenharmony_ci        self.load_bytecode(bucket)
179e31aef6aSopenharmony_ci        return bucket
180e31aef6aSopenharmony_ci
181e31aef6aSopenharmony_ci    def set_bucket(self, bucket: Bucket) -> None:
182e31aef6aSopenharmony_ci        """Put the bucket into the cache."""
183e31aef6aSopenharmony_ci        self.dump_bytecode(bucket)
184e31aef6aSopenharmony_ci
185e31aef6aSopenharmony_ci
186e31aef6aSopenharmony_ciclass FileSystemBytecodeCache(BytecodeCache):
187e31aef6aSopenharmony_ci    """A bytecode cache that stores bytecode on the filesystem.  It accepts
188e31aef6aSopenharmony_ci    two arguments: The directory where the cache items are stored and a
189e31aef6aSopenharmony_ci    pattern string that is used to build the filename.
190e31aef6aSopenharmony_ci
191e31aef6aSopenharmony_ci    If no directory is specified a default cache directory is selected.  On
192e31aef6aSopenharmony_ci    Windows the user's temp directory is used, on UNIX systems a directory
193e31aef6aSopenharmony_ci    is created for the user in the system temp directory.
194e31aef6aSopenharmony_ci
195e31aef6aSopenharmony_ci    The pattern can be used to have multiple separate caches operate on the
196e31aef6aSopenharmony_ci    same directory.  The default pattern is ``'__jinja2_%s.cache'``.  ``%s``
197e31aef6aSopenharmony_ci    is replaced with the cache key.
198e31aef6aSopenharmony_ci
199e31aef6aSopenharmony_ci    >>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache')
200e31aef6aSopenharmony_ci
201e31aef6aSopenharmony_ci    This bytecode cache supports clearing of the cache using the clear method.
202e31aef6aSopenharmony_ci    """
203e31aef6aSopenharmony_ci
204e31aef6aSopenharmony_ci    def __init__(
205e31aef6aSopenharmony_ci        self, directory: t.Optional[str] = None, pattern: str = "__jinja2_%s.cache"
206e31aef6aSopenharmony_ci    ) -> None:
207e31aef6aSopenharmony_ci        if directory is None:
208e31aef6aSopenharmony_ci            directory = self._get_default_cache_dir()
209e31aef6aSopenharmony_ci        self.directory = directory
210e31aef6aSopenharmony_ci        self.pattern = pattern
211e31aef6aSopenharmony_ci
212e31aef6aSopenharmony_ci    def _get_default_cache_dir(self) -> str:
213e31aef6aSopenharmony_ci        def _unsafe_dir() -> "te.NoReturn":
214e31aef6aSopenharmony_ci            raise RuntimeError(
215e31aef6aSopenharmony_ci                "Cannot determine safe temp directory.  You "
216e31aef6aSopenharmony_ci                "need to explicitly provide one."
217e31aef6aSopenharmony_ci            )
218e31aef6aSopenharmony_ci
219e31aef6aSopenharmony_ci        tmpdir = tempfile.gettempdir()
220e31aef6aSopenharmony_ci
221e31aef6aSopenharmony_ci        # On windows the temporary directory is used specific unless
222e31aef6aSopenharmony_ci        # explicitly forced otherwise.  We can just use that.
223e31aef6aSopenharmony_ci        if os.name == "nt":
224e31aef6aSopenharmony_ci            return tmpdir
225e31aef6aSopenharmony_ci        if not hasattr(os, "getuid"):
226e31aef6aSopenharmony_ci            _unsafe_dir()
227e31aef6aSopenharmony_ci
228e31aef6aSopenharmony_ci        dirname = f"_jinja2-cache-{os.getuid()}"
229e31aef6aSopenharmony_ci        actual_dir = os.path.join(tmpdir, dirname)
230e31aef6aSopenharmony_ci
231e31aef6aSopenharmony_ci        try:
232e31aef6aSopenharmony_ci            os.mkdir(actual_dir, stat.S_IRWXU)
233e31aef6aSopenharmony_ci        except OSError as e:
234e31aef6aSopenharmony_ci            if e.errno != errno.EEXIST:
235e31aef6aSopenharmony_ci                raise
236e31aef6aSopenharmony_ci        try:
237e31aef6aSopenharmony_ci            os.chmod(actual_dir, stat.S_IRWXU)
238e31aef6aSopenharmony_ci            actual_dir_stat = os.lstat(actual_dir)
239e31aef6aSopenharmony_ci            if (
240e31aef6aSopenharmony_ci                actual_dir_stat.st_uid != os.getuid()
241e31aef6aSopenharmony_ci                or not stat.S_ISDIR(actual_dir_stat.st_mode)
242e31aef6aSopenharmony_ci                or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU
243e31aef6aSopenharmony_ci            ):
244e31aef6aSopenharmony_ci                _unsafe_dir()
245e31aef6aSopenharmony_ci        except OSError as e:
246e31aef6aSopenharmony_ci            if e.errno != errno.EEXIST:
247e31aef6aSopenharmony_ci                raise
248e31aef6aSopenharmony_ci
249e31aef6aSopenharmony_ci        actual_dir_stat = os.lstat(actual_dir)
250e31aef6aSopenharmony_ci        if (
251e31aef6aSopenharmony_ci            actual_dir_stat.st_uid != os.getuid()
252e31aef6aSopenharmony_ci            or not stat.S_ISDIR(actual_dir_stat.st_mode)
253e31aef6aSopenharmony_ci            or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU
254e31aef6aSopenharmony_ci        ):
255e31aef6aSopenharmony_ci            _unsafe_dir()
256e31aef6aSopenharmony_ci
257e31aef6aSopenharmony_ci        return actual_dir
258e31aef6aSopenharmony_ci
259e31aef6aSopenharmony_ci    def _get_cache_filename(self, bucket: Bucket) -> str:
260e31aef6aSopenharmony_ci        return os.path.join(self.directory, self.pattern % (bucket.key,))
261e31aef6aSopenharmony_ci
262e31aef6aSopenharmony_ci    def load_bytecode(self, bucket: Bucket) -> None:
263e31aef6aSopenharmony_ci        filename = self._get_cache_filename(bucket)
264e31aef6aSopenharmony_ci
265e31aef6aSopenharmony_ci        # Don't test for existence before opening the file, since the
266e31aef6aSopenharmony_ci        # file could disappear after the test before the open.
267e31aef6aSopenharmony_ci        try:
268e31aef6aSopenharmony_ci            f = open(filename, "rb")
269e31aef6aSopenharmony_ci        except (FileNotFoundError, IsADirectoryError, PermissionError):
270e31aef6aSopenharmony_ci            # PermissionError can occur on Windows when an operation is
271e31aef6aSopenharmony_ci            # in progress, such as calling clear().
272e31aef6aSopenharmony_ci            return
273e31aef6aSopenharmony_ci
274e31aef6aSopenharmony_ci        with f:
275e31aef6aSopenharmony_ci            bucket.load_bytecode(f)
276e31aef6aSopenharmony_ci
277e31aef6aSopenharmony_ci    def dump_bytecode(self, bucket: Bucket) -> None:
278e31aef6aSopenharmony_ci        # Write to a temporary file, then rename to the real name after
279e31aef6aSopenharmony_ci        # writing. This avoids another process reading the file before
280e31aef6aSopenharmony_ci        # it is fully written.
281e31aef6aSopenharmony_ci        name = self._get_cache_filename(bucket)
282e31aef6aSopenharmony_ci        f = tempfile.NamedTemporaryFile(
283e31aef6aSopenharmony_ci            mode="wb",
284e31aef6aSopenharmony_ci            dir=os.path.dirname(name),
285e31aef6aSopenharmony_ci            prefix=os.path.basename(name),
286e31aef6aSopenharmony_ci            suffix=".tmp",
287e31aef6aSopenharmony_ci            delete=False,
288e31aef6aSopenharmony_ci        )
289e31aef6aSopenharmony_ci
290e31aef6aSopenharmony_ci        def remove_silent() -> None:
291e31aef6aSopenharmony_ci            try:
292e31aef6aSopenharmony_ci                os.remove(f.name)
293e31aef6aSopenharmony_ci            except OSError:
294e31aef6aSopenharmony_ci                # Another process may have called clear(). On Windows,
295e31aef6aSopenharmony_ci                # another program may be holding the file open.
296e31aef6aSopenharmony_ci                pass
297e31aef6aSopenharmony_ci
298e31aef6aSopenharmony_ci        try:
299e31aef6aSopenharmony_ci            with f:
300e31aef6aSopenharmony_ci                bucket.write_bytecode(f)
301e31aef6aSopenharmony_ci        except BaseException:
302e31aef6aSopenharmony_ci            remove_silent()
303e31aef6aSopenharmony_ci            raise
304e31aef6aSopenharmony_ci
305e31aef6aSopenharmony_ci        try:
306e31aef6aSopenharmony_ci            os.replace(f.name, name)
307e31aef6aSopenharmony_ci        except OSError:
308e31aef6aSopenharmony_ci            # Another process may have called clear(). On Windows,
309e31aef6aSopenharmony_ci            # another program may be holding the file open.
310e31aef6aSopenharmony_ci            remove_silent()
311e31aef6aSopenharmony_ci        except BaseException:
312e31aef6aSopenharmony_ci            remove_silent()
313e31aef6aSopenharmony_ci            raise
314e31aef6aSopenharmony_ci
315e31aef6aSopenharmony_ci    def clear(self) -> None:
316e31aef6aSopenharmony_ci        # imported lazily here because google app-engine doesn't support
317e31aef6aSopenharmony_ci        # write access on the file system and the function does not exist
318e31aef6aSopenharmony_ci        # normally.
319e31aef6aSopenharmony_ci        from os import remove
320e31aef6aSopenharmony_ci
321e31aef6aSopenharmony_ci        files = fnmatch.filter(os.listdir(self.directory), self.pattern % ("*",))
322e31aef6aSopenharmony_ci        for filename in files:
323e31aef6aSopenharmony_ci            try:
324e31aef6aSopenharmony_ci                remove(os.path.join(self.directory, filename))
325e31aef6aSopenharmony_ci            except OSError:
326e31aef6aSopenharmony_ci                pass
327e31aef6aSopenharmony_ci
328e31aef6aSopenharmony_ci
329e31aef6aSopenharmony_ciclass MemcachedBytecodeCache(BytecodeCache):
330e31aef6aSopenharmony_ci    """This class implements a bytecode cache that uses a memcache cache for
331e31aef6aSopenharmony_ci    storing the information.  It does not enforce a specific memcache library
332e31aef6aSopenharmony_ci    (tummy's memcache or cmemcache) but will accept any class that provides
333e31aef6aSopenharmony_ci    the minimal interface required.
334e31aef6aSopenharmony_ci
335e31aef6aSopenharmony_ci    Libraries compatible with this class:
336e31aef6aSopenharmony_ci
337e31aef6aSopenharmony_ci    -   `cachelib <https://github.com/pallets/cachelib>`_
338e31aef6aSopenharmony_ci    -   `python-memcached <https://pypi.org/project/python-memcached/>`_
339e31aef6aSopenharmony_ci
340e31aef6aSopenharmony_ci    (Unfortunately the django cache interface is not compatible because it
341e31aef6aSopenharmony_ci    does not support storing binary data, only text. You can however pass
342e31aef6aSopenharmony_ci    the underlying cache client to the bytecode cache which is available
343e31aef6aSopenharmony_ci    as `django.core.cache.cache._client`.)
344e31aef6aSopenharmony_ci
345e31aef6aSopenharmony_ci    The minimal interface for the client passed to the constructor is this:
346e31aef6aSopenharmony_ci
347e31aef6aSopenharmony_ci    .. class:: MinimalClientInterface
348e31aef6aSopenharmony_ci
349e31aef6aSopenharmony_ci        .. method:: set(key, value[, timeout])
350e31aef6aSopenharmony_ci
351e31aef6aSopenharmony_ci            Stores the bytecode in the cache.  `value` is a string and
352e31aef6aSopenharmony_ci            `timeout` the timeout of the key.  If timeout is not provided
353e31aef6aSopenharmony_ci            a default timeout or no timeout should be assumed, if it's
354e31aef6aSopenharmony_ci            provided it's an integer with the number of seconds the cache
355e31aef6aSopenharmony_ci            item should exist.
356e31aef6aSopenharmony_ci
357e31aef6aSopenharmony_ci        .. method:: get(key)
358e31aef6aSopenharmony_ci
359e31aef6aSopenharmony_ci            Returns the value for the cache key.  If the item does not
360e31aef6aSopenharmony_ci            exist in the cache the return value must be `None`.
361e31aef6aSopenharmony_ci
362e31aef6aSopenharmony_ci    The other arguments to the constructor are the prefix for all keys that
363e31aef6aSopenharmony_ci    is added before the actual cache key and the timeout for the bytecode in
364e31aef6aSopenharmony_ci    the cache system.  We recommend a high (or no) timeout.
365e31aef6aSopenharmony_ci
366e31aef6aSopenharmony_ci    This bytecode cache does not support clearing of used items in the cache.
367e31aef6aSopenharmony_ci    The clear method is a no-operation function.
368e31aef6aSopenharmony_ci
369e31aef6aSopenharmony_ci    .. versionadded:: 2.7
370e31aef6aSopenharmony_ci       Added support for ignoring memcache errors through the
371e31aef6aSopenharmony_ci       `ignore_memcache_errors` parameter.
372e31aef6aSopenharmony_ci    """
373e31aef6aSopenharmony_ci
374e31aef6aSopenharmony_ci    def __init__(
375e31aef6aSopenharmony_ci        self,
376e31aef6aSopenharmony_ci        client: "_MemcachedClient",
377e31aef6aSopenharmony_ci        prefix: str = "jinja2/bytecode/",
378e31aef6aSopenharmony_ci        timeout: t.Optional[int] = None,
379e31aef6aSopenharmony_ci        ignore_memcache_errors: bool = True,
380e31aef6aSopenharmony_ci    ):
381e31aef6aSopenharmony_ci        self.client = client
382e31aef6aSopenharmony_ci        self.prefix = prefix
383e31aef6aSopenharmony_ci        self.timeout = timeout
384e31aef6aSopenharmony_ci        self.ignore_memcache_errors = ignore_memcache_errors
385e31aef6aSopenharmony_ci
386e31aef6aSopenharmony_ci    def load_bytecode(self, bucket: Bucket) -> None:
387e31aef6aSopenharmony_ci        try:
388e31aef6aSopenharmony_ci            code = self.client.get(self.prefix + bucket.key)
389e31aef6aSopenharmony_ci        except Exception:
390e31aef6aSopenharmony_ci            if not self.ignore_memcache_errors:
391e31aef6aSopenharmony_ci                raise
392e31aef6aSopenharmony_ci        else:
393e31aef6aSopenharmony_ci            bucket.bytecode_from_string(code)
394e31aef6aSopenharmony_ci
395e31aef6aSopenharmony_ci    def dump_bytecode(self, bucket: Bucket) -> None:
396e31aef6aSopenharmony_ci        key = self.prefix + bucket.key
397e31aef6aSopenharmony_ci        value = bucket.bytecode_to_string()
398e31aef6aSopenharmony_ci
399e31aef6aSopenharmony_ci        try:
400e31aef6aSopenharmony_ci            if self.timeout is not None:
401e31aef6aSopenharmony_ci                self.client.set(key, value, self.timeout)
402e31aef6aSopenharmony_ci            else:
403e31aef6aSopenharmony_ci                self.client.set(key, value)
404e31aef6aSopenharmony_ci        except Exception:
405e31aef6aSopenharmony_ci            if not self.ignore_memcache_errors:
406e31aef6aSopenharmony_ci                raise
407