| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 0bfd0a4 commit 6bc2c1e
7 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -43,6 +43,14 @@ The :mod:`linecache` module defines the following functions: | |||
| 43 | 43 | changed on disk, and you require the updated version. If *filename* is omitted, | |
| 44 | 44 | it will check all the entries in the cache. | |
| 45 | 45 | ||
| 46 | + .. function:: lazycache(filename, module_globals) | ||
| 47 | + | ||
| 48 | + Capture enough detail about a non-file based module to permit getting its | ||
| 49 | + lines later via :func:`getline` even if *module_globals* is None in the later | ||
| 50 | + call. This avoids doing I/O until a line is actually needed, without having | ||
| 51 | + to carry the module globals around indefinitely. | ||
| 52 | + | ||
| 53 | + .. versionadded:: 3.5 | ||
| 46 | 54 | ||
| 47 | 55 | Example:: | |
| 48 | 56 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -136,6 +136,130 @@ The module defines the following functions: | |||
| 136 | 136 | ||
| 137 | 137 | .. versionadded:: 3.4 | |
| 138 | 138 | ||
| 139 | + .. function:: walk_stack(f) | ||
| 140 | + | ||
| 141 | + Walk a stack following f.f_back from the given frame, yielding the frame and | ||
| 142 | + line number for each frame. If f is None, the current stack is used. | ||
| 143 | + This helper is used with *Stack.extract*. | ||
| 144 | + | ||
| 145 | + .. versionadded:: 3.5 | ||
| 146 | + | ||
| 147 | + .. function:: walk_tb(tb) | ||
| 148 | + | ||
| 149 | + Walk a traceback following tb_next yielding the frame and line number for | ||
| 150 | + each frame. This helper is used with *Stack.extract*. | ||
| 151 | + | ||
| 152 | + .. versionadded:: 3.5 | ||
| 153 | + | ||
| 154 | + The module also defines the following classes: | ||
| 155 | + | ||
| 156 | + :class:`TracebackException` Objects | ||
| 157 | + ----------------------------------- | ||
| 158 | + | ||
| 159 | + :class:`.TracebackException` objects are created from actual exceptions to | ||
| 160 | + capture data for later printing in a lightweight fashion. | ||
| 161 | + | ||
| 162 | + .. class:: TracebackException(exc_type, exc_value, exc_traceback, limit=None, lookup_lines=True) | ||
| 163 | + | ||
| 164 | + Capture an exception for later rendering. limit, lookup_lines are as for | ||
| 165 | + the :class:`.StackSummary` class. | ||
| 166 | + | ||
| 167 | + .. versionadded:: 3.5 | ||
| 168 | + | ||
| 169 | + .. classmethod:: `.from_exception`(exc, limit=None, lookup_lines=True) | ||
| 170 | + | ||
| 171 | + Capture an exception for later rendering. limit and lookup_lines | ||
| 172 | + are as for the :class:`.StackSummary` class. | ||
| 173 | + | ||
| 174 | + .. versionadded:: 3.5 | ||
| 175 | + | ||
| 176 | + .. attribute:: `.__cause__` A TracebackException of the original *__cause__*. | ||
| 177 | + | ||
| 178 | + .. attribute:: `.__context__` A TracebackException of the original *__context__*. | ||
| 179 | + .. attribute:: `.__suppress_context__` The *__suppress_context__* value from the | ||
| 180 | + original exception. | ||
| 181 | + .. attribute:: `.stack` A `StackSummary` representing the traceback. | ||
| 182 | + .. attribute:: `.exc_type` The class of the original traceback. | ||
| 183 | + .. attribute:: `.filename` For syntax errors - the filename where the error | ||
| 184 | + occured. | ||
| 185 | + .. attribute:: `.lineno` For syntax errors - the linenumber where the error | ||
| 186 | + occured. | ||
| 187 | + .. attribute:: `.text` For syntax errors - the text where the error | ||
| 188 | + occured. | ||
| 189 | + .. attribute:: `.offset` For syntax errors - the offset into the text where the | ||
| 190 | + error occured. | ||
| 191 | + .. attribute:: `.msg` For syntax errors - the compiler error message. | ||
| 192 | + | ||
| 193 | + .. method:: TracebackException.format(chain=True) | ||
| 194 | + | ||
| 195 | + Format the exception. | ||
| 196 | + | ||
| 197 | + If chain is not *True*, *__cause__* and *__context__* will not be formatted. | ||
| 198 | + | ||
| 199 | + The return value is a generator of strings, each ending in a newline and | ||
| 200 | + some containing internal newlines. `print_exception` is a wrapper around | ||
| 201 | + this method which just prints the lines to a file. | ||
| 202 | + | ||
| 203 | + The message indicating which exception occurred is always the last | ||
| 204 | + string in the output. | ||
| 205 | + | ||
| 206 | + .. versionadded:: 3.5 | ||
| 207 | + | ||
| 208 | + .. method:: TracebackException.format_exception_only() | ||
| 209 | + | ||
| 210 | + Format the exception part of the traceback. | ||
| 211 | + | ||
| 212 | + The return value is a generator of strings, each ending in a newline. | ||
| 213 | + | ||
| 214 | + Normally, the generator emits a single string; however, for | ||
| 215 | + SyntaxError exceptions, it emites several lines that (when | ||
| 216 | + printed) display detailed information about where the syntax | ||
| 217 | + error occurred. | ||
| 218 | + | ||
| 219 | + The message indicating which exception occurred is always the last | ||
| 220 | + string in the output. | ||
| 221 | + | ||
| 222 | + .. versionadded:: 3.5 | ||
| 223 | + | ||
| 224 | + | ||
| 225 | + :class:`StackSummary` Objects | ||
| 226 | + ----------------------------- | ||
| 227 | + | ||
| 228 | + :class:`.StackSummary` objects represent a call stack ready for formatting. | ||
| 229 | + | ||
| 230 | + .. classmethod:: StackSummary.extract(frame_gen, limit=None, lookup_lines=True) | ||
| 231 | + | ||
| 232 | + Construct a StackSummary object from a frame generator (such as is returned by | ||
| 233 | + `walk_stack` or `walk_tb`. | ||
| 234 | + | ||
| 235 | + If limit is supplied, only this many frames are taken from frame_gen. | ||
| 236 | + If lookup_lines is False, the returned FrameSummary objects will not have read | ||
| 237 | + their lines in yet, making the cost of creating the StackSummary cheaper (which | ||
| 238 | + may be valuable if it may not actually get formatted). | ||
| 239 | + | ||
| 240 | + .. versionadded:: 3.5 | ||
| 241 | + | ||
| 242 | + .. classmethod:: StackSummary.from_list(a_list) | ||
| 243 | + | ||
| 244 | + Construct a StackSummary object from a supplied old-style list of tuples. Each | ||
| 245 | + tuple should be a 4-tuple with filename, lineno, name, line as the elements. | ||
| 246 | + | ||
| 247 | + .. versionadded:: 3.5 | ||
| 248 | + | ||
| 249 | + :class:`FrameSummary` Objects | ||
| 250 | + ----------------------------- | ||
| 251 | + | ||
| 252 | + FrameSummary objects represent a single frame in a traceback. | ||
| 253 | + | ||
| 254 | + .. class:: FrameSummary(filename, lineno, name, lookup_line=True, locals=None, line=None) | ||
| 255 | + :noindex: | ||
| 256 | + | ||
| 257 | + Represent a single frame in the traceback or stack that is being formatted | ||
| 258 | + or printed. It may optionally have a stringified version of the frames | ||
| 259 | + locals included in it. If *lookup_line* is False, the source code is not | ||
| 260 | + looked up until the FrameSummary has the :attr:`line` attribute accessed (which | ||
| 261 | + also happens when casting it to a tuple). Line may be directly provided, and | ||
| 262 | + will prevent line lookups happening at all. | ||
| 139 | 263 | ||
| 140 | 264 | .. _traceback-example: | |
| 141 | 265 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -5,6 +5,7 @@ | |||
| 5 | 5 | that name. | |
| 6 | 6 | """ | |
| 7 | 7 | ||
| 8 | + import functools | ||
| 8 | 9 | import sys | |
| 9 | 10 | import os | |
| 10 | 11 | import tokenize | |
@@ -21,7 +22,9 @@ def getline(filename, lineno, module_globals=None): | |||
| 21 | 22 | ||
| 22 | 23 | # The cache | |
| 23 | 24 | ||
| 24 | - cache = {} # The cache | ||
| 25 | + # The cache. Maps filenames to either a thunk which will provide source code, | ||
| 26 | + # or a tuple (size, mtime, lines, fullname) once loaded. | ||
| 27 | + cache = {} | ||
| 25 | 28 | ||
| 26 | 29 | ||
| 27 | 30 | def clearcache(): | |
@@ -36,6 +39,9 @@ def getlines(filename, module_globals=None): | |||
| 36 | 39 | Update the cache if it doesn't contain an entry for this file already.""" | |
| 37 | 40 | ||
| 38 | 41 | if filename in cache: | |
| 42 | + entry = cache[filename] | ||
| 43 | + if len(entry) == 1: | ||
| 44 | + return updatecache(filename, module_globals) | ||
| 39 | 45 | return cache[filename][2] | |
| 40 | 46 | else: | |
| 41 | 47 | return updatecache(filename, module_globals) | |
@@ -54,7 +60,11 @@ def checkcache(filename=None): | |||
| 54 | 60 | return | |
| 55 | 61 | ||
| 56 | 62 | for filename in filenames: | |
| 57 | - size, mtime, lines, fullname = cache[filename] | ||
| 63 | + entry = cache[filename] | ||
| 64 | + if len(entry) == 1: | ||
| 65 | + # lazy cache entry, leave it lazy. | ||
| 66 | + continue | ||
| 67 | + size, mtime, lines, fullname = entry | ||
| 58 | 68 | if mtime is None: | |
| 59 | 69 | continue # no-op for files loaded via a __loader__ | |
| 60 | 70 | try: | |
@@ -72,7 +82,8 @@ def updatecache(filename, module_globals=None): | |||
| 72 | 82 | and return an empty list.""" | |
| 73 | 83 | ||
| 74 | 84 | if filename in cache: | |
| 75 | - del cache[filename] | ||
| 85 | + if len(cache[filename]) != 1: | ||
| 86 | + del cache[filename] | ||
| 76 | 87 | if not filename or (filename.startswith('<') and filename.endswith('>')): | |
| 77 | 88 | return [] | |
| 78 | 89 | ||
@@ -82,27 +93,23 @@ def updatecache(filename, module_globals=None): | |||
| 82 | 93 | except OSError: | |
| 83 | 94 | basename = filename | |
| 84 | 95 | ||
| 85 | - # Try for a __loader__, if available | ||
| 86 | - if module_globals and '__loader__' in module_globals: | ||
| 87 | - name = module_globals.get('__name__') | ||
| 88 | - loader = module_globals['__loader__'] | ||
| 89 | - get_source = getattr(loader, 'get_source', None) | ||
| 90 | - | ||
| 91 | - if name and get_source: | ||
| 92 | - try: | ||
| 93 | - data = get_source(name) | ||
| 94 | - except (ImportError, OSError): | ||
| 95 | - pass | ||
| 96 | - else: | ||
| 97 | - if data is None: | ||
| 98 | - # No luck, the PEP302 loader cannot find the source | ||
| 99 | - # for this module. | ||
| 100 | - return [] | ||
| 101 | - cache[filename] = ( | ||
| 102 | - len(data), None, | ||
| 103 | - [line+'\n' for line in data.splitlines()], fullname | ||
| 104 | - ) | ||
| 105 | - return cache[filename][2] | ||
| 96 | + # Realise a lazy loader based lookup if there is one | ||
| 97 | + # otherwise try to lookup right now. | ||
| 98 | + if lazycache(filename, module_globals): | ||
| 99 | + try: | ||
| 100 | + data = cache[filename][0]() | ||
| 101 | + except (ImportError, OSError): | ||
| 102 | + pass | ||
| 103 | + else: | ||
| 104 | + if data is None: | ||
| 105 | + # No luck, the PEP302 loader cannot find the source | ||
| 106 | + # for this module. | ||
| 107 | + return [] | ||
| 108 | + cache[filename] = ( | ||
| 109 | + len(data), None, | ||
| 110 | + [line+'\n' for line in data.splitlines()], fullname | ||
| 111 | + ) | ||
| 112 | + return cache[filename][2] | ||
| 106 | 113 | ||
| 107 | 114 | # Try looking through the module search path, which is only useful | |
| 108 | 115 | # when handling a relative filename. | |
@@ -132,3 +139,36 @@ def updatecache(filename, module_globals=None): | |||
| 132 | 139 | size, mtime = stat.st_size, stat.st_mtime | |
| 133 | 140 | cache[filename] = size, mtime, lines, fullname | |
| 134 | 141 | return lines | |
| 142 | + | ||
| 143 | + | ||
| 144 | + def lazycache(filename, module_globals): | ||
| 145 | + """Seed the cache for filename with module_globals. | ||
| 146 | + | ||
| 147 | + The module loader will be asked for the source only when getlines is | ||
| 148 | + called, not immediately. | ||
| 149 | + | ||
| 150 | + If there is an entry in the cache already, it is not altered. | ||
| 151 | + | ||
| 152 | + :return: True if a lazy load is registered in the cache, | ||
| 153 | + otherwise False. To register such a load a module loader with a | ||
| 154 | + get_source method must be found, the filename must be a cachable | ||
| 155 | + filename, and the filename must not be already cached. | ||
| 156 | + """ | ||
| 157 | + if filename in cache: | ||
| 158 | + if len(cache[filename]) == 1: | ||
| 159 | + return True | ||
| 160 | + else: | ||
| 161 | + return False | ||
| 162 | + if not filename or (filename.startswith('<') and filename.endswith('>')): | ||
| 163 | + return False | ||
| 164 | + # Try for a __loader__, if available | ||
| 165 | + if module_globals and '__loader__' in module_globals: | ||
| 166 | + name = module_globals.get('__name__') | ||
| 167 | + loader = module_globals['__loader__'] | ||
| 168 | + get_source = getattr(loader, 'get_source', None) | ||
| 169 | + | ||
| 170 | + if name and get_source: | ||
| 171 | + get_lines = functools.partial(get_source, name) | ||
| 172 | + cache[filename] = (get_lines,) | ||
| 173 | + return True | ||
| 174 | + return False | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,6 +7,7 @@ | |||
| 7 | 7 | ||
| 8 | 8 | ||
| 9 | 9 | FILENAME = linecache.__file__ | |
| 10 | + NONEXISTENT_FILENAME = FILENAME + '.missing' | ||
| 10 | 11 | INVALID_NAME = '!@$)(!@#_1' | |
| 11 | 12 | EMPTY = '' | |
| 12 | 13 | TESTS = 'inspect_fodder inspect_fodder2 mapping_tests' | |
@@ -126,6 +127,49 @@ def test_checkcache(self): | |||
| 126 | 127 | self.assertEqual(line, getline(source_name, index + 1)) | |
| 127 | 128 | source_list.append(line) | |
| 128 | 129 | ||
| 130 | + def test_lazycache_no_globals(self): | ||
| 131 | + lines = linecache.getlines(FILENAME) | ||
| 132 | + linecache.clearcache() | ||
| 133 | + self.assertEqual(False, linecache.lazycache(FILENAME, None)) | ||
| 134 | + self.assertEqual(lines, linecache.getlines(FILENAME)) | ||
| 135 | + | ||
| 136 | + def test_lazycache_smoke(self): | ||
| 137 | + lines = linecache.getlines(NONEXISTENT_FILENAME, globals()) | ||
| 138 | + linecache.clearcache() | ||
| 139 | + self.assertEqual( | ||
| 140 | + True, linecache.lazycache(NONEXISTENT_FILENAME, globals())) | ||
| 141 | + self.assertEqual(1, len(linecache.cache[NONEXISTENT_FILENAME])) | ||
| 142 | + # Note here that we're looking up a non existant filename with no | ||
| 143 | + # globals: this would error if the lazy value wasn't resolved. | ||
| 144 | + self.assertEqual(lines, linecache.getlines(NONEXISTENT_FILENAME)) | ||
| 145 | + | ||
| 146 | + def test_lazycache_provide_after_failed_lookup(self): | ||
| 147 | + linecache.clearcache() | ||
| 148 | + lines = linecache.getlines(NONEXISTENT_FILENAME, globals()) | ||
| 149 | + linecache.clearcache() | ||
| 150 | + linecache.getlines(NONEXISTENT_FILENAME) | ||
| 151 | + linecache.lazycache(NONEXISTENT_FILENAME, globals()) | ||
| 152 | + self.assertEqual(lines, linecache.updatecache(NONEXISTENT_FILENAME)) | ||
| 153 | + | ||
| 154 | + def test_lazycache_check(self): | ||
| 155 | + linecache.clearcache() | ||
| 156 | + linecache.lazycache(NONEXISTENT_FILENAME, globals()) | ||
| 157 | + linecache.checkcache() | ||
| 158 | + | ||
| 159 | + def test_lazycache_bad_filename(self): | ||
| 160 | + linecache.clearcache() | ||
| 161 | + self.assertEqual(False, linecache.lazycache('', globals())) | ||
| 162 | + self.assertEqual(False, linecache.lazycache('<foo>', globals())) | ||
| 163 | + | ||
| 164 | + def test_lazycache_already_cached(self): | ||
| 165 | + linecache.clearcache() | ||
| 166 | + lines = linecache.getlines(NONEXISTENT_FILENAME, globals()) | ||
| 167 | + self.assertEqual( | ||
| 168 | + False, | ||
| 169 | + linecache.lazycache(NONEXISTENT_FILENAME, globals())) | ||
| 170 | + self.assertEqual(4, len(linecache.cache[NONEXISTENT_FILENAME])) | ||
| 171 | + | ||
| 172 | + | ||
| 129 | 173 | def test_main(): | |
| 130 | 174 | support.run_unittest(LineCacheTests) | |
| 131 | 175 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments