(root)/
Python-3.11.7/
Lib/
linecache.py
       1  """Cache lines from Python source files.
       2  
       3  This is intended to read lines from modules imported -- hence if a filename
       4  is not found, it will look down the module search path for a file by
       5  that name.
       6  """
       7  
       8  import functools
       9  import sys
      10  import os
      11  import tokenize
      12  
      13  __all__ = ["getline", "clearcache", "checkcache", "lazycache"]
      14  
      15  
      16  # The cache. Maps filenames to either a thunk which will provide source code,
      17  # or a tuple (size, mtime, lines, fullname) once loaded.
      18  cache = {}
      19  
      20  
      21  def clearcache():
      22      """Clear the cache entirely."""
      23      cache.clear()
      24  
      25  
      26  def getline(filename, lineno, module_globals=None):
      27      """Get a line for a Python source file from the cache.
      28      Update the cache if it doesn't contain an entry for this file already."""
      29  
      30      lines = getlines(filename, module_globals)
      31      if 1 <= lineno <= len(lines):
      32          return lines[lineno - 1]
      33      return ''
      34  
      35  
      36  def getlines(filename, module_globals=None):
      37      """Get the lines for a Python source file from the cache.
      38      Update the cache if it doesn't contain an entry for this file already."""
      39  
      40      if filename in cache:
      41          entry = cache[filename]
      42          if len(entry) != 1:
      43              return cache[filename][2]
      44  
      45      try:
      46          return updatecache(filename, module_globals)
      47      except MemoryError:
      48          clearcache()
      49          return []
      50  
      51  
      52  def checkcache(filename=None):
      53      """Discard cache entries that are out of date.
      54      (This is not checked upon each call!)"""
      55  
      56      if filename is None:
      57          filenames = list(cache.keys())
      58      elif filename in cache:
      59          filenames = [filename]
      60      else:
      61          return
      62  
      63      for filename in filenames:
      64          entry = cache[filename]
      65          if len(entry) == 1:
      66              # lazy cache entry, leave it lazy.
      67              continue
      68          size, mtime, lines, fullname = entry
      69          if mtime is None:
      70              continue   # no-op for files loaded via a __loader__
      71          try:
      72              stat = os.stat(fullname)
      73          except OSError:
      74              cache.pop(filename, None)
      75              continue
      76          if size != stat.st_size or mtime != stat.st_mtime:
      77              cache.pop(filename, None)
      78  
      79  
      80  def updatecache(filename, module_globals=None):
      81      """Update a cache entry and return its list of lines.
      82      If something's wrong, print a message, discard the cache entry,
      83      and return an empty list."""
      84  
      85      if filename in cache:
      86          if len(cache[filename]) != 1:
      87              cache.pop(filename, None)
      88      if not filename or (filename.startswith('<') and filename.endswith('>')):
      89          return []
      90  
      91      fullname = filename
      92      try:
      93          stat = os.stat(fullname)
      94      except OSError:
      95          basename = filename
      96  
      97          # Realise a lazy loader based lookup if there is one
      98          # otherwise try to lookup right now.
      99          if lazycache(filename, module_globals):
     100              try:
     101                  data = cache[filename][0]()
     102              except (ImportError, OSError):
     103                  pass
     104              else:
     105                  if data is None:
     106                      # No luck, the PEP302 loader cannot find the source
     107                      # for this module.
     108                      return []
     109                  cache[filename] = (
     110                      len(data),
     111                      None,
     112                      [line + '\n' for line in data.splitlines()],
     113                      fullname
     114                  )
     115                  return cache[filename][2]
     116  
     117          # Try looking through the module search path, which is only useful
     118          # when handling a relative filename.
     119          if os.path.isabs(filename):
     120              return []
     121  
     122          for dirname in sys.path:
     123              try:
     124                  fullname = os.path.join(dirname, basename)
     125              except (TypeError, AttributeError):
     126                  # Not sufficiently string-like to do anything useful with.
     127                  continue
     128              try:
     129                  stat = os.stat(fullname)
     130                  break
     131              except OSError:
     132                  pass
     133          else:
     134              return []
     135      try:
     136          with tokenize.open(fullname) as fp:
     137              lines = fp.readlines()
     138      except (OSError, UnicodeDecodeError, SyntaxError):
     139          return []
     140      if lines and not lines[-1].endswith('\n'):
     141          lines[-1] += '\n'
     142      size, mtime = stat.st_size, stat.st_mtime
     143      cache[filename] = size, mtime, lines, fullname
     144      return lines
     145  
     146  
     147  def lazycache(filename, module_globals):
     148      """Seed the cache for filename with module_globals.
     149  
     150      The module loader will be asked for the source only when getlines is
     151      called, not immediately.
     152  
     153      If there is an entry in the cache already, it is not altered.
     154  
     155      :return: True if a lazy load is registered in the cache,
     156          otherwise False. To register such a load a module loader with a
     157          get_source method must be found, the filename must be a cacheable
     158          filename, and the filename must not be already cached.
     159      """
     160      if filename in cache:
     161          if len(cache[filename]) == 1:
     162              return True
     163          else:
     164              return False
     165      if not filename or (filename.startswith('<') and filename.endswith('>')):
     166          return False
     167      # Try for a __loader__, if available
     168      if module_globals and '__name__' in module_globals:
     169          name = module_globals['__name__']
     170          if (loader := module_globals.get('__loader__')) is None:
     171              if spec := module_globals.get('__spec__'):
     172                  try:
     173                      loader = spec.loader
     174                  except AttributeError:
     175                      pass
     176          get_source = getattr(loader, 'get_source', None)
     177  
     178          if name and get_source:
     179              get_lines = functools.partial(get_source, name)
     180              cache[filename] = (get_lines,)
     181              return True
     182      return False