comparison env/lib/python3.7/site-packages/distlib/resources.py @ 0:26e78fe6e8c4 draft

"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author shellac
date Sat, 02 May 2020 07:14:21 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:26e78fe6e8c4
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright (C) 2013-2017 Vinay Sajip.
4 # Licensed to the Python Software Foundation under a contributor agreement.
5 # See LICENSE.txt and CONTRIBUTORS.txt.
6 #
7 from __future__ import unicode_literals
8
9 import bisect
10 import io
11 import logging
12 import os
13 import pkgutil
14 import shutil
15 import sys
16 import types
17 import zipimport
18
19 from . import DistlibException
20 from .util import cached_property, get_cache_base, path_to_cache_dir, Cache
21
22 logger = logging.getLogger(__name__)
23
24
25 cache = None # created when needed
26
27
28 class ResourceCache(Cache):
29 def __init__(self, base=None):
30 if base is None:
31 # Use native string to avoid issues on 2.x: see Python #20140.
32 base = os.path.join(get_cache_base(), str('resource-cache'))
33 super(ResourceCache, self).__init__(base)
34
35 def is_stale(self, resource, path):
36 """
37 Is the cache stale for the given resource?
38
39 :param resource: The :class:`Resource` being cached.
40 :param path: The path of the resource in the cache.
41 :return: True if the cache is stale.
42 """
43 # Cache invalidation is a hard problem :-)
44 return True
45
46 def get(self, resource):
47 """
48 Get a resource into the cache,
49
50 :param resource: A :class:`Resource` instance.
51 :return: The pathname of the resource in the cache.
52 """
53 prefix, path = resource.finder.get_cache_info(resource)
54 if prefix is None:
55 result = path
56 else:
57 result = os.path.join(self.base, self.prefix_to_dir(prefix), path)
58 dirname = os.path.dirname(result)
59 if not os.path.isdir(dirname):
60 os.makedirs(dirname)
61 if not os.path.exists(result):
62 stale = True
63 else:
64 stale = self.is_stale(resource, path)
65 if stale:
66 # write the bytes of the resource to the cache location
67 with open(result, 'wb') as f:
68 f.write(resource.bytes)
69 return result
70
71
72 class ResourceBase(object):
73 def __init__(self, finder, name):
74 self.finder = finder
75 self.name = name
76
77
78 class Resource(ResourceBase):
79 """
80 A class representing an in-package resource, such as a data file. This is
81 not normally instantiated by user code, but rather by a
82 :class:`ResourceFinder` which manages the resource.
83 """
84 is_container = False # Backwards compatibility
85
86 def as_stream(self):
87 """
88 Get the resource as a stream.
89
90 This is not a property to make it obvious that it returns a new stream
91 each time.
92 """
93 return self.finder.get_stream(self)
94
95 @cached_property
96 def file_path(self):
97 global cache
98 if cache is None:
99 cache = ResourceCache()
100 return cache.get(self)
101
102 @cached_property
103 def bytes(self):
104 return self.finder.get_bytes(self)
105
106 @cached_property
107 def size(self):
108 return self.finder.get_size(self)
109
110
111 class ResourceContainer(ResourceBase):
112 is_container = True # Backwards compatibility
113
114 @cached_property
115 def resources(self):
116 return self.finder.get_resources(self)
117
118
119 class ResourceFinder(object):
120 """
121 Resource finder for file system resources.
122 """
123
124 if sys.platform.startswith('java'):
125 skipped_extensions = ('.pyc', '.pyo', '.class')
126 else:
127 skipped_extensions = ('.pyc', '.pyo')
128
129 def __init__(self, module):
130 self.module = module
131 self.loader = getattr(module, '__loader__', None)
132 self.base = os.path.dirname(getattr(module, '__file__', ''))
133
134 def _adjust_path(self, path):
135 return os.path.realpath(path)
136
137 def _make_path(self, resource_name):
138 # Issue #50: need to preserve type of path on Python 2.x
139 # like os.path._get_sep
140 if isinstance(resource_name, bytes): # should only happen on 2.x
141 sep = b'/'
142 else:
143 sep = '/'
144 parts = resource_name.split(sep)
145 parts.insert(0, self.base)
146 result = os.path.join(*parts)
147 return self._adjust_path(result)
148
149 def _find(self, path):
150 return os.path.exists(path)
151
152 def get_cache_info(self, resource):
153 return None, resource.path
154
155 def find(self, resource_name):
156 path = self._make_path(resource_name)
157 if not self._find(path):
158 result = None
159 else:
160 if self._is_directory(path):
161 result = ResourceContainer(self, resource_name)
162 else:
163 result = Resource(self, resource_name)
164 result.path = path
165 return result
166
167 def get_stream(self, resource):
168 return open(resource.path, 'rb')
169
170 def get_bytes(self, resource):
171 with open(resource.path, 'rb') as f:
172 return f.read()
173
174 def get_size(self, resource):
175 return os.path.getsize(resource.path)
176
177 def get_resources(self, resource):
178 def allowed(f):
179 return (f != '__pycache__' and not
180 f.endswith(self.skipped_extensions))
181 return set([f for f in os.listdir(resource.path) if allowed(f)])
182
183 def is_container(self, resource):
184 return self._is_directory(resource.path)
185
186 _is_directory = staticmethod(os.path.isdir)
187
188 def iterator(self, resource_name):
189 resource = self.find(resource_name)
190 if resource is not None:
191 todo = [resource]
192 while todo:
193 resource = todo.pop(0)
194 yield resource
195 if resource.is_container:
196 rname = resource.name
197 for name in resource.resources:
198 if not rname:
199 new_name = name
200 else:
201 new_name = '/'.join([rname, name])
202 child = self.find(new_name)
203 if child.is_container:
204 todo.append(child)
205 else:
206 yield child
207
208
209 class ZipResourceFinder(ResourceFinder):
210 """
211 Resource finder for resources in .zip files.
212 """
213 def __init__(self, module):
214 super(ZipResourceFinder, self).__init__(module)
215 archive = self.loader.archive
216 self.prefix_len = 1 + len(archive)
217 # PyPy doesn't have a _files attr on zipimporter, and you can't set one
218 if hasattr(self.loader, '_files'):
219 self._files = self.loader._files
220 else:
221 self._files = zipimport._zip_directory_cache[archive]
222 self.index = sorted(self._files)
223
224 def _adjust_path(self, path):
225 return path
226
227 def _find(self, path):
228 path = path[self.prefix_len:]
229 if path in self._files:
230 result = True
231 else:
232 if path and path[-1] != os.sep:
233 path = path + os.sep
234 i = bisect.bisect(self.index, path)
235 try:
236 result = self.index[i].startswith(path)
237 except IndexError:
238 result = False
239 if not result:
240 logger.debug('_find failed: %r %r', path, self.loader.prefix)
241 else:
242 logger.debug('_find worked: %r %r', path, self.loader.prefix)
243 return result
244
245 def get_cache_info(self, resource):
246 prefix = self.loader.archive
247 path = resource.path[1 + len(prefix):]
248 return prefix, path
249
250 def get_bytes(self, resource):
251 return self.loader.get_data(resource.path)
252
253 def get_stream(self, resource):
254 return io.BytesIO(self.get_bytes(resource))
255
256 def get_size(self, resource):
257 path = resource.path[self.prefix_len:]
258 return self._files[path][3]
259
260 def get_resources(self, resource):
261 path = resource.path[self.prefix_len:]
262 if path and path[-1] != os.sep:
263 path += os.sep
264 plen = len(path)
265 result = set()
266 i = bisect.bisect(self.index, path)
267 while i < len(self.index):
268 if not self.index[i].startswith(path):
269 break
270 s = self.index[i][plen:]
271 result.add(s.split(os.sep, 1)[0]) # only immediate children
272 i += 1
273 return result
274
275 def _is_directory(self, path):
276 path = path[self.prefix_len:]
277 if path and path[-1] != os.sep:
278 path += os.sep
279 i = bisect.bisect(self.index, path)
280 try:
281 result = self.index[i].startswith(path)
282 except IndexError:
283 result = False
284 return result
285
286 _finder_registry = {
287 type(None): ResourceFinder,
288 zipimport.zipimporter: ZipResourceFinder
289 }
290
291 try:
292 # In Python 3.6, _frozen_importlib -> _frozen_importlib_external
293 try:
294 import _frozen_importlib_external as _fi
295 except ImportError:
296 import _frozen_importlib as _fi
297 _finder_registry[_fi.SourceFileLoader] = ResourceFinder
298 _finder_registry[_fi.FileFinder] = ResourceFinder
299 del _fi
300 except (ImportError, AttributeError):
301 pass
302
303
304 def register_finder(loader, finder_maker):
305 _finder_registry[type(loader)] = finder_maker
306
307 _finder_cache = {}
308
309
310 def finder(package):
311 """
312 Return a resource finder for a package.
313 :param package: The name of the package.
314 :return: A :class:`ResourceFinder` instance for the package.
315 """
316 if package in _finder_cache:
317 result = _finder_cache[package]
318 else:
319 if package not in sys.modules:
320 __import__(package)
321 module = sys.modules[package]
322 path = getattr(module, '__path__', None)
323 if path is None:
324 raise DistlibException('You cannot get a finder for a module, '
325 'only for a package')
326 loader = getattr(module, '__loader__', None)
327 finder_maker = _finder_registry.get(type(loader))
328 if finder_maker is None:
329 raise DistlibException('Unable to locate finder for %r' % package)
330 result = finder_maker(module)
331 _finder_cache[package] = result
332 return result
333
334
335 _dummy_module = types.ModuleType(str('__dummy__'))
336
337
338 def finder_for_path(path):
339 """
340 Return a resource finder for a path, which should represent a container.
341
342 :param path: The path.
343 :return: A :class:`ResourceFinder` instance for the path.
344 """
345 result = None
346 # calls any path hooks, gets importer into cache
347 pkgutil.get_importer(path)
348 loader = sys.path_importer_cache.get(path)
349 finder = _finder_registry.get(type(loader))
350 if finder:
351 module = _dummy_module
352 module.__file__ = os.path.join(path, '')
353 module.__loader__ = loader
354 result = finder(module)
355 return result