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