class_docstr: In-memory cache storage wrapper.
  
  This class wraps a cache storage and adds an in-memory cache front layer,
  which is used to reduce the number of calls to the storage.
  
  The in-memory cache is a TTL cache, which means that the entries are
  automatically removed if a given time to live (TTL) has passed.
  
  The in-memory cache is also an LRU cache, which means that the entries
  are automatically removed if the cache size exceeds a given maxsize.
  
  If the storage implements its strategy for maxsize, it is recommended
  (but not necessary) that the storage implement the same LRU strategy,
  otherwise a situation may arise when different items are deleted from
  the memory cache and from the storage.
  
  Notes
  -----
  Threading: in-memory caching layer is thread safe: we hold self._mem_cache_lock for
  working with this self._mem_cache object.
  However, we do not hold this lock when calling into the underlying storage,
  so it is the responsibility of the that storage to ensure that it is safe to use
  it from multiple threads.
class_docstr_tok: ["In-memory","cache","storage","wrapper",".","This","class","wraps","a","cache","storage","and","adds","an","in-memory","cache","front","layer",",","which","is","used","to","reduce","the","number","of","calls","to","the","storage",".","The","in-memory","cache","is","a","TTL","cache",",","which","means","that","the","entries","are","automatically","removed","if","a","given","time","to","live","(","TTL",")","has","passed",".","The","in-memory","cache","is","also","an","LRU","cache",",","which","means","that","the","entries","are","automatically","removed","if","the","cache","size","exceeds","a","given","maxsize",".","If","the","storage","implements","its","strategy","for","maxsize",",","it","is","recommended","(","but","not","necessary",")","that","the","storage","implement","the","same","LRU","strategy",",","otherwise","a","situation","may","arise","when","different","items","are","deleted","from","the","memory","cache","and","from","the","storage",".","Notes","--","--","-","Threading",":","in-memory","caching","layer","is","thread","safe",":","we","hold","self._mem_cache_lock","for","working","with","this","self._mem_cache","object",".","However",",","we","do","not","hold","this","lock","when","calling","into","the","underlying","storage",",","so","it","is","the","responsibility","of","the","that","storage","to","ensure","that","it","is","safe","to","use","it","from","multiple","threads","."]
code: def _read_from_mem_cache(self, key: str) -> bytes:
      with self._mem_cache_lock:
          if key in self._mem_cache:
              entry = bytes(self._mem_cache[key])
              _LOGGER.debug("Memory cache HIT: %s", key)
              return entry
  
          else:
              _LOGGER.debug("Memory cache MISS: %s", key)
              raise CacheStorageKeyNotFoundError(
                  "Key not found in mem cache"
              )
  
imports: from __future__ import annotations
  import math
  import threading
  from cachetools import TTLCache
  from streamlit.logger import get_logger
  from streamlit.runtime.caching import cache_utils
  from streamlit.runtime.caching.storage.cache_storage_protocol import CacheStorage, CacheStorageContext, CacheStorageKeyNotFoundError
  from streamlit.runtime.stats import CacheStat
label_desc: Use image node_id 10 for calling the InMemoryCacheStorageWrapper obj's underlying member method code with example usage: obj._read_from_mem_cache(key) and returns: entry