Commit b6bb3314 authored by PJ Eby's avatar PJ Eby

Added ``ExtractionError`` and ``ResourceManager.extraction_error()`` so that

cache permission problems get a more user-friendly explanation of the
problem, and so that programs can catch and handle extraction errors if they
need to.

--HG--
branch : setuptools
extra : convert_revision : svn%3A6015fed2-1504-0410-9fe1-9d1591cc4771/sandbox/trunk/setuptools%4043426
parent 281fa7d0
...@@ -57,7 +57,8 @@ __all__ = [ ...@@ -57,7 +57,8 @@ __all__ = [
# Exceptions # Exceptions
'ResolutionError','VersionConflict','DistributionNotFound','UnknownExtra', 'ResolutionError','VersionConflict','DistributionNotFound','UnknownExtra',
'ExtractionError',
# Parsing functions and string utilities # Parsing functions and string utilities
'parse_requirements', 'parse_version', 'safe_name', 'safe_version', 'parse_requirements', 'parse_version', 'safe_name', 'safe_version',
'get_platform', 'compatible_platforms', 'yield_lines', 'split_sections', 'get_platform', 'compatible_platforms', 'yield_lines', 'split_sections',
...@@ -79,7 +80,6 @@ __all__ = [ ...@@ -79,7 +80,6 @@ __all__ = [
# Deprecated/backward compatibility only # Deprecated/backward compatibility only
'run_main', 'AvailableDistributions', 'run_main', 'AvailableDistributions',
] ]
class ResolutionError(Exception): class ResolutionError(Exception):
"""Abstract base for dependency resolution errors""" """Abstract base for dependency resolution errors"""
...@@ -759,20 +759,20 @@ class Environment(object): ...@@ -759,20 +759,20 @@ class Environment(object):
AvailableDistributions = Environment # XXX backward compatibility AvailableDistributions = Environment # XXX backward compatibility
class ExtractionError(RuntimeError):
"""An error occurred extracting a resource
The following attributes are available from instances of this exception:
manager
The resource manager that raised this exception
cache_path
The base directory for resource extraction
original_error
The exception instance that caused extraction to fail
"""
...@@ -818,6 +818,47 @@ class ResourceManager: ...@@ -818,6 +818,47 @@ class ResourceManager:
resource_name resource_name
) )
def extraction_error(self):
"""Give an error message for problems extracting file(s)"""
old_exc = sys.exc_info()[1]
cache_path = self.extraction_path or get_default_cache()
err = ExtractionError("""Can't extract file(s) to egg cache
The following error occurred while trying to extract file(s) to the Python egg
cache:
%s
The Python egg cache directory is currently set to:
%s
Perhaps your account does not have write access to this directory? You can
change the cache directory by setting the PYTHON_EGG_CACHE environment
variable to point to an accessible directory.
""" % (old_exc, cache_path)
)
err.manager = self
err.cache_path = cache_path
err.original_error = old_exc
raise err
def get_cache_path(self, archive_name, names=()): def get_cache_path(self, archive_name, names=()):
"""Return absolute location in cache for `archive_name` and `names` """Return absolute location in cache for `archive_name` and `names`
...@@ -833,7 +874,11 @@ class ResourceManager: ...@@ -833,7 +874,11 @@ class ResourceManager:
""" """
extract_path = self.extraction_path or get_default_cache() extract_path = self.extraction_path or get_default_cache()
target_path = os.path.join(extract_path, archive_name+'-tmp', *names) target_path = os.path.join(extract_path, archive_name+'-tmp', *names)
ensure_directory(target_path) try:
ensure_directory(target_path)
except:
self.extraction_error()
self.cached_files[target_path] = 1 self.cached_files[target_path] = 1
return target_path return target_path
...@@ -855,10 +900,6 @@ class ResourceManager: ...@@ -855,10 +900,6 @@ class ResourceManager:
# XXX # XXX
def set_extraction_path(self, path): def set_extraction_path(self, path):
"""Set the base path where resources will be extracted to, if needed. """Set the base path where resources will be extracted to, if needed.
...@@ -1188,12 +1229,14 @@ class ZipProvider(EggProvider): ...@@ -1188,12 +1229,14 @@ class ZipProvider(EggProvider):
return self._extract_resource(manager, zip_path) return self._extract_resource(manager, zip_path)
def _extract_resource(self, manager, zip_path): def _extract_resource(self, manager, zip_path):
if zip_path in self._index(): if zip_path in self._index():
for name in self._index()[zip_path]: for name in self._index()[zip_path]:
last = self._extract_resource( last = self._extract_resource(
manager, os.path.join(zip_path, name) manager, os.path.join(zip_path, name)
) )
return os.path.dirname(last) # return the extracted directory name return os.path.dirname(last) # return the extracted directory name
zip_stat = self.zipinfo[zip_path] zip_stat = self.zipinfo[zip_path]
t,d,size = zip_stat[5], zip_stat[6], zip_stat[3] t,d,size = zip_stat[5], zip_stat[6], zip_stat[3]
date_time = ( date_time = (
...@@ -1201,32 +1244,45 @@ class ZipProvider(EggProvider): ...@@ -1201,32 +1244,45 @@ class ZipProvider(EggProvider):
(t&0xFFFF)>>11, (t>>5)&0x3F, (t&0x1F) * 2, 0, 0, -1 # hms, etc. (t&0xFFFF)>>11, (t>>5)&0x3F, (t&0x1F) * 2, 0, 0, -1 # hms, etc.
) )
timestamp = time.mktime(date_time) timestamp = time.mktime(date_time)
real_path = manager.get_cache_path(self.egg_name, self._parts(zip_path))
if os.path.isfile(real_path): try:
stat = os.stat(real_path) real_path = manager.get_cache_path(
if stat.st_size==size and stat.st_mtime==timestamp: self.egg_name, self._parts(zip_path)
# size and stamp match, don't bother extracting )
return real_path
outf, tmpnam = _mkstemp(".$extract", dir=os.path.dirname(real_path))
os.write(outf, self.loader.get_data(zip_path))
os.close(outf)
utime(tmpnam, (timestamp,timestamp))
manager.postprocess(tmpnam, real_path)
try: rename(tmpnam, real_path)
except os.error:
if os.path.isfile(real_path): if os.path.isfile(real_path):
stat = os.stat(real_path) stat = os.stat(real_path)
if stat.st_size==size and stat.st_mtime==timestamp: if stat.st_size==size and stat.st_mtime==timestamp:
# size and stamp match, somebody did it just ahead of us # size and stamp match, don't bother extracting
# so we're done
return real_path
elif os.name=='nt': # Windows, delete old file and retry
unlink(real_path)
rename(tmpnam, real_path)
return real_path return real_path
raise
return real_path
outf, tmpnam = _mkstemp(".$extract", dir=os.path.dirname(real_path))
os.write(outf, self.loader.get_data(zip_path))
os.close(outf)
utime(tmpnam, (timestamp,timestamp))
manager.postprocess(tmpnam, real_path)
try:
rename(tmpnam, real_path)
except os.error:
if os.path.isfile(real_path):
stat = os.stat(real_path)
if stat.st_size==size and stat.st_mtime==timestamp:
# size and stamp match, somebody did it just ahead of
# us, so we're done
return real_path
elif os.name=='nt': # Windows, del old file and retry
unlink(real_path)
rename(tmpnam, real_path)
return real_path
raise
except os.error:
manager.extraction_error() # report a user-friendly error
return real_path
def _get_eager_resources(self): def _get_eager_resources(self):
if self.eagers is None: if self.eagers is None:
...@@ -1264,11 +1320,6 @@ class ZipProvider(EggProvider): ...@@ -1264,11 +1320,6 @@ class ZipProvider(EggProvider):
def _listdir(self,fspath): def _listdir(self,fspath):
return list(self._index().get(self._zipinfo_name(fspath), ())) return list(self._index().get(self._zipinfo_name(fspath), ()))
def _eager_to_zip(self,resource_name): def _eager_to_zip(self,resource_name):
return self._zipinfo_name(self._fn(self.egg_root,resource_name)) return self._zipinfo_name(self._fn(self.egg_root,resource_name))
...@@ -1278,6 +1329,28 @@ class ZipProvider(EggProvider): ...@@ -1278,6 +1329,28 @@ class ZipProvider(EggProvider):
register_loader_type(zipimport.zipimporter, ZipProvider) register_loader_type(zipimport.zipimporter, ZipProvider)
class FileMetadata(EmptyProvider): class FileMetadata(EmptyProvider):
"""Metadata handler for standalone PKG-INFO files """Metadata handler for standalone PKG-INFO files
...@@ -1310,6 +1383,15 @@ class FileMetadata(EmptyProvider): ...@@ -1310,6 +1383,15 @@ class FileMetadata(EmptyProvider):
class PathMetadata(DefaultProvider): class PathMetadata(DefaultProvider):
"""Metadata provider for egg directories """Metadata provider for egg directories
......
...@@ -1194,6 +1194,14 @@ you must therefore have an explicit ``ResourceManager`` instance to use them. ...@@ -1194,6 +1194,14 @@ you must therefore have an explicit ``ResourceManager`` instance to use them.
obtain an extraction location, and only for names they intend to obtain an extraction location, and only for names they intend to
extract, as it tracks the generated names for possible cleanup later. extract, as it tracks the generated names for possible cleanup later.
``extraction_error()``
Raise an ``ExtractionError`` describing the active exception as interfering
with the extraction process. You should call this if you encounter any
OS errors extracting the file to the cache path; it will format the
operating system exception for you, and add other information to the
``ExtractionError`` instance that may be needed by programs that want to
wrap or handle extraction errors themselves.
``postprocess(tempname, filename)`` ``postprocess(tempname, filename)``
Perform any platform-specific postprocessing of `tempname`. Perform any platform-specific postprocessing of `tempname`.
Resource providers should call this method ONLY after successfully Resource providers should call this method ONLY after successfully
...@@ -1277,6 +1285,8 @@ occur when processing requests to locate and activate packages:: ...@@ -1277,6 +1285,8 @@ occur when processing requests to locate and activate packages::
VersionConflict VersionConflict
UnknownExtra UnknownExtra
ExtractionError
``ResolutionError`` ``ResolutionError``
This class is used as a base class for the other three exceptions, so that This class is used as a base class for the other three exceptions, so that
you can catch all of them with a single "except" clause. It is also raised you can catch all of them with a single "except" clause. It is also raised
...@@ -1294,6 +1304,19 @@ occur when processing requests to locate and activate packages:: ...@@ -1294,6 +1304,19 @@ occur when processing requests to locate and activate packages::
One of the "extras" requested was not recognized by the distribution it One of the "extras" requested was not recognized by the distribution it
was requested from. was requested from.
``ExtractionError``
A problem occurred extracting a resource to the Python Egg cache. The
following attributes are available on instances of this exception:
manager
The resource manager that raised this exception
cache_path
The base directory for resource extraction
original_error
The exception instance that caused extraction to fail
Supporting Custom Importers Supporting Custom Importers
=========================== ===========================
...@@ -1629,6 +1652,12 @@ File/Path Utilities ...@@ -1629,6 +1652,12 @@ File/Path Utilities
Release Notes/Change History Release Notes/Change History
---------------------------- ----------------------------
0.6a11
* Added ``ExtractionError`` and ``ResourceManager.extraction_error()`` so that
cache permission problems get a more user-friendly explanation of the
problem, and so that programs can catch and handle extraction errors if they
need to.
0.6a10 0.6a10
* Added the ``extras`` attribute to ``Distribution``, the ``find_plugins()`` * Added the ``extras`` attribute to ``Distribution``, the ``find_plugins()``
method to ``WorkingSet``, and the ``__add__()`` and ``__iadd__()`` methods method to ``WorkingSet``, and the ``__add__()`` and ``__iadd__()`` methods
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment