diff options
Diffstat (limited to 'setuptools')
| -rw-r--r-- | setuptools/__init__.py | 40 | ||||
| -rwxr-xr-x | setuptools/command/easy_install.py | 545 | ||||
| -rwxr-xr-x | setuptools/command/sdist.py | 39 | ||||
| -rwxr-xr-x | setuptools/command/upload.py | 13 | ||||
| -rw-r--r-- | setuptools/compat.py | 11 | ||||
| -rw-r--r-- | setuptools/dist.py | 168 | ||||
| -rwxr-xr-x | setuptools/package_index.py | 157 | ||||
| -rw-r--r-- | setuptools/py26compat.py | 19 | ||||
| -rw-r--r-- | setuptools/script template (dev).py | 8 | ||||
| -rw-r--r-- | setuptools/ssl_support.py | 6 | ||||
| -rw-r--r-- | setuptools/tests/script-with-bom.py | 3 | ||||
| -rw-r--r-- | setuptools/tests/test_easy_install.py | 4 | ||||
| -rw-r--r-- | setuptools/tests/test_packageindex.py | 15 | ||||
| -rw-r--r-- | setuptools/tests/test_resources.py | 133 | ||||
| -rw-r--r-- | setuptools/tests/test_sandbox.py | 13 | ||||
| -rw-r--r-- | setuptools/version.py | 1 |
16 files changed, 490 insertions, 685 deletions
diff --git a/setuptools/__init__.py b/setuptools/__init__.py index 18dd363d..fc9b7b93 100644 --- a/setuptools/__init__.py +++ b/setuptools/__init__.py @@ -1,19 +1,24 @@ """Extensions to the 'distutils' for large or complex distributions""" -from setuptools.extension import Extension, Library -from setuptools.dist import Distribution, Feature, _get_unpatched -import distutils.core, setuptools.command -from setuptools.depends import Require -from distutils.core import Command as _Command -from distutils.util import convert_path + import os import sys +import distutils.core +import distutils.filelist +from distutils.core import Command as _Command +from distutils.util import convert_path + +import setuptools.version +from setuptools.extension import Extension +from setuptools.dist import Distribution, Feature, _get_unpatched +from setuptools.depends import Require -__version__ = '0.9.7' __all__ = [ 'setup', 'Distribution', 'Feature', 'Command', 'Extension', 'Require', 'find_packages' ] +__version__ = setuptools.version.__version__ + bootstrap_install_from = None # If we run 2to3 on .py files, should we also convert docstrings? @@ -37,10 +42,14 @@ def find_packages(where='.', exclude=()): where,prefix = stack.pop(0) for name in os.listdir(where): fn = os.path.join(where,name) - if ('.' not in name and os.path.isdir(fn) and - os.path.isfile(os.path.join(fn,'__init__.py')) - ): - out.append(prefix+name); stack.append((fn,prefix+name+'.')) + looks_like_package = ( + '.' not in name + and os.path.isdir(fn) + and os.path.isfile(os.path.join(fn, '__init__.py')) + ) + if looks_like_package: + out.append(prefix+name) + stack.append((fn, prefix+name+'.')) for pat in list(exclude)+['ez_setup']: from fnmatch import fnmatchcase out = [item for item in out if not fnmatchcase(item,pat)] @@ -67,7 +76,6 @@ class Command(_Command): setattr(cmd,k,v) # update command with keywords return cmd -import distutils.core distutils.core.Command = Command # we can't patch distutils.cmd, alas def findall(dir = os.curdir): @@ -83,12 +91,8 @@ def findall(dir = os.curdir): all_files.extend(filter(os.path.isfile, files)) return all_files -import distutils.filelist distutils.filelist.findall = findall # fix findall bug in distutils. # sys.dont_write_bytecode was introduced in Python 2.6. -if ((hasattr(sys, "dont_write_bytecode") and sys.dont_write_bytecode) or - (not hasattr(sys, "dont_write_bytecode") and os.environ.get("PYTHONDONTWRITEBYTECODE"))): - _dont_write_bytecode = True -else: - _dont_write_bytecode = False +_dont_write_bytecode = getattr(sys, 'dont_write_bytecode', + bool(os.environ.get("PYTHONDONTWRITEBYTECODE"))) diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index 3194644e..6ce19fa4 100755 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -1,5 +1,6 @@ #!python -"""\ + +""" Easy Install ------------ @@ -10,6 +11,7 @@ file, or visit the `EasyInstall home page`__. __ https://pythonhosted.org/setuptools/easy_install.html """ + import sys import os import zipimport @@ -20,11 +22,16 @@ import re import stat import random import platform +import textwrap +import warnings +import site +import struct from glob import glob +from distutils import log, dir_util + import pkg_resources from setuptools import Command, _dont_write_bytecode from setuptools.sandbox import run_setup -from distutils import log, dir_util try: # Python 2.7 or >=3.2 from sysconfig import get_config_vars, get_path @@ -51,12 +58,12 @@ from setuptools.package_index import URL_SCHEME from setuptools.command import bdist_egg, egg_info from setuptools.compat import (iteritems, maxsize, xrange, basestring, unicode, reraise) -from pkg_resources import yield_lines, normalize_path, resource_string, \ - ensure_directory, get_distribution, find_distributions, \ - Environment, Requirement, Distribution, \ - PathMetadata, EggMetadata, WorkingSet, \ - DistributionNotFound, VersionConflict, \ - DEVELOP_DIST +from pkg_resources import ( + yield_lines, normalize_path, resource_string, ensure_directory, + get_distribution, find_distributions, Environment, Requirement, + Distribution, PathMetadata, EggMetadata, WorkingSet, DistributionNotFound, + VersionConflict, DEVELOP_DIST, +) if '__VENV_LAUNCHER__' in os.environ: sys_executable = os.environ['__VENV_LAUNCHER__'] @@ -68,22 +75,19 @@ __all__ = [ 'main', 'get_exe_prefixes', ] -import site HAS_USER_SITE = not sys.version < "2.6" and site.ENABLE_USER_SITE -import struct def is_64bit(): return struct.calcsize("P") == 8 -def samefile(p1,p2): - if hasattr(os.path,'samefile') and ( - os.path.exists(p1) and os.path.exists(p2) - ): - return os.path.samefile(p1,p2) - return ( - os.path.normpath(os.path.normcase(p1)) == - os.path.normpath(os.path.normcase(p2)) - ) +def samefile(p1, p2): + both_exist = os.path.exists(p1) and os.path.exists(p2) + use_samefile = hasattr(os.path, 'samefile') and both_exist + if use_samefile: + return os.path.samefile(p1, p2) + norm_p1 = os.path.normpath(os.path.normcase(p1)) + norm_p2 = os.path.normpath(os.path.normcase(p2)) + return norm_p1 == norm_p2 if sys.version_info <= (3,): def _to_ascii(s): @@ -120,38 +124,35 @@ class easy_install(Command): ("always-copy", "a", "Copy all needed packages to install dir"), ("index-url=", "i", "base URL of Python Package Index"), ("find-links=", "f", "additional URL(s) to search for packages"), - ("delete-conflicting", "D", "no longer needed; don't use this"), - ("ignore-conflicts-at-my-risk", None, - "no longer needed; don't use this"), ("build-directory=", "b", "download/extract/build in DIR; keep the results"), ('optimize=', 'O', - "also compile with optimization: -O1 for \"python -O\", " - "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), + "also compile with optimization: -O1 for \"python -O\", " + "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"), ('record=', None, - "filename in which to record list of installed files"), + "filename in which to record list of installed files"), ('always-unzip', 'Z', "don't install as a zipfile, no matter what"), ('site-dirs=','S',"list of directories where .pth files work"), ('editable', 'e', "Install specified packages in editable form"), ('no-deps', 'N', "don't install dependencies"), ('allow-hosts=', 'H', "pattern(s) that hostnames must match"), - ('local-snapshots-ok', 'l', "allow building eggs from local checkouts"), + ('local-snapshots-ok', 'l', + "allow building eggs from local checkouts"), ('version', None, "print version information and exit"), ('no-find-links', None, - "Don't load find-links defined in packages being installed") + "Don't load find-links defined in packages being installed") ] boolean_options = [ 'zip-ok', 'multi-version', 'exclude-scripts', 'upgrade', 'always-copy', - 'delete-conflicting', 'ignore-conflicts-at-my-risk', 'editable', + 'editable', 'no-deps', 'local-snapshots-ok', 'version' ] if HAS_USER_SITE: - user_options.append(('user', None, - "install in user site-package '%s'" % site.USER_SITE)) + help_msg = "install in user site-package '%s'" % site.USER_SITE + user_options.append(('user', None, help_msg)) boolean_options.append('user') - negative_opt = {'always-unzip': 'zip-ok'} create_index = PackageIndex @@ -192,8 +193,6 @@ class easy_install(Command): # Options not specifiable via command line self.package_index = None self.pth_file = self.always_copy_from = None - self.delete_conflicting = None - self.ignore_conflicts_at_my_risk = None self.site_dirs = None self.installed_projects = {} self.sitepy_installed = False @@ -226,19 +225,20 @@ class easy_install(Command): py_version = sys.version.split()[0] prefix, exec_prefix = get_config_vars('prefix', 'exec_prefix') - self.config_vars = {'dist_name': self.distribution.get_name(), - 'dist_version': self.distribution.get_version(), - 'dist_fullname': self.distribution.get_fullname(), - 'py_version': py_version, - 'py_version_short': py_version[0:3], - 'py_version_nodot': py_version[0] + py_version[2], - 'sys_prefix': prefix, - 'prefix': prefix, - 'sys_exec_prefix': exec_prefix, - 'exec_prefix': exec_prefix, - # Only python 3.2+ has abiflags - 'abiflags': getattr(sys, 'abiflags', ''), - } + self.config_vars = { + 'dist_name': self.distribution.get_name(), + 'dist_version': self.distribution.get_version(), + 'dist_fullname': self.distribution.get_fullname(), + 'py_version': py_version, + 'py_version_short': py_version[0:3], + 'py_version_nodot': py_version[0] + py_version[2], + 'sys_prefix': prefix, + 'prefix': prefix, + 'sys_exec_prefix': exec_prefix, + 'exec_prefix': exec_prefix, + # Only python 3.2+ has abiflags + 'abiflags': getattr(sys, 'abiflags', ''), + } if HAS_USER_SITE: self.config_vars['userbase'] = self.install_userbase @@ -336,11 +336,6 @@ class easy_install(Command): except ValueError: raise DistutilsOptionError("--optimize must be 0, 1, or 2") - if self.delete_conflicting and self.ignore_conflicts_at_my_risk: - raise DistutilsOptionError( - "Can't use both --delete-conflicting and " - "--ignore-conflicts-at-my-risk at the same time" - ) if self.editable and not self.build_directory: raise DistutilsArgError( "Must specify a build directory (-b) when using --editable" @@ -351,7 +346,6 @@ class easy_install(Command): self.outputs = [] - def _expand_attrs(self, attrs): for attr in attrs: val = getattr(self, attr) @@ -406,12 +400,7 @@ class easy_install(Command): return os.path.join(self.install_dir, "test-easy-install-%s" % pid) def warn_deprecated_options(self): - if self.delete_conflicting or self.ignore_conflicts_at_my_risk: - log.warn( - "Note: The -D, --delete-conflicting and" - " --ignore-conflicts-at-my-risk no longer have any purpose" - " and should not be used." - ) + pass def check_site_dir(self): """Verify that self.install_dir is .pth-capable dir, if needed""" @@ -456,7 +445,7 @@ class easy_install(Command): self.install_dir = instdir def cant_write_to_target(self): - msg = """can't create or remove files in install directory + template = """can't create or remove files in install directory The following error occurred while trying to add or remove files in the installation directory: @@ -467,7 +456,8 @@ The installation directory you specified (via --install-dir, --prefix, or the distutils default setting) was: %s -""" % (sys.exc_info()[1], self.install_dir,) +""" + msg = template % (sys.exc_info()[1], self.install_dir,) if not os.path.exists(self.install_dir): msg += """ @@ -493,9 +483,6 @@ Please make the appropriate changes for your system and try again. """ raise DistutilsError(msg) - - - def check_pth_processing(self): """Empirically verify whether .pth files are supported in inst. dir""" instdir = self.install_dir @@ -514,7 +501,8 @@ Please make the appropriate changes for your system and try again. else: try: f.write("import os; f = open(%r, 'w'); f.write('OK'); f.close()\n" % (ok_file,)) - f.close(); f=None + f.close() + f=None executable = sys.executable if os.name=='nt': dirname,basename = os.path.split(executable) @@ -533,9 +521,12 @@ Please make the appropriate changes for your system and try again. ) return True finally: - if f: f.close() - if os.path.exists(ok_file): os.unlink(ok_file) - if os.path.exists(pth_file): os.unlink(pth_file) + if f: + f.close() + if os.path.exists(ok_file): + os.unlink(ok_file) + if os.path.exists(pth_file): + os.unlink(pth_file) if not self.multi_version: log.warn("TEST FAILED: %s does NOT support .pth files", instdir) return False @@ -580,11 +571,6 @@ Please make the appropriate changes for your system and try again. (spec.key, self.build_directory) ) - - - - - def easy_install(self, spec, deps=False): tmpdir = tempfile.mkdtemp(prefix="easy_install-") download = None @@ -654,7 +640,7 @@ Please make the appropriate changes for your system and try again. for dist in dists: self.process_distribution(spec, dist, deps) else: - dists = [self.check_conflicts(self.egg_distribution(download))] + dists = [self.egg_distribution(download)] self.process_distribution(spec, dists[0], deps, "Using") if spec is not None: @@ -662,8 +648,6 @@ Please make the appropriate changes for your system and try again. if dist in spec: return dist - - def select_scheme(self, name): """Sets the install directories by applying the install schemes.""" # it's the caller's problem if they supply a bad name! @@ -673,9 +657,6 @@ Please make the appropriate changes for your system and try again. if getattr(self, attrname) is None: setattr(self, attrname, scheme[key]) - - - def process_distribution(self, requirement, dist, deps=True, *info): self.update_pth(dist) self.package_index.add(dist) @@ -684,7 +665,7 @@ Please make the appropriate changes for your system and try again. self.installed_projects[dist.key] = dist log.info(self.installation_report(requirement, dist, *info)) if (dist.has_metadata('dependency_links.txt') and - not self.no_find_links): + not self.no_find_links): self.package_index.add_find_links( dist.get_metadata_lines('dependency_links.txt') ) @@ -735,10 +716,8 @@ Please make the appropriate changes for your system and try again. def maybe_move(self, spec, dist_filename, setup_base): dst = os.path.join(self.build_directory, spec.key) if os.path.exists(dst): - log.warn( - "%r already exists in %s; build directory %s will not be kept", - spec.key, self.build_directory, setup_base - ) + msg = "%r already exists in %s; build directory %s will not be kept" + log.warn(msg, spec.key, self.build_directory, setup_base) return setup_base if os.path.isdir(dist_filename): setup_base = dist_filename @@ -751,7 +730,8 @@ Please make the appropriate changes for your system and try again. if os.path.isdir(dist_filename): # if the only thing there is a directory, move it instead setup_base = dist_filename - ensure_directory(dst); shutil.move(setup_base, dst) + ensure_directory(dst) + shutil.move(setup_base, dst) return dst def install_wrapper_scripts(self, dist): @@ -759,8 +739,6 @@ Please make the appropriate changes for your system and try again. for args in get_script_args(dist): self.write_script(*args) - - def install_script(self, dist, script_name, script_text, dev_path=None): """Generate a legacy script wrapper and install it""" spec = str(dist.as_requirement()) @@ -799,14 +777,13 @@ Please make the appropriate changes for your system and try again. mask = current_umask() if not self.dry_run: ensure_directory(target) + if os.path.exists(target): + os.unlink(target) f = open(target,"w"+mode) f.write(contents) f.close() chmod(target, 0x1FF-mask) # 0777 - - - def install_eggs(self, spec, dist_filename, tmpdir): # .egg dirs or files are already built, so just return them if dist_filename.lower().endswith('.egg'): @@ -822,8 +799,7 @@ Please make the appropriate changes for your system and try again. setup_base = os.path.abspath(dist_filename) if (setup_base.startswith(tmpdir) # something we downloaded - and self.build_directory and spec is not None - ): + and self.build_directory and spec is not None): setup_base = self.maybe_move(spec, dist_filename, setup_base) # Find the setup.py file @@ -862,7 +838,6 @@ Please make the appropriate changes for your system and try again. ensure_directory(destination) dist = self.egg_distribution(egg_path) - self.check_conflicts(dist) if not samefile(egg_path, destination): if os.path.isdir(destination) and not os.path.islink(destination): dir_util.remove_tree(destination, dry_run=self.dry_run) @@ -897,18 +872,19 @@ Please make the appropriate changes for your system and try again. "%s is not a valid distutils Windows .exe" % dist_filename ) # Create a dummy distribution object until we build the real distro - dist = Distribution(None, + dist = Distribution( + None, project_name=cfg.get('metadata','name'), - version=cfg.get('metadata','version'), platform=get_platform() + version=cfg.get('metadata','version'), platform=get_platform(), ) # Convert the .exe to an unpacked egg egg_path = dist.location = os.path.join(tmpdir, dist.egg_name()+'.egg') - egg_tmp = egg_path+'.tmp' - egg_info = os.path.join(egg_tmp, 'EGG-INFO') - pkg_inf = os.path.join(egg_info, 'PKG-INFO') + egg_tmp = egg_path + '.tmp' + _egg_info = os.path.join(egg_tmp, 'EGG-INFO') + pkg_inf = os.path.join(_egg_info, 'PKG-INFO') ensure_directory(pkg_inf) # make sure EGG-INFO dir exists - dist._provider = PathMetadata(egg_tmp, egg_info) # XXX + dist._provider = PathMetadata(egg_tmp, _egg_info) # XXX self.exe_to_egg(dist_filename, egg_tmp) # Write EGG-INFO/PKG-INFO @@ -919,7 +895,7 @@ Please make the appropriate changes for your system and try again. if k != 'target_version': f.write('%s: %s\n' % (k.replace('_','-').title(), v)) f.close() - script_dir = os.path.join(egg_info,'scripts') + script_dir = os.path.join(_egg_info,'scripts') self.delete_blockers( # delete entry-point scripts to avoid duping [os.path.join(script_dir,args[0]) for args in get_script_args(dist)] ) @@ -965,7 +941,8 @@ Please make the appropriate changes for your system and try again. resource = parts[-1] parts[-1] = bdist_egg.strip_module(parts[-1])+'.py' pyfile = os.path.join(egg_tmp, *parts) - to_compile.append(pyfile); stubs.append(pyfile) + to_compile.append(pyfile) + stubs.append(pyfile) bdist_egg.write_stub(resource, pyfile) self.byte_compile(to_compile) # compile .py's bdist_egg.write_safety_flag(os.path.join(egg_tmp,'EGG-INFO'), @@ -979,82 +956,6 @@ Please make the appropriate changes for your system and try again. f.write('\n'.join(locals()[name])+'\n') f.close() - def check_conflicts(self, dist): - """Verify that there are no conflicting "old-style" packages""" - - return dist # XXX temporarily disable until new strategy is stable - from imp import find_module, get_suffixes - from glob import glob - - blockers = [] - names = dict.fromkeys(dist._get_metadata('top_level.txt')) # XXX private attr - - exts = {'.pyc':1, '.pyo':1} # get_suffixes() might leave one out - for ext,mode,typ in get_suffixes(): - exts[ext] = 1 - - for path,files in expand_paths([self.install_dir]+self.all_site_dirs): - for filename in files: - base,ext = os.path.splitext(filename) - if base in names: - if not ext: - # no extension, check for package - try: - f, filename, descr = find_module(base, [path]) - except ImportError: - continue - else: - if f: f.close() - if filename not in blockers: - blockers.append(filename) - elif ext in exts and base!='site': # XXX ugh - blockers.append(os.path.join(path,filename)) - if blockers: - self.found_conflicts(dist, blockers) - - return dist - - def found_conflicts(self, dist, blockers): - if self.delete_conflicting: - log.warn("Attempting to delete conflicting packages:") - return self.delete_blockers(blockers) - - msg = """\ -------------------------------------------------------------------------- -CONFLICT WARNING: - -The following modules or packages have the same names as modules or -packages being installed, and will be *before* the installed packages in -Python's search path. You MUST remove all of the relevant files and -directories before you will be able to use the package(s) you are -installing: - - %s - -""" % '\n '.join(blockers) - - if self.ignore_conflicts_at_my_risk: - msg += """\ -(Note: you can run EasyInstall on '%s' with the ---delete-conflicting option to attempt deletion of the above files -and/or directories.) -""" % dist.project_name - else: - msg += """\ -Note: you can attempt this installation again with EasyInstall, and use -either the --delete-conflicting (-D) option or the ---ignore-conflicts-at-my-risk option, to either delete the above files -and directories, or to ignore the conflicts, respectively. Note that if -you ignore the conflicts, the installed package(s) may not work. -""" - msg += """\ -------------------------------------------------------------------------- -""" - sys.stderr.write(msg) - sys.stderr.flush() - if not self.ignore_conflicts_at_my_risk: - raise DistutilsError("Installation aborted due to conflicts") - def installation_report(self, req, dist, what="Installed"): """Helpful installation message for display to package users""" msg = "\n%(what)s %(eggloc)s%(extras)s" @@ -1164,8 +1065,7 @@ See the setuptools documentation for the "develop" command for more info. cfg_filename = os.path.join(base, 'setup.cfg') setopt.edit_config(cfg_filename, settings) - - def update_pth(self,dist): + def update_pth(self, dist): if self.pth_file is None: return @@ -1207,9 +1107,10 @@ See the setuptools documentation for the "develop" command for more info. return dst # only unpack-and-compile skips files for dry run def unpack_and_compile(self, egg_path, destination): - to_compile = []; to_chmod = [] + to_compile = [] + to_chmod = [] - def pf(src,dst): + def pf(src, dst): if dst.endswith('.py') and not src.startswith('EGG-INFO/'): to_compile.append(dst) elif dst.endswith('.dll') or dst.endswith('.so'): @@ -1243,16 +1144,8 @@ See the setuptools documentation for the "develop" command for more info. finally: log.set_verbosity(self.verbose) # restore original verbosity - - - - - - - - def no_default_version_msg(self): - return """bad install directory or PYTHONPATH + template = """bad install directory or PYTHONPATH You are attempting to install a package to a directory that is not on PYTHONPATH and which Python does not read ".pth" files from. The @@ -1279,18 +1172,8 @@ Here are some of your options for correcting the problem: https://pythonhosted.org/setuptools/easy_install.html#custom-installation-locations -Please make the appropriate changes for your system and try again.""" % ( - self.install_dir, os.environ.get('PYTHONPATH','') - ) - - - - - - - - - +Please make the appropriate changes for your system and try again.""" + return template % (self.install_dir, os.environ.get('PYTHONPATH','')) def install_site_py(self): """Make sure there's a site.py in the target dir, if needed""" @@ -1328,9 +1211,6 @@ Please make the appropriate changes for your system and try again.""" % ( self.sitepy_installed = True - - - def create_home_path(self): """Create directories under ~.""" if not self.user: @@ -1341,22 +1221,16 @@ Please make the appropriate changes for your system and try again.""" % ( self.debug_print("os.makedirs('%s', 0700)" % path) os.makedirs(path, 0x1C0) # 0700 - - - - - - INSTALL_SCHEMES = dict( posix = dict( install_dir = '$base/lib/python$py_version_short/site-packages', - script_dir = '$base/bin', + script_dir = '$base/bin', ), ) DEFAULT_SCHEME = dict( install_dir = '$base/Lib/site-packages', - script_dir = '$base/Scripts', + script_dir = '$base/Scripts', ) def _expand(self, *attrs): @@ -1380,14 +1254,6 @@ Please make the appropriate changes for your system and try again.""" % ( val = os.path.expanduser(val) setattr(self, attr, val) - - - - - - - - def get_site_dirs(): # return a list of 'site' dirs sitedirs = [_f for _f in os.environ.get('PYTHONPATH', @@ -1522,12 +1388,6 @@ def extract_wininst_cfg(dist_filename): f.close() - - - - - - def get_exe_prefixes(exe_filename): """Get exe->egg path translations for a given .exe file""" @@ -1561,7 +1421,8 @@ def get_exe_prefixes(exe_filename): finally: z.close() prefixes = [(x.lower(),y) for x, y in prefixes] - prefixes.sort(); prefixes.reverse() + prefixes.sort() + prefixes.reverse() return prefixes @@ -1582,7 +1443,8 @@ class PthDistributions(Environment): self.filename = filename self.sitedirs = list(map(normalize_path, sitedirs)) self.basedir = normalize_path(os.path.dirname(self.filename)) - self._load(); Environment.__init__(self, [], None, None) + self._load() + Environment.__init__(self, [], None, None) for path in yield_lines(self.paths): list(map(self.add, find_distributions(path, True))) @@ -1637,7 +1499,8 @@ class PthDistributions(Environment): if os.path.islink(self.filename): os.unlink(self.filename) f = open(self.filename,'wt') - f.write(data); f.close() + f.write(data) + f.close() elif os.path.exists(self.filename): log.debug("Deleting empty %s", self.filename) @@ -1645,22 +1508,22 @@ class PthDistributions(Environment): self.dirty = False - def add(self,dist): + def add(self, dist): """Add `dist` to the distribution map""" if (dist.location not in self.paths and ( dist.location not in self.sitedirs or - dist.location == os.getcwd() #account for '.' being in PYTHONPATH + dist.location == os.getcwd() # account for '.' being in PYTHONPATH )): self.paths.append(dist.location) self.dirty = True - Environment.add(self,dist) + Environment.add(self, dist) - def remove(self,dist): + def remove(self, dist): """Remove `dist` from the distribution map""" while dist.location in self.paths: - self.paths.remove(dist.location); self.dirty = True - Environment.remove(self,dist) - + self.paths.remove(dist.location) + self.dirty = True + Environment.remove(self, dist) def make_relative(self,path): npath, last = os.path.split(normalize_path(path)) @@ -1782,14 +1645,6 @@ def nt_quote_arg(arg): return ''.join(result) - - - - - - - - def is_python_script(script_text, filename): """Is this text, as a whole, a Python script? (as opposed to shell/bat/etc. """ @@ -1828,66 +1683,140 @@ def fix_jython_executable(executable, options): # shebang line interpreter) if options: # Can't apply the workaround, leave it broken - log.warn("WARNING: Unable to adapt shebang line for Jython," - " the following script is NOT executable\n" - " see http://bugs.jython.org/issue1112 for" - " more information.") + log.warn( + "WARNING: Unable to adapt shebang line for Jython," + " the following script is NOT executable\n" + " see http://bugs.jython.org/issue1112 for" + " more information.") else: return '/usr/bin/env %s' % executable return executable -def get_script_args(dist, executable=sys_executable, wininst=False): - """Yield write_script() argument tuples for a distribution's entrypoints""" - spec = str(dist.as_requirement()) - header = get_script_header("", executable, wininst) - for group in 'console_scripts', 'gui_scripts': - for name, ep in dist.get_entry_map(group).items(): - script_text = ( - "# EASY-INSTALL-ENTRY-SCRIPT: %(spec)r,%(group)r,%(name)r\n" - "__requires__ = %(spec)r\n" - "import sys\n" - "from pkg_resources import load_entry_point\n" - "\n" - "if __name__ == '__main__':" - "\n" - " sys.exit(\n" - " load_entry_point(%(spec)r, %(group)r, %(name)r)()\n" - " )\n" - ) % locals() - if sys.platform=='win32' or wininst: - # On Windows/wininst, add a .py extension and an .exe launcher - if group=='gui_scripts': - launcher_type = 'gui' - ext = '-script.pyw' - old = ['.pyw'] - new_header = re.sub('(?i)python.exe','pythonw.exe',header) - else: - launcher_type = 'cli' - ext = '-script.py' - old = ['.py','.pyc','.pyo'] - new_header = re.sub('(?i)pythonw.exe','python.exe',header) - if os.path.exists(new_header[2:-1].strip('"')) or sys.platform!='win32': - hdr = new_header - else: - hdr = header - yield (name+ext, hdr+script_text, 't', [name+x for x in old]) - yield ( - name+'.exe', get_win_launcher(launcher_type), - 'b' # write in binary mode - ) - if not is_64bit(): - # install a manifest for the launcher to prevent Windows - # from detecting it as an installer (which it will for - # launchers like easy_install.exe). Consider only - # adding a manifest for launchers detected as installers. - # See Distribute #143 for details. - m_name = name + '.exe.manifest' - yield (m_name, load_launcher_manifest(name), 't') - else: - # On other platforms, we assume the right thing to do is to - # just write the stub with no extension. - yield (name, header+script_text) +class ScriptWriter(object): + """ + Encapsulates behavior around writing entry point scripts for console and + gui apps. + """ + + template = textwrap.dedent(""" + # EASY-INSTALL-ENTRY-SCRIPT: %(spec)r,%(group)r,%(name)r + __requires__ = %(spec)r + import sys + from pkg_resources import load_entry_point + + if __name__ == '__main__': + sys.exit( + load_entry_point(%(spec)r, %(group)r, %(name)r)() + ) + """).lstrip() + + @classmethod + def get_script_args(cls, dist, executable=sys_executable, wininst=False): + """ + Yield write_script() argument tuples for a distribution's entrypoints + """ + gen_class = cls.get_writer(wininst) + spec = str(dist.as_requirement()) + header = get_script_header("", executable, wininst) + for type_ in 'console', 'gui': + group = type_ + '_scripts' + for name, ep in dist.get_entry_map(group).items(): + script_text = gen_class.template % locals() + for res in gen_class._get_script_args(type_, name, header, + script_text): + yield res + + @classmethod + def get_writer(cls, force_windows): + if force_windows or sys.platform=='win32': + return WindowsScriptWriter.get_writer() + return cls + + @classmethod + def _get_script_args(cls, type_, name, header, script_text): + # Simply write the stub with no extension. + yield (name, header+script_text) + + +class WindowsScriptWriter(ScriptWriter): + @classmethod + def get_writer(cls): + """ + Get a script writer suitable for Windows + """ + writer_lookup = dict( + executable=WindowsExecutableLauncherWriter, + natural=cls, + ) + # for compatibility, use the executable launcher by default + launcher = os.environ.get('SETUPTOOLS_LAUNCHER', 'executable') + return writer_lookup[launcher] + + @classmethod + def _get_script_args(cls, type_, name, header, script_text): + "For Windows, add a .py extension" + ext = dict(console='.pya', gui='.pyw')[type_] + if ext not in os.environ['PATHEXT'].lower().split(';'): + warnings.warn("%s not listed in PATHEXT; scripts will not be " + "recognized as executables." % ext, UserWarning) + old = ['.pya', '.py', '-script.py', '.pyc', '.pyo', '.pyw', '.exe'] + old.remove(ext) + header = cls._adjust_header(type_, header) + blockers = [name+x for x in old] + yield name+ext, header+script_text, 't', blockers + + @staticmethod + def _adjust_header(type_, orig_header): + """ + Make sure 'pythonw' is used for gui and and 'python' is used for + console (regardless of what sys.executable is). + """ + pattern = 'pythonw.exe' + repl = 'python.exe' + if type_ == 'gui': + pattern, repl = repl, pattern + pattern_ob = re.compile(re.escape(pattern), re.IGNORECASE) + new_header = pattern_ob.sub(string=orig_header, repl=repl) + clean_header = new_header[2:-1].strip('"') + if sys.platform == 'win32' and not os.path.exists(clean_header): + # the adjusted version doesn't exist, so return the original + return orig_header + return new_header + + +class WindowsExecutableLauncherWriter(WindowsScriptWriter): + @classmethod + def _get_script_args(cls, type_, name, header, script_text): + """ + For Windows, add a .py extension and an .exe launcher + """ + if type_=='gui': + launcher_type = 'gui' + ext = '-script.pyw' + old = ['.pyw'] + else: + launcher_type = 'cli' + ext = '-script.py' + old = ['.py','.pyc','.pyo'] + hdr = cls._adjust_header(type_, header) + blockers = [name+x for x in old] + yield (name+ext, hdr+script_text, 't', blockers) + yield ( + name+'.exe', get_win_launcher(launcher_type), + 'b' # write in binary mode + ) + if not is_64bit(): + # install a manifest for the launcher to prevent Windows + # from detecting it as an installer (which it will for + # launchers like easy_install.exe). Consider only + # adding a manifest for launchers detected as installers. + # See Distribute #143 for details. + m_name = name + '.exe.manifest' + yield (m_name, load_launcher_manifest(name), 't') + +# for backward-compatibility +get_script_args = ScriptWriter.get_script_args def get_win_launcher(type): """ @@ -1955,8 +1884,11 @@ def current_umask(): def bootstrap(): # This function is called when setuptools*.egg is run using /bin/sh - import setuptools; argv0 = os.path.dirname(setuptools.__path__[0]) - sys.argv[0] = argv0; sys.argv.append(argv0); main() + import setuptools + argv0 = os.path.dirname(setuptools.__path__[0]) + sys.argv[0] = argv0 + sys.argv.append(argv0) + main() def main(argv=None, **kw): from setuptools import setup @@ -1968,9 +1900,10 @@ usage: %(script)s [options] requirement_or_url ... or: %(script)s --help """ - def gen_usage (script_name): - script = os.path.basename(script_name) - return USAGE % vars() + def gen_usage(script_name): + return USAGE % dict( + script=os.path.basename(script_name), + ) def with_ei_usage(f): old_gen_usage = distutils.core.gen_usage @@ -1996,7 +1929,3 @@ usage: %(script)s [options] requirement_or_url ... distclass=DistributionWithoutHelpCommands, **kw ) ) - - - - diff --git a/setuptools/command/sdist.py b/setuptools/command/sdist.py index e1112ff9..6249e75c 100755 --- a/setuptools/command/sdist.py +++ b/setuptools/command/sdist.py @@ -1,9 +1,12 @@ +import os +import re +import sys +from glob import glob + +import pkg_resources from distutils.command.sdist import sdist as _sdist from distutils.util import convert_path from distutils import log -from glob import glob -import os, re, sys, pkg_resources -from glob import glob from setuptools import svn_utils READMES = ('README', 'README.rst', 'README.txt') @@ -37,7 +40,6 @@ class re_finder(object): #was an re_finder for calling unescape path = postproc(path) yield svn_utils.joinpath(dirname,path) - def __call__(self, dirname=''): path = svn_utils.joinpath(dirname, self.path) @@ -64,16 +66,6 @@ finders = [ - - - - - - - - - - class sdist(_sdist): """Smart sdist that finds anything supported by revision control""" @@ -128,11 +120,12 @@ class sdist(_sdist): # Beginning with Python 2.7.2, 3.1.4, and 3.2.1, this leaky file handle # has been fixed, so only override the method if we're using an earlier # Python. - if ( - sys.version_info < (2,7,2) - or (3,0) <= sys.version_info < (3,1,4) - or (3,2) <= sys.version_info < (3,2,1) - ): + has_leaky_handle = ( + sys.version_info < (2,7,2) + or (3,0) <= sys.version_info < (3,1,4) + or (3,2) <= sys.version_info < (3,2,1) + ) + if has_leaky_handle: read_template = __read_template_hack def add_defaults(self): @@ -197,7 +190,6 @@ class sdist(_sdist): "standard file not found: should have one of " +', '.join(READMES) ) - def make_release_tree(self, base_dir, files): _sdist.make_release_tree(self, base_dir, files) @@ -244,10 +236,3 @@ class sdist(_sdist): continue self.filelist.append(line) manifest.close() - - - - - - -# diff --git a/setuptools/command/upload.py b/setuptools/command/upload.py index 02d955ed..a6eff385 100755 --- a/setuptools/command/upload.py +++ b/setuptools/command/upload.py @@ -2,10 +2,10 @@ Implements the Distutils 'upload' subcommand (upload package to PyPI).""" -from distutils.errors import * +from distutils import errors +from distutils import log from distutils.core import Command from distutils.spawn import spawn -from distutils import log try: from hashlib import md5 except ImportError: @@ -45,7 +45,7 @@ class upload(Command): def finalize_options(self): if self.identity and not self.sign: - raise DistutilsOptionError( + raise errors.DistutilsOptionError( "Must use --sign for --identity to have meaning" ) if 'HOME' in os.environ: @@ -68,7 +68,7 @@ class upload(Command): def run(self): if not self.distribution.dist_files: - raise DistutilsOptionError("No dist file created in earlier command") + raise errors.DistutilsOptionError("No dist file created in earlier command") for command, pyversion, filename in self.distribution.dist_files: self.upload_file(command, pyversion, filename) @@ -119,10 +119,10 @@ class upload(Command): boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' sep_boundary = '\n--' + boundary end_boundary = sep_boundary + '--' - body = StringIO.StringIO() + body = StringIO() for key, value in data.items(): # handle multiple entries for the same name - if type(value) != type([]): + if not isinstance(value, list): value = [value] for value in value: if type(value) is tuple: @@ -158,7 +158,6 @@ class upload(Command): raise AssertionError("unsupported schema " + schema) data = '' - loglevel = log.INFO try: http.connect() http.putrequest("POST", url) diff --git a/setuptools/compat.py b/setuptools/compat.py index 94fe23e9..bbc98d66 100644 --- a/setuptools/compat.py +++ b/setuptools/compat.py @@ -27,10 +27,10 @@ if sys.version_info[0] < 3: unichr = unichr unicode = unicode bytes = str - from urllib import url2pathname + from urllib import url2pathname, splittag import urllib2 from urllib2 import urlopen, HTTPError, URLError, unquote, splituser - from urlparse import urlparse, urlunparse, urljoin + from urlparse import urlparse, urlunparse, urljoin, urlsplit, urlunsplit xrange = xrange filterfalse = itertools.ifilterfalse @@ -74,7 +74,10 @@ else: from urllib.error import HTTPError, URLError import urllib.request as urllib2 from urllib.request import urlopen, url2pathname - from urllib.parse import urlparse, urlunparse, unquote, splituser, urljoin + from urllib.parse import ( + urlparse, urlunparse, unquote, splituser, urljoin, urlsplit, + urlunsplit, splittag, + ) xrange = range filterfalse = itertools.filterfalse @@ -83,7 +86,7 @@ else: globs = globals() if locs is None: locs = globs - f = open(fn) + f = open(fn, 'rb') try: source = f.read() finally: diff --git a/setuptools/dist.py b/setuptools/dist.py index 01889215..c5b02f99 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -1,17 +1,19 @@ __all__ = ['Distribution'] import re +import os import sys +import warnings +import distutils.log +import distutils.core +import distutils.cmd from distutils.core import Distribution as _Distribution +from distutils.errors import (DistutilsOptionError, DistutilsPlatformError, + DistutilsSetupError) + from setuptools.depends import Require -from setuptools.command.install import install -from setuptools.command.sdist import sdist -from setuptools.command.install_lib import install_lib from setuptools.compat import numeric_types, basestring -from distutils.errors import DistutilsOptionError, DistutilsPlatformError -from distutils.errors import DistutilsSetupError -import setuptools, pkg_resources, distutils.core, distutils.dist, distutils.cmd -import os, distutils.log +import pkg_resources def _get_unpatched(cls): """Protect against re-patching the distutils if reloaded @@ -132,38 +134,6 @@ def check_packages(dist, attr, value): "WARNING: %r not a valid package name; please use only" ".-separated package names in setup.py", pkgname ) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - class Distribution(_Distribution): @@ -194,7 +164,8 @@ class Distribution(_Distribution): EasyInstall and requests one of your extras, the corresponding additional requirements will be installed if needed. - 'features' -- a dictionary mapping option names to 'setuptools.Feature' + 'features' **deprecated** -- a dictionary mapping option names to + 'setuptools.Feature' objects. Features are a portion of the distribution that can be included or excluded based on user options, inter-feature dependencies, and availability on the current system. Excluded features are omitted @@ -248,10 +219,13 @@ class Distribution(_Distribution): dist._version = pkg_resources.safe_version(str(attrs['version'])) self._patched_dist = dist - def __init__ (self, attrs=None): + def __init__(self, attrs=None): have_package_data = hasattr(self, "package_data") if not have_package_data: self.package_data = {} + _attrs_dict = attrs or {} + if 'features' in _attrs_dict or 'require_features' in _attrs_dict: + Feature.warn_deprecated() self.require_features = [] self.features = {} self.dist_files = [] @@ -362,23 +336,6 @@ class Distribution(_Distribution): self.global_options = self.feature_options = go + self.global_options self.negative_opt = self.feature_negopt = no - - - - - - - - - - - - - - - - - def _finalize_features(self): """Add/remove features and resolve dependencies between them""" @@ -396,7 +353,6 @@ class Distribution(_Distribution): feature.exclude_from(self) self._set_feature(name,0) - def get_command_class(self, command): """Pluggable version of get_command_class()""" if command in self.cmdclass: @@ -416,10 +372,6 @@ class Distribution(_Distribution): self.cmdclass[ep.name] = cmdclass return _Distribution.print_commands(self) - - - - def _set_feature(self,name,status): """Set feature's inclusion status""" setattr(self,self._feature_attrname(name),status) @@ -434,8 +386,8 @@ class Distribution(_Distribution): if self.feature_is_included(name)==0: descr = self.features[name].description raise DistutilsOptionError( - descr + " is required, but was excluded or is not available" - ) + descr + " is required, but was excluded or is not available" + ) self.features[name].include_in(self) self._set_feature(name,1) @@ -483,7 +435,6 @@ class Distribution(_Distribution): if p.name != package and not p.name.startswith(pfx) ] - def has_contents_for(self,package): """Return true if 'exclude_package(package)' would do something""" @@ -493,15 +444,6 @@ class Distribution(_Distribution): if p==package or p.startswith(pfx): return True - - - - - - - - - def _exclude_misc(self,name,value): """Handle 'exclude()' for list/tuple attrs without a special handler""" if not isinstance(value,sequence): @@ -573,17 +515,6 @@ class Distribution(_Distribution): ) list(map(self.exclude_package, packages)) - - - - - - - - - - - def _parse_command_opts(self, parser, args): # Remove --with-X/--without-X options when processing command args self.global_options = self.__class__.global_options @@ -610,21 +541,6 @@ class Distribution(_Distribution): return nargs - - - - - - - - - - - - - - - def get_cmdline_options(self): """Return a '{cmd: {opt:val}}' map of all command-line options @@ -665,7 +581,6 @@ class Distribution(_Distribution): return d - def iter_distribution_names(self): """Yield all packages, modules, and extension names in distribution""" @@ -684,7 +599,6 @@ class Distribution(_Distribution): name = name[:-6] yield name - def handle_display_options(self, option_order): """If there were any non-global "display-only" options (--help-commands or the metadata display options) on the command @@ -726,26 +640,14 @@ for module in distutils.dist, distutils.core, distutils.cmd: module.Distribution = Distribution - - - - - - - - - - - - - - - - - - class Feature: - """A subset of the distribution that can be excluded if unneeded/wanted + """ + **deprecated** -- The `Feature` facility was never completely implemented + or supported, `has reported issues + <https://bitbucket.org/pypa/setuptools/issue/58>`_ and will be removed in + a future version. + + A subset of the distribution that can be excluded if unneeded/wanted Features are created using these keyword arguments: @@ -794,9 +696,19 @@ class Feature: Aside from the methods, the only feature attributes that distributions look at are 'description' and 'optional'. """ + + @staticmethod + def warn_deprecated(): + warnings.warn( + "Features are deprecated and will be removed in a future " + "version. See http://bitbucket.org/pypa/setuptools/65.", + DeprecationWarning, + stacklevel=3, + ) + def __init__(self, description, standard=False, available=True, - optional=True, require_features=(), remove=(), **extras - ): + optional=True, require_features=(), remove=(), **extras): + self.warn_deprecated() self.description = description self.standard = standard @@ -847,8 +759,6 @@ class Feature: for f in self.require_features: dist.include_feature(f) - - def exclude_from(self,dist): """Ensure feature is excluded from distribution @@ -865,8 +775,6 @@ class Feature: for item in self.remove: dist.exclude_package(item) - - def validate(self,dist): """Verify that feature makes sense in context of distribution @@ -886,7 +794,3 @@ class Feature: " doesn't contain any packages or modules under %s" % (self.description, item, item) ) - - - - diff --git a/setuptools/package_index.py b/setuptools/package_index.py index 47f00c00..4c9e40a7 100755 --- a/setuptools/package_index.py +++ b/setuptools/package_index.py @@ -1,19 +1,28 @@ """PyPI and direct package downloading""" -import sys, os.path, re, shutil, random, socket -import itertools +import sys +import os +import re +import shutil +import socket import base64 + +from pkg_resources import ( + CHECKOUT_DIST, Distribution, BINARY_DIST, normalize_path, SOURCE_DIST, + require, Environment, find_distributions, safe_name, safe_version, + to_filename, Requirement, DEVELOP_DIST, +) from setuptools import ssl_support -from pkg_resources import * from distutils import log from distutils.errors import DistutilsError from setuptools.compat import (urllib2, httplib, StringIO, HTTPError, urlparse, urlunparse, unquote, splituser, url2pathname, name2codepoint, - unichr, urljoin) + unichr, urljoin, urlsplit, urlunsplit) from setuptools.compat import filterfalse from fnmatch import translate from setuptools.py24compat import hashlib from setuptools.py24compat import wraps +from setuptools.py26compat import strip_fragment from setuptools.py27compat import get_all_headers EGG_FRAGMENT = re.compile(r'^egg=([-A-Za-z0-9_.]+)$') @@ -105,9 +114,10 @@ def distros_for_filename(filename, metadata=None): ) -def interpret_distro_name(location, basename, metadata, - py_version=None, precedence=SOURCE_DIST, platform=None -): +def interpret_distro_name( + location, basename, metadata, py_version=None, precedence=SOURCE_DIST, + platform=None + ): """Generate alternative interpretations of a source distro name Note: if `location` is a filesystem filename, you should call @@ -222,6 +232,7 @@ class HashChecker(ContentChecker): ) def __init__(self, hash_name, expected): + self.hash_name = hash_name self.hash = hashlib.new(hash_name) self.expected = expected @@ -242,32 +253,18 @@ class HashChecker(ContentChecker): def is_valid(self): return self.hash.hexdigest() == self.expected - def _get_hash_name(self): - """ - Python 2.4 implementation of MD5 doesn't supply a .name attribute - so provide that name. - - When Python 2.4 is no longer required, replace invocations of this - method with simply 'self.hash.name'. - """ - try: - return self.hash.name - except AttributeError: - if 'md5' in str(type(self.hash)): - return 'md5' - raise - def report(self, reporter, template): - msg = template % self._get_hash_name() + msg = template % self.hash_name return reporter(msg) class PackageIndex(Environment): """A distribution index that scans web pages for download URLs""" - def __init__(self, index_url="https://pypi.python.org/simple", hosts=('*',), - ca_bundle=None, verify_ssl=True, *args, **kw - ): + def __init__( + self, index_url="https://pypi.python.org/simple", hosts=('*',), + ca_bundle=None, verify_ssl=True, *args, **kw + ): Environment.__init__(self,*args,**kw) self.index_url = index_url + "/"[:not index_url.endswith('/')] self.scanned_urls = {} @@ -347,7 +344,8 @@ class PackageIndex(Environment): s = URL_SCHEME(url) if (s and s.group(1).lower()=='file') or self.allows(urlparse(url)[1]): return True - msg = "\nLink to % s ***BLOCKED*** by --allow-hosts\n" + msg = ("\nNote: Bypassing %s (disallowed host; see " + "http://bit.ly/1dg9ijs for details).\n") if fatal: raise DistutilsError(msg % url) else: @@ -388,7 +386,7 @@ class PackageIndex(Environment): # process an index page into the package-page index for match in HREF.finditer(page): try: - scan( urljoin(url, htmldecode(match.group(1))) ) + scan(urljoin(url, htmldecode(match.group(1)))) except ValueError: pass @@ -411,8 +409,6 @@ class PackageIndex(Environment): else: return "" # no sense double-scanning non-package pages - - def need_version_info(self, url): self.scan_all( "Page at %s links to .py file(s) without version info; an index " @@ -443,17 +439,14 @@ class PackageIndex(Environment): self.scan_url(url) def obtain(self, requirement, installer=None): - self.prescan(); self.find_packages(requirement) + self.prescan() + self.find_packages(requirement) for dist in self[requirement.key]: if dist in requirement: return dist self.debug("%s does not match %s", requirement, dist) return super(PackageIndex, self).obtain(requirement,installer) - - - - def check_hash(self, checker, filename, tfp): """ checker is a ContentChecker @@ -539,11 +532,10 @@ class PackageIndex(Environment): ) return getattr(self.fetch_distribution(spec, tmpdir),'location',None) - - def fetch_distribution(self, - requirement, tmpdir, force_scan=False, source=False, develop_ok=False, - local_index=None - ): + def fetch_distribution( + self, requirement, tmpdir, force_scan=False, source=False, + develop_ok=False, local_index=None + ): """Obtain a distribution suitable for fulfilling `requirement` `requirement` must be a ``pkg_resources.Requirement`` instance. @@ -581,8 +573,6 @@ class PackageIndex(Environment): if dist in req and (dist.precedence<=SOURCE_DIST or not source): return dist - - if force_scan: self.prescan() self.find_packages(requirement) @@ -609,7 +599,6 @@ class PackageIndex(Environment): self.info("Best match: %s", dist) return dist.clone(location=self.download(dist.location, tmpdir)) - def fetch(self, requirement, tmpdir, force_scan=False, source=False): """Obtain a file suitable for fulfilling `requirement` @@ -623,10 +612,10 @@ class PackageIndex(Environment): return dist.location return None - def gen_setup(self, filename, fragment, tmpdir): match = EGG_FRAGMENT.match(fragment) - dists = match and [d for d in + dists = match and [ + d for d in interpret_distro_name(filename, match.group(1), None) if d.version ] or [] @@ -672,7 +661,7 @@ class PackageIndex(Environment): fp, tfp, info = None, None, None try: checker = HashChecker.from_url(url) - fp = self.open_url(url) + fp = self.open_url(strip_fragment(url)) if isinstance(fp, HTTPError): raise DistutilsError( "Can't download %s: %s %s" % (url, fp.code,fp.msg) @@ -732,9 +721,11 @@ class PackageIndex(Environment): if warning: self.warn(warning, v.line) else: - raise DistutilsError('%s returned a bad status line. ' - 'The server might be down, %s' % \ - (url, v.line)) + raise DistutilsError( + '%s returned a bad status line. The server might be ' + 'down, %s' % + (url, v.line) + ) except httplib.HTTPException: v = sys.exc_info()[1] if warning: @@ -775,7 +766,6 @@ class PackageIndex(Environment): def scan_url(self, url): self.process_url(url, True) - def _attempt_download(self, url, filename): headers = self._download_to(url, filename) if 'html' in headers.get('content-type','').lower(): @@ -798,21 +788,6 @@ class PackageIndex(Environment): os.unlink(filename) raise DistutilsError("Unexpected HTML page found at "+url) - - - - - - - - - - - - - - - def _download_svn(self, url, filename): url = url.split('#',1)[0] # remove any fragment for svn's sake creds = '' @@ -833,7 +808,8 @@ class PackageIndex(Environment): os.system("svn checkout%s -q %s %s" % (creds, url, filename)) return filename - def _vcs_split_rev_from_url(self, url, pop_prefix=False): + @staticmethod + def _vcs_split_rev_from_url(url, pop_prefix=False): scheme, netloc, path, query, frag = urlsplit(url) scheme = scheme.split('+', 1)[-1] @@ -891,18 +867,6 @@ class PackageIndex(Environment): def warn(self, msg, *args): log.warn(msg, *args) - - - - - - - - - - - - # This pattern matches a character entity reference (a decimal numeric # references, a hexadecimal numeric reference, or a named reference). entity_sub = re.compile(r'&(#(\d+|x[\da-fA-F]+)|[\w.:-]+);?').sub @@ -927,20 +891,6 @@ def htmldecode(text): """Decode HTML entities in the given text.""" return entity_sub(decode_entity, text) - - - - - - - - - - - - - - def socket_timeout(timeout=15): def _socket_timeout(func): def _socket_timeout(*args, **kwargs): @@ -1009,15 +959,6 @@ def open_with_auth(url, opener=urllib2.urlopen): open_with_auth = socket_timeout(_SOCKET_TIMEOUT)(open_with_auth) - - - - - - - - - def fix_sf_url(url): return url # backward compatibility @@ -1047,17 +988,3 @@ def local_open(url): return HTTPError(url, status, message, {'content-type':'text/html'}, StringIO(body)) - - - - - - - - - - - - - -# this line is a kludge to keep the trailing blank lines for pje's editor diff --git a/setuptools/py26compat.py b/setuptools/py26compat.py new file mode 100644 index 00000000..738b0cc4 --- /dev/null +++ b/setuptools/py26compat.py @@ -0,0 +1,19 @@ +""" +Compatibility Support for Python 2.6 and earlier +""" + +import sys + +from setuptools.compat import splittag + +def strip_fragment(url): + """ + In `Python 8280 <http://bugs.python.org/issue8280>`_, Python 2.7 and + later was patched to disregard the fragment when making URL requests. + Do the same for Python 2.6 and earlier. + """ + url, fragment = splittag(url) + return url + +if sys.version_info >= (2,7): + strip_fragment = lambda x: x diff --git a/setuptools/script template (dev).py b/setuptools/script template (dev).py index 901790e7..b3fe209e 100644 --- a/setuptools/script template (dev).py +++ b/setuptools/script template (dev).py @@ -1,9 +1,11 @@ # EASY-INSTALL-DEV-SCRIPT: %(spec)r,%(script_name)r __requires__ = """%(spec)r""" -from pkg_resources import require; require("""%(spec)r""") +import sys +from pkg_resources import require +require("""%(spec)r""") del require __file__ = """%(dev_path)r""" -try: +if sys.version_info < (3, 0): execfile(__file__) -except NameError: +else: exec(compile(open(__file__).read(), __file__, 'exec')) diff --git a/setuptools/ssl_support.py b/setuptools/ssl_support.py index f8a780a9..90359b2c 100644 --- a/setuptools/ssl_support.py +++ b/setuptools/ssl_support.py @@ -194,6 +194,12 @@ class VerifyingHTTPSConn(HTTPSConnection): sock = create_connection( (self.host, self.port), getattr(self,'source_address',None) ) + + # Handle the socket if a (proxy) tunnel is present + if hasattr(self, '_tunnel') and getattr(self, '_tunnel_host', None): + self.sock = sock + self._tunnel() + self.sock = ssl.wrap_socket( sock, cert_reqs=ssl.CERT_REQUIRED, ca_certs=self.ca_bundle ) diff --git a/setuptools/tests/script-with-bom.py b/setuptools/tests/script-with-bom.py new file mode 100644 index 00000000..22dee0d2 --- /dev/null +++ b/setuptools/tests/script-with-bom.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +result = 'passed' diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py index 2732bb3e..189e3d55 100644 --- a/setuptools/tests/test_easy_install.py +++ b/setuptools/tests/test_easy_install.py @@ -14,7 +14,7 @@ import distutils.core from setuptools.compat import StringIO, BytesIO, next, urlparse from setuptools.sandbox import run_setup, SandboxViolation -from setuptools.command.easy_install import easy_install, fix_jython_executable, get_script_args +from setuptools.command.easy_install import easy_install, fix_jython_executable, get_script_args, nt_quote_arg from setuptools.command.easy_install import PthDistributions from setuptools.command import easy_install as easy_install_pkg from setuptools.dist import Distribution @@ -52,7 +52,7 @@ if __name__ == '__main__': sys.exit( load_entry_point('spec', 'console_scripts', 'name')() ) -""" % fix_jython_executable(sys.executable, "") +""" % nt_quote_arg(fix_jython_executable(sys.executable, "")) SETUP_PY = """\ from setuptools import setup diff --git a/setuptools/tests/test_packageindex.py b/setuptools/tests/test_packageindex.py index 3791914a..08969b7e 100644 --- a/setuptools/tests/test_packageindex.py +++ b/setuptools/tests/test_packageindex.py @@ -142,6 +142,15 @@ class TestPackageIndex(unittest.TestCase): self.assertEqual(setuptools.package_index.parse_bdist_wininst( 'reportlab-2.5.win-amd64.exe'), ('reportlab-2.5', None, 'win-amd64')) + def test__vcs_split_rev_from_url(self): + """ + Test the basic usage of _vcs_split_rev_from_url + """ + vsrfu = setuptools.package_index.PackageIndex._vcs_split_rev_from_url + url, rev = vsrfu('https://example.com/bar@2995') + self.assertEqual(url, 'https://example.com/bar') + self.assertEqual(rev, '2995') + class TestContentCheckers(unittest.TestCase): def test_md5(self): @@ -169,11 +178,7 @@ class TestContentCheckers(unittest.TestCase): def test_get_hash_name_md5(self): checker = setuptools.package_index.HashChecker.from_url( 'http://foo/bar#md5=f12895fdffbd45007040d2e44df98478') - if sys.version_info >= (2,5): - self.assertEqual(checker.hash.name, 'md5') - else: - # Python 2.4 compatability - self.assertEqual(checker._get_hash_name(), 'md5') + self.assertEqual(checker.hash_name, 'md5') def test_report(self): checker = setuptools.package_index.HashChecker.from_url( diff --git a/setuptools/tests/test_resources.py b/setuptools/tests/test_resources.py index df5261d1..c9fcf76c 100644 --- a/setuptools/tests/test_resources.py +++ b/setuptools/tests/test_resources.py @@ -1,11 +1,24 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# NOTE: the shebang and encoding lines are for ScriptHeaderTests; do not remove -from unittest import TestCase, makeSuite; from pkg_resources import * -from setuptools.command.easy_install import get_script_header, is_sh +# NOTE: the shebang and encoding lines are for ScriptHeaderTests do not remove + +import os +import sys +import tempfile +import shutil +from unittest import TestCase + +import pkg_resources +from pkg_resources import (parse_requirements, VersionConflict, parse_version, + Distribution, EntryPoint, Requirement, safe_version, safe_name, + WorkingSet) + +from setuptools.command.easy_install import (get_script_header, is_sh, + nt_quote_arg) from setuptools.compat import StringIO, iteritems -import os, pkg_resources, sys, tempfile, shutil -try: frozenset + +try: + frozenset except NameError: from sets import ImmutableSet as frozenset @@ -15,11 +28,11 @@ def safe_repr(obj, short=False): result = repr(obj) except Exception: result = object.__repr__(obj) - if not short or len(result) < _MAX_LENGTH: + if not short or len(result) < pkg_resources._MAX_LENGTH: return result - return result[:_MAX_LENGTH] + ' [truncated]...' + return result[:pkg_resources._MAX_LENGTH] + ' [truncated]...' -class Metadata(EmptyProvider): +class Metadata(pkg_resources.EmptyProvider): """Mock object to return metadata as if from an on-disk distribution""" def __init__(self,*pairs): @@ -32,18 +45,20 @@ class Metadata(EmptyProvider): return self.metadata[name] def get_metadata_lines(self,name): - return yield_lines(self.get_metadata(name)) + return pkg_resources.yield_lines(self.get_metadata(name)) + +dist_from_fn = pkg_resources.Distribution.from_filename class DistroTests(TestCase): def testCollection(self): # empty path should produce no distributions - ad = Environment([], platform=None, python=None) + ad = pkg_resources.Environment([], platform=None, python=None) self.assertEqual(list(ad), []) self.assertEqual(ad['FooPkg'],[]) - ad.add(Distribution.from_filename("FooPkg-1.3_1.egg")) - ad.add(Distribution.from_filename("FooPkg-1.4-py2.4-win32.egg")) - ad.add(Distribution.from_filename("FooPkg-1.2-py2.4.egg")) + ad.add(dist_from_fn("FooPkg-1.3_1.egg")) + ad.add(dist_from_fn("FooPkg-1.4-py2.4-win32.egg")) + ad.add(dist_from_fn("FooPkg-1.2-py2.4.egg")) # Name is in there now self.assertTrue(ad['FooPkg']) @@ -60,27 +75,33 @@ class DistroTests(TestCase): [dist.version for dist in ad['FooPkg']], ['1.4','1.2'] ) # And inserting adds them in order - ad.add(Distribution.from_filename("FooPkg-1.9.egg")) + ad.add(dist_from_fn("FooPkg-1.9.egg")) self.assertEqual( [dist.version for dist in ad['FooPkg']], ['1.9','1.4','1.2'] ) ws = WorkingSet([]) - foo12 = Distribution.from_filename("FooPkg-1.2-py2.4.egg") - foo14 = Distribution.from_filename("FooPkg-1.4-py2.4-win32.egg") + foo12 = dist_from_fn("FooPkg-1.2-py2.4.egg") + foo14 = dist_from_fn("FooPkg-1.4-py2.4-win32.egg") req, = parse_requirements("FooPkg>=1.3") # Nominal case: no distros on path, should yield all applicable self.assertEqual(ad.best_match(req,ws).version, '1.9') # If a matching distro is already installed, should return only that - ws.add(foo14); self.assertEqual(ad.best_match(req,ws).version, '1.4') + ws.add(foo14) + self.assertEqual(ad.best_match(req,ws).version, '1.4') # If the first matching distro is unsuitable, it's a version conflict - ws = WorkingSet([]); ws.add(foo12); ws.add(foo14) + ws = WorkingSet([]) + ws.add(foo12) + ws.add(foo14) self.assertRaises(VersionConflict, ad.best_match, req, ws) # If more than one match on the path, the first one takes precedence - ws = WorkingSet([]); ws.add(foo14); ws.add(foo12); ws.add(foo14); + ws = WorkingSet([]) + ws.add(foo14) + ws.add(foo12) + ws.add(foo14) self.assertEqual(ad.best_match(req,ws).version, '1.4') def checkFooPkg(self,d): @@ -103,9 +124,9 @@ class DistroTests(TestCase): self.assertEqual(d.platform, None) def testDistroParse(self): - d = Distribution.from_filename("FooPkg-1.3_1-py2.4-win32.egg") + d = dist_from_fn("FooPkg-1.3_1-py2.4-win32.egg") self.checkFooPkg(d) - d = Distribution.from_filename("FooPkg-1.3_1-py2.4-win32.egg-info") + d = dist_from_fn("FooPkg-1.3_1-py2.4-win32.egg-info") self.checkFooPkg(d) def testDistroMetadata(self): @@ -117,7 +138,6 @@ class DistroTests(TestCase): ) self.checkFooPkg(d) - def distRequires(self, txt): return Distribution("/foo", metadata=Metadata(('depends.txt', txt))) @@ -131,20 +151,21 @@ class DistroTests(TestCase): for v in "Twisted>=1.5", "Twisted>=1.5\nZConfig>=2.0": self.checkRequires(self.distRequires(v), v) - def testResolve(self): - ad = Environment([]); ws = WorkingSet([]) + ad = pkg_resources.Environment([]) + ws = WorkingSet([]) # Resolving no requirements -> nothing to install - self.assertEqual( list(ws.resolve([],ad)), [] ) + self.assertEqual(list(ws.resolve([],ad)), []) # Request something not in the collection -> DistributionNotFound self.assertRaises( - DistributionNotFound, ws.resolve, parse_requirements("Foo"), ad + pkg_resources.DistributionNotFound, ws.resolve, parse_requirements("Foo"), ad ) Foo = Distribution.from_filename( "/foo_dir/Foo-1.2.egg", metadata=Metadata(('depends.txt', "[bar]\nBaz>=2.0")) ) - ad.add(Foo); ad.add(Distribution.from_filename("Foo-0.9.egg")) + ad.add(Foo) + ad.add(Distribution.from_filename("Foo-0.9.egg")) # Request thing(s) that are available -> list to activate for i in range(3): @@ -157,7 +178,7 @@ class DistroTests(TestCase): # Request an extra that causes an unresolved dependency for "Baz" self.assertRaises( - DistributionNotFound, ws.resolve,parse_requirements("Foo[bar]"), ad + pkg_resources.DistributionNotFound, ws.resolve,parse_requirements("Foo[bar]"), ad ) Baz = Distribution.from_filename( "/foo_dir/Baz-2.1.egg", metadata=Metadata(('depends.txt', "Foo")) @@ -169,9 +190,8 @@ class DistroTests(TestCase): list(ws.resolve(parse_requirements("Foo[bar]"), ad)), [Foo,Baz] ) # Requests for conflicting versions produce VersionConflict - self.assertRaises( VersionConflict, - ws.resolve, parse_requirements("Foo==1.2\nFoo!=1.2"), ad - ) + self.assertRaises(VersionConflict, + ws.resolve, parse_requirements("Foo==1.2\nFoo!=1.2"), ad) def testDistroDependsOptions(self): d = self.distRequires(""" @@ -196,7 +216,7 @@ class DistroTests(TestCase): d,"Twisted>=1.5 fcgiapp>=0.1 ZConfig>=2.0 docutils>=0.3".split(), ["fastcgi", "docgen"] ) - self.assertRaises(UnknownExtra, d.requires, ["foo"]) + self.assertRaises(pkg_resources.UnknownExtra, d.requires, ["foo"]) class EntryPointTests(TestCase): @@ -304,8 +324,8 @@ class RequirementsTests(TestCase): def testBasicContains(self): r = Requirement("Twisted", [('>=','1.2')], ()) foo_dist = Distribution.from_filename("FooPkg-1.3_1.egg") - twist11 = Distribution.from_filename("Twisted-1.1.egg") - twist12 = Distribution.from_filename("Twisted-1.2.egg") + twist11 = Distribution.from_filename("Twisted-1.1.egg") + twist12 = Distribution.from_filename("Twisted-1.2.egg") self.assertTrue(parse_version('1.2') in r) self.assertTrue(parse_version('1.1') not in r) self.assertTrue('1.2' in r) @@ -321,7 +341,6 @@ class RequirementsTests(TestCase): for v in ('1.2c1','1.3.1','1.5','1.9.1','2.0','2.5','3.0','4.0'): self.assertTrue(v not in r, (v,r)) - def testOptionsAndHashing(self): r1 = Requirement.parse("Twisted[foo,bar]>=1.2") r2 = Requirement.parse("Twisted[bar,FOO]>=1.2") @@ -366,15 +385,6 @@ class RequirementsTests(TestCase): Requirement.parse('setuptools >= 0.7').project_name, 'setuptools') - - - - - - - - - class ParseTests(TestCase): def testEmptyParse(self): @@ -388,9 +398,7 @@ class ParseTests(TestCase): self.assertEqual(list(pkg_resources.yield_lines(inp)),out) def testSplitting(self): - self.assertEqual( - list( - pkg_resources.split_sections(""" + sample = """ x [Y] z @@ -403,8 +411,7 @@ class ParseTests(TestCase): [q] v """ - ) - ), + self.assertEqual(list(pkg_resources.split_sections(sample)), [(None,["x"]), ("Y",["z","a"]), ("b",["c"]), ("d",[]), ("q",["v"])] ) self.assertRaises(ValueError,list,pkg_resources.split_sections("[foo")) @@ -455,7 +462,8 @@ class ParseTests(TestCase): c('0pre1', '0.0c1') c('0.0.0preview1', '0c1') c('0.0c1', '0-rc1') - c('1.2a1', '1.2.a.1'); c('1.2...a', '1.2a') + c('1.2a1', '1.2.a.1') + c('1.2...a', '1.2a') def testVersionOrdering(self): def c(s1,s2): @@ -492,30 +500,30 @@ class ParseTests(TestCase): c(v2,v1) - - - - - - class ScriptHeaderTests(TestCase): non_ascii_exe = '/Users/José/bin/python' + exe_with_spaces = r'C:\Program Files\Python33\python.exe' def test_get_script_header(self): if not sys.platform.startswith('java') or not is_sh(sys.executable): # This test is for non-Jython platforms + expected = '#!%s\n' % nt_quote_arg(os.path.normpath(sys.executable)) self.assertEqual(get_script_header('#!/usr/local/bin/python'), - '#!%s\n' % os.path.normpath(sys.executable)) + expected) + expected = '#!%s -x\n' % nt_quote_arg(os.path.normpath(sys.executable)) self.assertEqual(get_script_header('#!/usr/bin/python -x'), - '#!%s -x\n' % os.path.normpath(sys.executable)) + expected) self.assertEqual(get_script_header('#!/usr/bin/python', executable=self.non_ascii_exe), '#!%s -x\n' % self.non_ascii_exe) + candidate = get_script_header('#!/usr/bin/python', + executable=self.exe_with_spaces) + self.assertEqual(candidate, '#!"%s"\n' % self.exe_with_spaces) def test_get_script_header_jython_workaround(self): # This test doesn't work with Python 3 in some locales if (sys.version_info >= (3,) and os.environ.get("LC_CTYPE") - in (None, "C", "POSIX")): + in (None, "C", "POSIX")): return class java: @@ -554,8 +562,6 @@ class ScriptHeaderTests(TestCase): sys.stdout, sys.stderr = stdout, stderr - - class NamespaceTests(TestCase): def setUp(self): @@ -610,6 +616,5 @@ class NamespaceTests(TestCase): self.assertEqual(pkg_resources._namespace_packages["pkg1"], ["pkg1.pkg2"]) # check the __path__ attribute contains both paths self.assertEqual(pkg1.pkg2.__path__, [ - os.path.join(self._tmpdir, "site-pkgs", "pkg1", "pkg2"), - os.path.join(self._tmpdir, "site-pkgs2", "pkg1", "pkg2") ]) - + os.path.join(self._tmpdir, "site-pkgs", "pkg1", "pkg2"), + os.path.join(self._tmpdir, "site-pkgs2", "pkg1", "pkg2")]) diff --git a/setuptools/tests/test_sandbox.py b/setuptools/tests/test_sandbox.py index 1609ee86..3dad1376 100644 --- a/setuptools/tests/test_sandbox.py +++ b/setuptools/tests/test_sandbox.py @@ -5,7 +5,10 @@ import os import shutil import unittest import tempfile +import types +import pkg_resources +import setuptools.sandbox from setuptools.sandbox import DirectorySandbox, SandboxViolation def has_win32com(): @@ -62,5 +65,15 @@ class TestSandbox(unittest.TestCase): finally: if os.path.exists(target): os.remove(target) + def test_setup_py_with_BOM(self): + """ + It should be possible to execute a setup.py with a Byte Order Mark + """ + target = pkg_resources.resource_filename(__name__, + 'script-with-bom.py') + namespace = types.ModuleType('namespace') + setuptools.sandbox.execfile(target, vars(namespace)) + assert namespace.result == 'passed' + if __name__ == '__main__': unittest.main() diff --git a/setuptools/version.py b/setuptools/version.py new file mode 100644 index 00000000..9910ac22 --- /dev/null +++ b/setuptools/version.py @@ -0,0 +1 @@ +__version__ = '1.1.7' |
