5
5
that name.
6
6
"""
7
7
8
+ import functools
8
9
import io
9
10
import sys
10
11
import os
@@ -22,7 +23,9 @@ def getline(filename, lineno, module_globals=None):
22
23
23
24
# The cache
24
25
25
- cache = {} # The cache
26
+ # The cache. Maps filenames to either a thunk which will provide source code,
27
+ # or a tuple (size, mtime, lines, fullname) once loaded.
28
+ cache = {}
26
29
27
30
28
31
def clearcache ():
@@ -37,6 +40,9 @@ def getlines(filename, module_globals=None):
37
40
Update the cache if it doesn't contain an entry for this file already."""
38
41
39
42
if filename in cache :
43
+ entry = cache [filename ]
44
+ if len (entry ) == 1 :
45
+ return updatecache (filename , module_globals )
40
46
return cache [filename ][2 ]
41
47
else :
42
48
return updatecache (filename , module_globals )
@@ -55,7 +61,11 @@ def checkcache(filename=None):
55
61
return
56
62
57
63
for filename in filenames :
58
- size , mtime , lines , fullname = cache [filename ]
64
+ entry = cache [filename ]
65
+ if len (entry ) == 1 :
66
+ # lazy cache entry, leave it lazy.
67
+ continue
68
+ size , mtime , lines , fullname = entry
59
69
if mtime is None :
60
70
continue # no-op for files loaded via a __loader__
61
71
try :
@@ -73,7 +83,8 @@ def updatecache(filename, module_globals=None):
73
83
and return an empty list."""
74
84
75
85
if filename in cache :
76
- del cache [filename ]
86
+ if len (cache [filename ]) != 1 :
87
+ del cache [filename ]
77
88
if not filename or (filename .startswith ('<' ) and filename .endswith ('>' )):
78
89
return []
79
90
@@ -83,27 +94,23 @@ def updatecache(filename, module_globals=None):
83
94
except OSError :
84
95
basename = filename
85
96
86
- # Try for a __loader__, if available
87
- if module_globals and '__loader__' in module_globals :
88
- name = module_globals .get ('__name__' )
89
- loader = module_globals ['__loader__' ]
90
- get_source = getattr (loader , 'get_source' , None )
91
-
92
- if name and get_source :
93
- try :
94
- data = get_source (name )
95
- except (ImportError , OSError ):
96
- pass
97
- else :
98
- if data is None :
99
- # No luck, the PEP302 loader cannot find the source
100
- # for this module.
101
- return []
102
- cache [filename ] = (
103
- len (data ), None ,
104
- [line + '\n ' for line in data .splitlines ()], fullname
105
- )
106
- return cache [filename ][2 ]
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 ), None ,
111
+ [line + '\n ' for line in data .splitlines ()], fullname
112
+ )
113
+ return cache [filename ][2 ]
107
114
108
115
# Try looking through the module search path, which is only useful
109
116
# when handling a relative filename.
@@ -134,6 +141,40 @@ def updatecache(filename, module_globals=None):
134
141
cache [filename ] = size , mtime , lines , fullname
135
142
return lines
136
143
144
+
145
+ def lazycache (filename , module_globals ):
146
+ """Seed the cache for filename with module_globals.
147
+
148
+ The module loader will be asked for the source only when getlines is
149
+ called, not immediately.
150
+
151
+ If there is an entry in the cache already, it is not altered.
152
+
153
+ :return: True if a lazy load is registered in the cache,
154
+ otherwise False. To register such a load a module loader with a
155
+ get_source method must be found, the filename must be a cachable
156
+ filename, and the filename must not be already cached.
157
+ """
158
+ if filename in cache :
159
+ if len (cache [filename ]) == 1 :
160
+ return True
161
+ else :
162
+ return False
163
+ if not filename or (filename .startswith ('<' ) and filename .endswith ('>' )):
164
+ return False
165
+ # Try for a __loader__, if available
166
+ if module_globals and '__loader__' in module_globals :
167
+ name = module_globals .get ('__name__' )
168
+ loader = module_globals ['__loader__' ]
169
+ get_source = getattr (loader , 'get_source' , None )
170
+
171
+ if name and get_source :
172
+ get_lines = functools .partial (get_source , name )
173
+ cache [filename ] = (get_lines ,)
174
+ return True
175
+ return False
176
+
177
+
137
178
#### ---- avoiding having a tokenize2 backport for now ----
138
179
from codecs import lookup , BOM_UTF8
139
180
import re
0 commit comments