docstr: Get a node from storage.
  
  The cache is not there to prevent hitting the disk, the OS is already
  very good at it. It is there to avoid paying the price of deserializing
  the data to create the Node object and its entry. This is a very
  expensive operation in Python.
  
  Since we have at most a single writer we can write to cache on
  `set_node` if we invalidate the cache when a transaction is rolled
  back.
code: def get_node(self, page: int):
      """Get a node from storage.
  
      The cache is not there to prevent hitting the disk, the OS is already
      very good at it. It is there to avoid paying the price of deserializing
      the data to create the Node object and its entry. This is a very
      expensive operation in Python.
  
      Since we have at most a single writer we can write to cache on
      `set_node` if we invalidate the cache when a transaction is rolled
      back.
      """
      node = self._cache.get(page)
      if node is not None:
          return node
  
      data = self._wal.get_page(page)
      if not data:
          data = self._read_page(page)
  
      node = Node.from_page_data(self._tree_conf, data=data, page=page)
      self._cache[node.page] = node
      return node
  
imports: import enum
  import io
  from logging import getLogger
  import os
  import platform
  from typing import Union, Tuple, Optional
  import cachetools
  import rwlock
  from .node import Node, FreelistNode
  from .const import ENDIAN, PAGE_REFERENCE_BYTES, OTHERS_BYTES, TreeConf, FRAME_TYPE_BYTES
label_desc: Use image node_id 2 for calling the FileMemory obj's underlying member method code with example usage: obj.get_node(page) and returns: node, node