Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2013 The LUCI Authors. All rights reserved. | 1 # Copyright 2013 The LUCI Authors. All rights reserved. |
| 2 # Use of this source code is governed under the Apache License, Version 2.0 | 2 # Use of this source code is governed under the Apache License, Version 2.0 |
| 3 # that can be found in the LICENSE file. | 3 # that can be found in the LICENSE file. |
| 4 | 4 |
| 5 """Utilities to work with importable python zip packages.""" | 5 """Utilities to work with importable python zip packages.""" |
| 6 | 6 |
| 7 import atexit | 7 import atexit |
| 8 import collections | 8 import collections |
| 9 import cStringIO as StringIO | 9 import cStringIO as StringIO |
| 10 import hashlib | 10 import hashlib |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 26 # Ignore precompiled python files since they depend on python version and we | 26 # Ignore precompiled python files since they depend on python version and we |
| 27 # don't want zip package to be version-depended. | 27 # don't want zip package to be version-depended. |
| 28 r'.*\.pyc$', | 28 r'.*\.pyc$', |
| 29 r'.*\.pyo$', | 29 r'.*\.pyo$', |
| 30 ) | 30 ) |
| 31 | 31 |
| 32 | 32 |
| 33 # Temporary files extracted by extract_resource. Removed in atexit hook. | 33 # Temporary files extracted by extract_resource. Removed in atexit hook. |
| 34 _extracted_files = [] | 34 _extracted_files = [] |
| 35 _extracted_files_lock = threading.Lock() | 35 _extracted_files_lock = threading.Lock() |
| 36 | 36 |
|
M-A Ruel
2016/06/23 13:09:54
add another empty line above.
mithro
2016/06/24 03:22:42
Done.
| |
| 37 # Patch zipimport.zipimporter hook to accept unicode strings | |
| 38 def zipimporter_unicode(archivepath): | |
| 39 if isinstance(archivepath, unicode): | |
| 40 archivepath = archivepath.encode(sys.getfilesystemencoding()) | |
| 41 return zipimport.zipimporter(archivepath) | |
| 42 | |
| 43 for i, hook in enumerate(sys.path_hooks): | |
| 44 if hook is zipimport.zipimporter: | |
| 45 sys.path_hooks[i] = zipimporter_unicode | |
|
M-A Ruel
2016/06/23 13:09:54
sadness
mithro
2016/06/24 03:22:42
Yeah :(
It works properly in Python 3...
| |
| 46 | |
| 37 | 47 |
| 38 class ZipPackageError(RuntimeError): | 48 class ZipPackageError(RuntimeError): |
| 39 """Failed to create a zip package.""" | 49 """Failed to create a zip package.""" |
| 40 | 50 |
| 41 | 51 |
| 42 class ZipPackage(object): | 52 class ZipPackage(object): |
| 43 """A set of files that can be zipped to file on disk or into memory buffer. | 53 """A set of files that can be zipped to file on disk or into memory buffer. |
| 44 | 54 |
| 45 Usage: | 55 Usage: |
| 46 package = ZipPackage(root) | 56 package = ZipPackage(root) |
| (...skipping 184 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 231 def get_main_script_path(): | 241 def get_main_script_path(): |
| 232 """If running from zip returns path to a zip file, else path to __main__. | 242 """If running from zip returns path to a zip file, else path to __main__. |
| 233 | 243 |
| 234 Basically returns path to a file passed to python for execution | 244 Basically returns path to a file passed to python for execution |
| 235 as in 'python <main_script>' considering a case of executable zip package. | 245 as in 'python <main_script>' considering a case of executable zip package. |
| 236 | 246 |
| 237 Returns path relative to a current directory of when process was started. | 247 Returns path relative to a current directory of when process was started. |
| 238 """ | 248 """ |
| 239 # If running from interactive console __file__ is not defined. | 249 # If running from interactive console __file__ is not defined. |
| 240 main = sys.modules['__main__'] | 250 main = sys.modules['__main__'] |
| 241 return get_module_zip_archive(main) or getattr(main, '__file__', None) | 251 path = get_module_zip_archive(main) |
| 252 if path: | |
| 253 return path | |
| 254 | |
| 255 path = getattr(main, '__file__', None) | |
| 256 if path: | |
| 257 return path.decode(sys.getfilesystemencoding()) | |
| 242 | 258 |
| 243 | 259 |
| 244 def _write_temp_data(name, data, temp_dir): | 260 def _write_temp_data(name, data, temp_dir): |
| 245 """Writes content-addressed file in `temp_dir` if relevant.""" | 261 """Writes content-addressed file in `temp_dir` if relevant.""" |
| 246 filename = '%s-%s' % (hashlib.sha1(data).hexdigest(), name) | 262 filename = '%s-%s' % (hashlib.sha1(data).hexdigest(), name) |
| 247 filepath = os.path.join(temp_dir, filename) | 263 filepath = os.path.join(temp_dir, filename) |
| 248 if os.path.isfile(filepath): | 264 if os.path.isfile(filepath): |
| 249 with open(filepath, 'rb') as f: | 265 with open(filepath, 'rb') as f: |
| 250 if f.read() == data: | 266 if f.read() == data: |
| 251 # It already exists. | 267 # It already exists. |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 272 package: is a python module object that represents a package. | 288 package: is a python module object that represents a package. |
| 273 resource: should be a relative filename, using '/'' as the path separator. | 289 resource: should be a relative filename, using '/'' as the path separator. |
| 274 temp_dir: if set, it will extra the file in this directory with the filename | 290 temp_dir: if set, it will extra the file in this directory with the filename |
| 275 being the hash of the content. Otherwise, it uses tempfile.mkstemp(). | 291 being the hash of the content. Otherwise, it uses tempfile.mkstemp(). |
| 276 | 292 |
| 277 Raises ValueError if no such resource. | 293 Raises ValueError if no such resource. |
| 278 """ | 294 """ |
| 279 # For regular non-zip packages just construct an absolute path. | 295 # For regular non-zip packages just construct an absolute path. |
| 280 if not is_zipped_module(package): | 296 if not is_zipped_module(package): |
| 281 # Package's __file__ attribute is always an absolute path. | 297 # Package's __file__ attribute is always an absolute path. |
| 282 path = os.path.join(os.path.dirname(package.__file__), | 298 ppath = package.__file__.decode(sys.getfilesystemencoding()) |
| 299 path = os.path.join(os.path.dirname(ppath), | |
| 283 resource.replace('/', os.sep)) | 300 resource.replace('/', os.sep)) |
| 284 if not os.path.exists(path): | 301 if not os.path.exists(path): |
| 285 raise ValueError('No such resource in %s: %s' % (package, resource)) | 302 raise ValueError('No such resource in %s: %s' % (package, resource)) |
| 286 return path | 303 return path |
| 287 | 304 |
| 288 # For zipped packages extract the resource into a temp file. | 305 # For zipped packages extract the resource into a temp file. |
| 289 data = pkgutil.get_data(package.__name__, resource) | 306 data = pkgutil.get_data(package.__name__, resource) |
| 290 if data is None: | 307 if data is None: |
| 291 raise ValueError('No such resource in zipped %s: %s' % (package, resource)) | 308 raise ValueError('No such resource in zipped %s: %s' % (package, resource)) |
| 292 | 309 |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 336 h = hashlib.sha1() | 353 h = hashlib.sha1() |
| 337 with zipfile.ZipFile(get_main_script_path(), 'r') as z: | 354 with zipfile.ZipFile(get_main_script_path(), 'r') as z: |
| 338 for name in sorted(z.namelist()): | 355 for name in sorted(z.namelist()): |
| 339 with z.open(name) as f: | 356 with z.open(name) as f: |
| 340 h.update(str(len(name))) | 357 h.update(str(len(name))) |
| 341 h.update(name) | 358 h.update(name) |
| 342 content = f.read() | 359 content = f.read() |
| 343 h.update(str(len(content))) | 360 h.update(str(len(content))) |
| 344 h.update(content) | 361 h.update(content) |
| 345 return h.hexdigest() | 362 return h.hexdigest() |
| OLD | NEW |