diff options
Diffstat (limited to 'setuptools')
-rwxr-xr-x | setuptools/command/easy_install.py | 177 | ||||
-rw-r--r-- | setuptools/dist.py | 25 | ||||
-rw-r--r-- | setuptools/ssl_support.py | 6 | ||||
-rw-r--r-- | setuptools/tests/test_easy_install.py | 4 | ||||
-rw-r--r-- | setuptools/tests/test_resources.py | 133 | ||||
-rw-r--r-- | setuptools/version.py | 2 |
6 files changed, 226 insertions, 121 deletions
diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index 8b88cd1d..cf4402a5 100755 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -22,6 +22,8 @@ import re import stat import random import platform +import textwrap +import warnings from glob import glob from distutils import log, dir_util @@ -1780,57 +1782,130 @@ def fix_jython_executable(executable, options): 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 + """ + # for compatibility, return the writer that creates exe launchers + # unless the SETUPTOOLS_USE_PYLAUNCHER is set, indicating + # future behavior. + use_legacy = 'SETUPTOOLS_USE_PYLAUNCHER' not in os.environ + if use_legacy: + return WindowsLauncherScriptWriter + return cls + + @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 WindowsLauncherScriptWriter(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): """ diff --git a/setuptools/dist.py b/setuptools/dist.py index 01889215..9dbf04ba 100644 --- a/setuptools/dist.py +++ b/setuptools/dist.py @@ -2,6 +2,7 @@ __all__ = ['Distribution'] import re import sys +import warnings from distutils.core import Distribution as _Distribution from setuptools.depends import Require from setuptools.command.install import install @@ -132,7 +133,7 @@ def check_packages(dist, attr, value): "WARNING: %r not a valid package name; please use only" ".-separated package names in setup.py", pkgname ) - + @@ -194,7 +195,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 @@ -252,6 +254,9 @@ class Distribution(_Distribution): 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 = [] @@ -745,7 +750,13 @@ for module in distutils.dist, distutils.core, distutils.cmd: 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 +805,17 @@ 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) + def __init__(self, description, standard=False, available=True, optional=True, require_features=(), remove=(), **extras ): + self.warn_deprecated() self.description = description self.standard = standard 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/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_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/version.py b/setuptools/version.py index 1b1a934d..7e49527e 100644 --- a/setuptools/version.py +++ b/setuptools/version.py @@ -1 +1 @@ -__version__ = '0.9.9' +__version__ = '1.0' |