cache lines from python source files this is intended to read lines from modules imported hence if a filename is not found it will look down the module search path for a file by that name the cache maps filenames to either a thunk which will provide source code or a tuple size mtime lines fullname once loaded clear the cache entirely cache clear def getlinefilename lineno moduleglobalsnone get the lines for a python source file from the cache update the cache if it doesn t contain an entry for this file already if filename in cache entry cachefilename if lenentry 1 return cachefilename2 try return updatecachefilename moduleglobals except memoryerror clearcache return def checkcachefilenamenone lazy cache entry leave it lazy update a cache entry and return its list of lines if something s wrong print a message discard the cache entry and return an empty list import tokenize if filename in cache if lencachefilename 1 cache popfilename none if not filename or filename startswith and filename endswith return fullname filename try stat os statfullname except oserror basename filename realise a lazy loader based lookup if there is one otherwise try to lookup right now if lazycachefilename moduleglobals try data cachefilename0 except importerror oserror pass else if data is none no luck the pep302 loader cannot find the source for this module return cachefilename lendata none line n for line in data splitlines fullname return cachefilename2 try looking through the module search path which is only useful when handling a relative filename if os path isabsfilename return for dirname in sys path try fullname os path joindirname basename except typeerror attributeerror not sufficiently stringlike to do anything useful with continue try stat os statfullname break except oserror pass else return try with tokenize openfullname as fp lines fp readlines except oserror unicodedecodeerror syntaxerror return if lines and not lines1 endswith n lines1 n size mtime stat stsize stat stmtime cachefilename size mtime lines fullname return lines def lazycachefilename moduleglobals if filename in cache if lencachefilename 1 return true else return false if not filename or filename startswith and filename endswith return false try for a loader if available if moduleglobals and name in moduleglobals name moduleglobals name if loader moduleglobals get loader is none if spec moduleglobals get spec try loader spec loader except attributeerror pass getsource getattrloader getsource none if name and getsource def getlinesnamename args kwargs return getsourcename args kwargs cachefilename getlines return true return false def registercodecode string name cachecode lenstring none line n for line in string splitlines name the cache maps filenames to either a thunk which will provide source code or a tuple size mtime lines fullname once loaded clear the cache entirely get a line for a python source file from the cache update the cache if it doesn t contain an entry for this file already get the lines for a python source file from the cache update the cache if it doesn t contain an entry for this file already discard cache entries that are out of date this is not checked upon each call lazy cache entry leave it lazy no op for files loaded via a __loader__ update a cache entry and return its list of lines if something s wrong print a message discard the cache entry and return an empty list realise a lazy loader based lookup if there is one otherwise try to lookup right now no luck the pep302 loader cannot find the source for this module try looking through the module search path which is only useful when handling a relative filename not sufficiently string like to do anything useful with seed the cache for filename with module_globals the module loader will be asked for the source only when getlines is called not immediately if there is an entry in the cache already it is not altered return true if a lazy load is registered in the cache otherwise false to register such a load a module loader with a get_source method must be found the filename must be a cacheable filename and the filename must not be already cached try for a __loader__ if available
import sys
import os

__all__ = ["getline", "clearcache", "checkcache", "lazycache"]




cache = {}


def clearcache():
    
    cache.clear()


def getline(filename, lineno, module_globals=None):
    

    lines = getlines(filename, module_globals)
    if 1 <= lineno <= len(lines):
        return lines[lineno - 1]
    return ''


def getlines(filename, module_globals=None):
    

    if filename in cache:
        entry = cache[filename]
        if len(entry) != 1:
            return cache[filename][2]

    try:
        return updatecache(filename, module_globals)
    except MemoryError:
        clearcache()
        return []


def checkcache(filename=None):
    

    if filename is None:
        filenames = list(cache.keys())
    elif filename in cache:
        filenames = [filename]
    else:
        return

    for filename in filenames:
        entry = cache[filename]
        if len(entry) == 1:
            
            continue
        size, mtime, lines, fullname = entry
        if mtime is None:
            continue   
        try:
            stat = os.stat(fullname)
        except OSError:
            cache.pop(filename, None)
            continue
        if size != stat.st_size or mtime != stat.st_mtime:
            cache.pop(filename, None)


def updatecache(filename, module_globals=None):
    

    import tokenize

    if filename in cache:
        if len(cache[filename]) != 1:
            cache.pop(filename, None)
    if not filename or (filename.startswith('<') and filename.endswith('>')):
        return []

    fullname = filename
    try:
        stat = os.stat(fullname)
    except OSError:
        basename = filename

        
        
        if lazycache(filename, module_globals):
            try:
                data = cache[filename][0]()
            except (ImportError, OSError):
                pass
            else:
                if data is None:
                    
                    
                    return []
                cache[filename] = (
                    len(data),
                    None,
                    [line + '\n' for line in data.splitlines()],
                    fullname
                )
                return cache[filename][2]

        
        
        if os.path.isabs(filename):
            return []

        for dirname in sys.path:
            try:
                fullname = os.path.join(dirname, basename)
            except (TypeError, AttributeError):
                
                continue
            try:
                stat = os.stat(fullname)
                break
            except OSError:
                pass
        else:
            return []
    try:
        with tokenize.open(fullname) as fp:
            lines = fp.readlines()
    except (OSError, UnicodeDecodeError, SyntaxError):
        return []
    if lines and not lines[-1].endswith('\n'):
        lines[-1] += '\n'
    size, mtime = stat.st_size, stat.st_mtime
    cache[filename] = size, mtime, lines, fullname
    return lines


def lazycache(filename, module_globals):
    
    if filename in cache:
        if len(cache[filename]) == 1:
            return True
        else:
            return False
    if not filename or (filename.startswith('<') and filename.endswith('>')):
        return False
    
    if module_globals and '__name__' in module_globals:
        name = module_globals['__name__']
        if (loader := module_globals.get('__loader__')) is None:
            if spec := module_globals.get('__spec__'):
                try:
                    loader = spec.loader
                except AttributeError:
                    pass
        get_source = getattr(loader, 'get_source', None)

        if name and get_source:
            def get_lines(name=name, *args, **kwargs):
                return get_source(name, *args, **kwargs)
            cache[filename] = (get_lines,)
            return True
    return False


def _register_code(code, string, name):
    cache[code] = (
            len(string),
            None,
            [line + '\n' for line in string.splitlines()],
            name)