Commit 522dd170 authored by Jason R. Coombs's avatar Jason R. Coombs

Merged latest changes from setuptools-0.6 branch

--HG--
rename : doc/formats.txt => docs/formats.txt
parents 8f055654 43052941
...@@ -2,6 +2,21 @@ ...@@ -2,6 +2,21 @@
CHANGES CHANGES
======= =======
---
0.7
---
* Merged Setuptools and Distribute. See MERGE.txt for details.
Added several features that were slated for setuptools 0.6c12:
* Index URL now defaults to HTTPS.
* Added environment marker support. Now clients may designate a PEP-426
environment marker for "extra" dependencies. For an example, see the
Setuptools ``setup.py`` script.
* Added support for SSL certificate validation when installing packages from
an HTTPS service.
------ ------
0.6.39 0.6.39
------ ------
......
This diff is collapsed.
...@@ -152,7 +152,7 @@ __all__ = [ ...@@ -152,7 +152,7 @@ __all__ = [
# 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',
'safe_extra', 'to_filename', 'safe_extra', 'to_filename', 'invalid_marker', 'evaluate_marker',
# filesystem utilities # filesystem utilities
'ensure_directory', 'normalize_path', 'ensure_directory', 'normalize_path',
...@@ -1170,6 +1170,129 @@ def to_filename(name): ...@@ -1170,6 +1170,129 @@ def to_filename(name):
_marker_names = {
'os': ['name'], 'sys': ['platform'],
'platform': ['version','machine','python_implementation'],
'python_version': [], 'python_full_version': [], 'extra':[],
}
_marker_values = {
'os_name': lambda: os.name,
'sys_platform': lambda: sys.platform,
'python_full_version': lambda: sys.version.split()[0],
'python_version': lambda:'%s.%s' % (sys.version_info[0], sys.version_info[1]),
'platform_version': lambda: _platinfo('version'),
'platform_machine': lambda: _platinfo('machine'),
'python_implementation': lambda: _platinfo('python_implementation') or _pyimp(),
}
def _platinfo(attr):
try:
import platform
except ImportError:
return ''
return getattr(platform, attr, lambda:'')()
def _pyimp():
if sys.platform=='cli':
return 'IronPython'
elif sys.platform.startswith('java'):
return 'Jython'
elif '__pypy__' in sys.builtin_module_names:
return 'PyPy'
else:
return 'CPython'
def invalid_marker(text):
"""Validate text as a PEP 426 environment marker; return exception or False"""
try:
evaluate_marker(text)
except SyntaxError:
return sys.exc_info()[1]
return False
def evaluate_marker(text, extra=None, _ops={}):
"""Evaluate a PEP 426 environment marker; SyntaxError if marker is invalid"""
if not _ops:
from token import NAME, STRING
import token, symbol, operator
def and_test(nodelist):
# MUST NOT short-circuit evaluation, or invalid syntax can be skipped!
return reduce(operator.and_, [interpret(nodelist[i]) for i in range(1,len(nodelist),2)])
def test(nodelist):
# MUST NOT short-circuit evaluation, or invalid syntax can be skipped!
return reduce(operator.or_, [interpret(nodelist[i]) for i in range(1,len(nodelist),2)])
def atom(nodelist):
t = nodelist[1][0]
if t == token.LPAR:
if nodelist[2][0] == token.RPAR:
raise SyntaxError("Empty parentheses")
return interpret(nodelist[2])
raise SyntaxError("Language feature not supported in environment markers")
def comparison(nodelist):
if len(nodelist)>4:
raise SyntaxError("Chained comparison not allowed in environment markers")
comp = nodelist[2][1]
cop = comp[1]
if comp[0] == NAME:
if len(nodelist[2]) == 3:
if cop == 'not':
cop = 'not in'
else:
cop = 'is not'
try:
cop = _ops[cop]
except KeyError:
raise SyntaxError(repr(cop)+" operator not allowed in environment markers")
return cop(evaluate(nodelist[1]), evaluate(nodelist[3]))
_ops.update({
symbol.test: test, symbol.and_test: and_test, symbol.atom: atom,
symbol.comparison: comparison, 'not in': lambda x,y: x not in y,
'in': lambda x,y: x in y, '==': operator.eq, '!=': operator.ne,
})
if hasattr(symbol,'or_test'):
_ops[symbol.or_test] = test
def interpret(nodelist):
while len(nodelist)==2: nodelist = nodelist[1]
try:
op = _ops[nodelist[0]]
except KeyError:
raise SyntaxError("Comparison or logical expression expected")
raise SyntaxError("Language feature not supported in environment markers: "+symbol.sym_name[nodelist[0]])
return op(nodelist)
def evaluate(nodelist):
while len(nodelist)==2: nodelist = nodelist[1]
kind = nodelist[0]
name = nodelist[1]
#while len(name)==2: name = name[1]
if kind==NAME:
try:
op = _marker_values[name]
except KeyError:
raise SyntaxError("Unknown name %r" % name)
return op()
if kind==STRING:
s = nodelist[1]
if s[:1] not in "'\"" or s.startswith('"""') or s.startswith("'''") \
or '\\' in s:
raise SyntaxError(
"Only plain strings allowed in environment markers")
return s[1:-1]
raise SyntaxError("Language feature not supported in environment markers")
import parser
return interpret(parser.expr(text).totuple(1)[1])
class NullProvider: class NullProvider:
"""Try to implement resources and metadata for arbitrary PEP 302 loaders""" """Try to implement resources and metadata for arbitrary PEP 302 loaders"""
...@@ -1994,7 +2117,6 @@ def parse_version(s): ...@@ -1994,7 +2117,6 @@ def parse_version(s):
parts.pop() parts.pop()
parts.append(part) parts.append(part)
return tuple(parts) return tuple(parts)
class EntryPoint(object): class EntryPoint(object):
"""Object representing an advertised importable object""" """Object representing an advertised importable object"""
...@@ -2235,7 +2357,14 @@ class Distribution(object): ...@@ -2235,7 +2357,14 @@ class Distribution(object):
dm = self.__dep_map = {None: []} dm = self.__dep_map = {None: []}
for name in 'requires.txt', 'depends.txt': for name in 'requires.txt', 'depends.txt':
for extra,reqs in split_sections(self._get_metadata(name)): for extra,reqs in split_sections(self._get_metadata(name)):
if extra: extra = safe_extra(extra) if extra:
if ':' in extra:
extra, marker = extra.split(':',1)
if invalid_marker(marker):
reqs=[] # XXX warn
elif not evaluate_marker(marker):
reqs=[]
extra = safe_extra(extra) or None
dm.setdefault(extra,[]).extend(parse_requirements(reqs)) dm.setdefault(extra,[]).extend(parse_requirements(reqs))
return dm return dm
_dep_map = property(_dep_map) _dep_map = property(_dep_map)
...@@ -2259,6 +2388,8 @@ class Distribution(object): ...@@ -2259,6 +2388,8 @@ class Distribution(object):
for line in self.get_metadata_lines(name): for line in self.get_metadata_lines(name):
yield line yield line
def activate(self,path=None): def activate(self,path=None):
"""Ensure distribution is importable on `path` (default=sys.path)""" """Ensure distribution is importable on `path` (default=sys.path)"""
if path is None: path = sys.path if path is None: path = sys.path
...@@ -2297,6 +2428,9 @@ class Distribution(object): ...@@ -2297,6 +2428,9 @@ class Distribution(object):
raise AttributeError,attr raise AttributeError,attr
return getattr(self._provider, attr) return getattr(self._provider, attr)
#@classmethod #@classmethod
def from_filename(cls,filename,metadata=None, **kw): def from_filename(cls,filename,metadata=None, **kw):
return cls.from_location( return cls.from_location(
...@@ -2338,18 +2472,6 @@ class Distribution(object): ...@@ -2338,18 +2472,6 @@ class Distribution(object):
def insert_on(self, path, loc = None): def insert_on(self, path, loc = None):
"""Insert self.location in path before its nearest parent directory""" """Insert self.location in path before its nearest parent directory"""
......
...@@ -207,5 +207,22 @@ dist = setup( ...@@ -207,5 +207,22 @@ dist = setup(
Topic :: System :: Systems Administration Topic :: System :: Systems Administration
Topic :: Utilities Topic :: Utilities
""").strip().splitlines(), """).strip().splitlines(),
scripts = scripts, extras_require = {
"ssl:sys_platform=='win32'": "wincertstore==0.1",
"ssl:sys_platform=='win32' and python_version in '2.3, 2.4'": "ctypes==1.0.2",
"ssl:python_version in '2.3, 2.4, 2.5'":"ssl==1.16",
"certs": "certifi==0.0.8",
},
dependency_links = [
'http://pypi.python.org/packages/source/c/certifi/certifi-0.0.8.tar.gz#md5=dc5f5e7f0b5fc08d27654b17daa6ecec',
'http://pypi.python.org/packages/source/s/ssl/ssl-1.16.tar.gz#md5=fb12d335d56f3c8c7c1fefc1c06c4bfb',
'http://pypi.python.org/packages/source/w/wincertstore/wincertstore-0.1.zip#md5=2f9accbebe8f7b4c06ac7aa83879b81c',
'http://sourceforge.net/projects/ctypes/files/ctypes/1.0.2/ctypes-1.0.2.win32-py2.3.exe/download#md5=9afe4b75240a8808a24df7a76b6081e3',
'http://sourceforge.net/projects/ctypes/files/ctypes/1.0.2/ctypes-1.0.2.win32-py2.4.exe/download#md5=9092a0ad5a3d79fa2d980f1ddc5e9dbc',
'http://peak.telecommunity.com/dist/ssl-1.16-py2.3-win32.egg#md5=658f74b3eb6f32050e8531bb73de8e74',
'http://peak.telecommunity.com/dist/ssl-1.16-py2.4-win32.egg#md5=3cfa2c526dc66e318e8520b6f1aadce5',
'http://peak.telecommunity.com/dist/ssl-1.16-py2.5-win32.egg#md5=85ad1cda806d639743121c0bbcb5f39b',
],
scripts = [],
# tests_require = "setuptools[ssl]",
) )
http://pypi.python.org/packages/source/c/certifi/certifi-0.0.8.tar.gz#md5=dc5f5e7f0b5fc08d27654b17daa6ecec
http://pypi.python.org/packages/source/s/ssl/ssl-1.16.tar.gz#md5=fb12d335d56f3c8c7c1fefc1c06c4bfb
http://pypi.python.org/packages/source/w/wincertstore/wincertstore-0.1.zip#md5=2f9accbebe8f7b4c06ac7aa83879b81c
http://sourceforge.net/projects/ctypes/files/ctypes/1.0.2/ctypes-1.0.2.win32-py2.3.exe/download#md5=9afe4b75240a8808a24df7a76b6081e3
http://sourceforge.net/projects/ctypes/files/ctypes/1.0.2/ctypes-1.0.2.win32-py2.4.exe/download#md5=9092a0ad5a3d79fa2d980f1ddc5e9dbc
http://peak.telecommunity.com/dist/ssl-1.16-py2.3-win32.egg#md5=658f74b3eb6f32050e8531bb73de8e74
http://peak.telecommunity.com/dist/ssl-1.16-py2.4-win32.egg#md5=3cfa2c526dc66e318e8520b6f1aadce5
http://peak.telecommunity.com/dist/ssl-1.16-py2.5-win32.egg#md5=85ad1cda806d639743121c0bbcb5f39b
...@@ -7,6 +7,7 @@ build_py = setuptools.command.build_py:build_py ...@@ -7,6 +7,7 @@ build_py = setuptools.command.build_py:build_py
saveopts = setuptools.command.saveopts:saveopts saveopts = setuptools.command.saveopts:saveopts
egg_info = setuptools.command.egg_info:egg_info egg_info = setuptools.command.egg_info:egg_info
register = setuptools.command.register:register register = setuptools.command.register:register
upload = setuptools.command.upload:upload
upload_docs = setuptools.command.upload_docs:upload_docs upload_docs = setuptools.command.upload_docs:upload_docs
install_egg_info = setuptools.command.install_egg_info:install_egg_info install_egg_info = setuptools.command.install_egg_info:install_egg_info
alias = setuptools.command.alias:alias alias = setuptools.command.alias:alias
......
[ssl:sys_platform=='win32']
wincertstore==0.1
[certs]
certifi==0.0.8
[ssl:sys_platform=='win32' and python_version in '2.3, 2.4']
ctypes==1.0.2
[ssl:python_version in '2.3, 2.4, 2.5']
ssl==1.16
\ No newline at end of file
...@@ -283,7 +283,7 @@ class easy_install(Command): ...@@ -283,7 +283,7 @@ class easy_install(Command):
else: else:
self.all_site_dirs.append(normalize_path(d)) self.all_site_dirs.append(normalize_path(d))
if not self.editable: self.check_site_dir() if not self.editable: self.check_site_dir()
self.index_url = self.index_url or "http://pypi.python.org/simple" self.index_url = self.index_url or "https://pypi.python.org/simple"
self.shadow_path = self.all_site_dirs[:] self.shadow_path = self.all_site_dirs[:]
for path_item in self.install_dir, normalize_path(self.script_dir): for path_item in self.install_dir, normalize_path(self.script_dir):
if path_item not in self.shadow_path: if path_item not in self.shadow_path:
......
...@@ -48,7 +48,6 @@ def assert_string_list(dist, attr, value): ...@@ -48,7 +48,6 @@ def assert_string_list(dist, attr, value):
raise DistutilsSetupError( raise DistutilsSetupError(
"%r must be a list of strings (got %r)" % (attr,value) "%r must be a list of strings (got %r)" % (attr,value)
) )
def check_nsp(dist, attr, value): def check_nsp(dist, attr, value):
"""Verify that namespace packages are valid""" """Verify that namespace packages are valid"""
assert_string_list(dist,attr,value) assert_string_list(dist,attr,value)
...@@ -70,6 +69,10 @@ def check_extras(dist, attr, value): ...@@ -70,6 +69,10 @@ def check_extras(dist, attr, value):
"""Verify that extras_require mapping is valid""" """Verify that extras_require mapping is valid"""
try: try:
for k,v in value.items(): for k,v in value.items():
if ':' in k:
k,m = k.split(':',1)
if pkg_resources.invalid_marker(m):
raise DistutilsSetupError("Invalid environment marker: "+m)
list(pkg_resources.parse_requirements(v)) list(pkg_resources.parse_requirements(v))
except (TypeError,ValueError,AttributeError): except (TypeError,ValueError,AttributeError):
raise DistutilsSetupError( raise DistutilsSetupError(
...@@ -78,9 +81,6 @@ def check_extras(dist, attr, value): ...@@ -78,9 +81,6 @@ def check_extras(dist, attr, value):
"requirement specifiers." "requirement specifiers."
) )
def assert_bool(dist, attr, value): def assert_bool(dist, attr, value):
"""Verify that value is True, False, 0, or 1""" """Verify that value is True, False, 0, or 1"""
if bool(value) != value: if bool(value) != value:
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import sys, os.path, re, urlparse, urllib2, shutil, random, socket, cStringIO import sys, os.path, re, urlparse, urllib2, shutil, random, socket, cStringIO
import base64 import base64
import httplib, urllib import httplib, urllib
from setuptools import ssl_support
from pkg_resources import * from pkg_resources import *
from distutils import log from distutils import log
from distutils.errors import DistutilsError from distutils.errors import DistutilsError
...@@ -157,12 +158,11 @@ user_agent = "Python-urllib/%s setuptools/%s" % ( ...@@ -157,12 +158,11 @@ user_agent = "Python-urllib/%s setuptools/%s" % (
sys.version[:3], require('setuptools')[0].version sys.version[:3], require('setuptools')[0].version
) )
class PackageIndex(Environment): class PackageIndex(Environment):
"""A distribution index that scans web pages for download URLs""" """A distribution index that scans web pages for download URLs"""
def __init__(self, index_url="http://pypi.python.org/simple", hosts=('*',), def __init__(self, index_url="https://pypi.python.org/simple", hosts=('*',),
*args, **kw ca_bundle=None, verify_ssl=True, *args, **kw
): ):
Environment.__init__(self,*args,**kw) Environment.__init__(self,*args,**kw)
self.index_url = index_url + "/"[:not index_url.endswith('/')] self.index_url = index_url + "/"[:not index_url.endswith('/')]
...@@ -171,8 +171,9 @@ class PackageIndex(Environment): ...@@ -171,8 +171,9 @@ class PackageIndex(Environment):
self.package_pages = {} self.package_pages = {}
self.allows = re.compile('|'.join(map(translate,hosts))).match self.allows = re.compile('|'.join(map(translate,hosts))).match
self.to_scan = [] self.to_scan = []
if verify_ssl and ssl_support.is_available and (ca_bundle or ssl_support.find_ca_bundle()):
self.opener = ssl_support.opener_for(ca_bundle)
else: self.opener = urllib2.urlopen
def process_url(self, url, retrieve=False): def process_url(self, url, retrieve=False):
"""Evaluate a URL as a possible download, and maybe retrieve it""" """Evaluate a URL as a possible download, and maybe retrieve it"""
...@@ -601,7 +602,7 @@ class PackageIndex(Environment): ...@@ -601,7 +602,7 @@ class PackageIndex(Environment):
if url.startswith('file:'): if url.startswith('file:'):
return local_open(url) return local_open(url)
try: try:
return open_with_auth(url) return open_with_auth(url, self.opener)
except (ValueError, httplib.InvalidURL), v: except (ValueError, httplib.InvalidURL), v:
msg = ' '.join([str(arg) for arg in v.args]) msg = ' '.join([str(arg) for arg in v.args])
if warning: if warning:
...@@ -659,7 +660,6 @@ class PackageIndex(Environment): ...@@ -659,7 +660,6 @@ class PackageIndex(Environment):
self.url_ok(url, True) # raises error if not allowed self.url_ok(url, True) # raises error if not allowed
return self._attempt_download(url, filename) return self._attempt_download(url, filename)
def scan_url(self, url): def scan_url(self, url):
self.process_url(url, True) self.process_url(url, True)
...@@ -859,7 +859,7 @@ def _encode_auth(auth): ...@@ -859,7 +859,7 @@ def _encode_auth(auth):
# strip the trailing carriage return # strip the trailing carriage return
return encoded.rstrip() return encoded.rstrip()
def open_with_auth(url): def open_with_auth(url, opener=urllib2.urlopen):
"""Open a urllib2 request, handling HTTP authentication""" """Open a urllib2 request, handling HTTP authentication"""
scheme, netloc, path, params, query, frag = urlparse.urlparse(url) scheme, netloc, path, params, query, frag = urlparse.urlparse(url)
...@@ -883,7 +883,7 @@ def open_with_auth(url): ...@@ -883,7 +883,7 @@ def open_with_auth(url):
request = urllib2.Request(url) request = urllib2.Request(url)
request.add_header('User-Agent', user_agent) request.add_header('User-Agent', user_agent)
fp = urllib2.urlopen(request) fp = opener(request)
if auth: if auth:
# Put authentication info back into request URL if same host, # Put authentication info back into request URL if same host,
......
import sys, os, socket, urllib2, atexit, re
from pkg_resources import ResolutionError, ExtractionError
try:
import ssl
except ImportError:
ssl = None
__all__ = [
'VerifyingHTTPSHandler', 'find_ca_bundle', 'is_available', 'cert_paths',
'opener_for'
]
cert_paths = """
/etc/pki/tls/certs/ca-bundle.crt
/etc/ssl/certs/ca-certificates.crt
/usr/share/ssl/certs/ca-bundle.crt
/usr/local/share/certs/ca-root.crt
/etc/ssl/cert.pem
/System/Library/OpenSSL/certs/cert.pem
""".strip().split()
HTTPSHandler = HTTPSConnection = object
for what, where in (
('HTTPSHandler', ['urllib2','urllib.request']),
('HTTPSConnection', ['httplib', 'http.client']),
):
for module in where:
try:
exec("from %s import %s" % (module, what))
except ImportError:
pass
is_available = ssl is not None and object not in (HTTPSHandler, HTTPSConnection)
try:
from socket import create_connection
except ImportError:
_GLOBAL_DEFAULT_TIMEOUT = getattr(socket, '_GLOBAL_DEFAULT_TIMEOUT', object())
def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
source_address=None):
"""Connect to *address* and return the socket object.
Convenience function. Connect to *address* (a 2-tuple ``(host,
port)``) and return the socket object. Passing the optional
*timeout* parameter will set the timeout on the socket instance
before attempting to connect. If no *timeout* is supplied, the
global default timeout setting returned by :func:`getdefaulttimeout`
is used. If *source_address* is set it must be a tuple of (host, port)
for the socket to bind as a source address before making the connection.
An host of '' or port 0 tells the OS to use the default.
"""
host, port = address
err = None
for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
af, socktype, proto, canonname, sa = res
sock = None
try:
sock = socket.socket(af, socktype, proto)
if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
sock.settimeout(timeout)
if source_address:
sock.bind(source_address)
sock.connect(sa)
return sock
except error:
err = True
if sock is not None:
sock.close()
if err:
raise
else:
raise error("getaddrinfo returns an empty list")
try:
from ssl import CertificateError, match_hostname
except ImportError:
class CertificateError(ValueError):
pass
def _dnsname_to_pat(dn):
pats = []
for frag in dn.split(r'.'):
if frag == '*':
# When '*' is a fragment by itself, it matches a non-empty dotless
# fragment.
pats.append('[^.]+')
else:
# Otherwise, '*' matches any dotless fragment.
frag = re.escape(frag)
pats.append(frag.replace(r'\*', '[^.]*'))
return re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
def match_hostname(cert, hostname):
"""Verify that *cert* (in decoded format as returned by
SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 rules
are mostly followed, but IP addresses are not accepted for *hostname*.
CertificateError is raised on failure. On success, the function
returns nothing.
"""
if not cert:
raise ValueError("empty or no certificate")
dnsnames = []
san = cert.get('subjectAltName', ())
for key, value in san:
if key == 'DNS':
if _dnsname_to_pat(value).match(hostname):
return
dnsnames.append(value)
if not dnsnames:
# The subject is only checked when there is no dNSName entry
# in subjectAltName
for sub in cert.get('subject', ()):
for key, value in sub:
# XXX according to RFC 2818, the most specific Common Name
# must be used.
if key == 'commonName':
if _dnsname_to_pat(value).match(hostname):
return
dnsnames.append(value)
if len(dnsnames) > 1:
raise CertificateError("hostname %r "
"doesn't match either of %s"
% (hostname, ', '.join(map(repr, dnsnames))))
elif len(dnsnames) == 1:
raise CertificateError("hostname %r "
"doesn't match %r"
% (hostname, dnsnames[0]))
else:
raise CertificateError("no appropriate commonName or "
"subjectAltName fields were found")
class VerifyingHTTPSHandler(HTTPSHandler):
"""Simple verifying handler: no auth, subclasses, timeouts, etc."""
def __init__(self, ca_bundle):
self.ca_bundle = ca_bundle
HTTPSHandler.__init__(self)
def https_open(self, req):
return self.do_open(
lambda host, **kw: VerifyingHTTPSConn(host, self.ca_bundle, **kw), req
)
class VerifyingHTTPSConn(HTTPSConnection):
"""Simple verifying connection: no auth, subclasses, timeouts, etc."""
def __init__(self, host, ca_bundle, **kw):
HTTPSConnection.__init__(self, host, **kw)
self.ca_bundle = ca_bundle
def connect(self):
sock = create_connection(
(self.host, self.port), getattr(self,'source_address',None)
)
self.sock = ssl.wrap_socket(
sock, cert_reqs=ssl.CERT_REQUIRED, ca_certs=self.ca_bundle
)
try:
match_hostname(self.sock.getpeercert(), self.host)
except CertificateError:
self.sock.shutdown(socket.SHUT_RDWR)
self.sock.close()
raise
def opener_for(ca_bundle=None):
"""Get a urlopen() replacement that uses ca_bundle for verification"""
return urllib2.build_opener(
VerifyingHTTPSHandler(ca_bundle or find_ca_bundle())
).open
_wincerts = None
def get_win_certfile():
global _wincerts
if _wincerts is not None:
return _wincerts.name
try:
from wincertstore import CertFile
except ImportError:
return None
class MyCertFile(CertFile):
def __init__(self, stores=(), certs=()):
CertFile.__init__(self)
for store in stores:
self.addstore(store)
self.addcerts(certs)
atexit.register(self.close)
_wincerts = MyCertFile(stores=['CA', 'ROOT'])
return _wincerts.name
def find_ca_bundle():
"""Return an existing CA bundle path, or None"""
if os.name=='nt':
return get_win_certfile()
else:
for cert_path in cert_paths:
if os.path.isfile(cert_path):
return cert_path
try:
return pkg_resources.resource_filename('certifi', 'cacert.pem')
except (ImportError, ResolutionError, ExtractionError):
return None
...@@ -527,7 +527,7 @@ class ScriptHeaderTests(TestCase): ...@@ -527,7 +527,7 @@ class ScriptHeaderTests(TestCase):
platform = sys.platform platform = sys.platform
sys.platform = 'java1.5.0_13' sys.platform = 'java1.5.0_13'
stdout = sys.stdout stdout, stderr = sys.stdout, sys.stderr
try: try:
# A mock sys.executable that uses a shebang line (this file) # A mock sys.executable that uses a shebang line (this file)
exe = os.path.normpath(os.path.splitext(__file__)[0] + '.py') exe = os.path.normpath(os.path.splitext(__file__)[0] + '.py')
...@@ -550,7 +550,7 @@ class ScriptHeaderTests(TestCase): ...@@ -550,7 +550,7 @@ class ScriptHeaderTests(TestCase):
finally: finally:
del sys.modules["java"] del sys.modules["java"]
sys.platform = platform sys.platform = platform
sys.stdout = stdout sys.stdout, sys.stderr = stdout, stderr
......
...@@ -119,7 +119,7 @@ editing are also a Distribution. (And, with a little attention to the ...@@ -119,7 +119,7 @@ editing are also a Distribution. (And, with a little attention to the
directory names used, and including some additional metadata, such a directory names used, and including some additional metadata, such a
"development distribution" can be made pluggable as well.) "development distribution" can be made pluggable as well.)
>>> from pkg_resources import WorkingSet, VersionConflict >>> from pkg_resources import WorkingSet
A working set's entries are the sys.path entries that correspond to the active A working set's entries are the sys.path entries that correspond to the active
distributions. By default, the working set's entries are the items on distributions. By default, the working set's entries are the items on
...@@ -170,8 +170,8 @@ You can append a path entry to a working set using ``add_entry()``:: ...@@ -170,8 +170,8 @@ You can append a path entry to a working set using ``add_entry()``::
>>> ws.entries >>> ws.entries
['http://example.com/something'] ['http://example.com/something']
>>> ws.add_entry(pkg_resources.__file__) >>> ws.add_entry(pkg_resources.__file__)
>>> ws.entries == ['http://example.com/something', pkg_resources.__file__] >>> ws.entries
True ['http://example.com/something', '...pkg_resources.py...']
Multiple additions result in multiple entries, even if the entry is already in Multiple additions result in multiple entries, even if the entry is already in
the working set (because ``sys.path`` can contain the same entry more than the working set (because ``sys.path`` can contain the same entry more than
...@@ -208,11 +208,11 @@ You can ask a WorkingSet to ``find()`` a distribution matching a requirement:: ...@@ -208,11 +208,11 @@ You can ask a WorkingSet to ``find()`` a distribution matching a requirement::
Note that asking for a conflicting version of a distribution already in a Note that asking for a conflicting version of a distribution already in a
working set triggers a ``pkg_resources.VersionConflict`` error: working set triggers a ``pkg_resources.VersionConflict`` error:
>>> try: >>> ws.find(Requirement.parse("Bar==1.0")) # doctest: +NORMALIZE_WHITESPACE
... ws.find(Requirement.parse("Bar==1.0")) Traceback (most recent call last):
... except VersionConflict: ...
... print 'ok' VersionConflict: (Bar 0.9 (http://example.com/something),
ok Requirement.parse('Bar==1.0'))
You can subscribe a callback function to receive notifications whenever a new You can subscribe a callback function to receive notifications whenever a new
distribution is added to a working set. The callback is immediately invoked distribution is added to a working set. The callback is immediately invoked
...@@ -328,3 +328,94 @@ setuptools is provided as well:: ...@@ -328,3 +328,94 @@ setuptools is provided as well::
>>> cp("darwin-8.2.0-Power_Macintosh", "macosx-10.3-ppc") >>> cp("darwin-8.2.0-Power_Macintosh", "macosx-10.3-ppc")
False False
Environment Markers
-------------------
>>> from pkg_resources import invalid_marker as im, evaluate_marker as em
>>> import os
>>> print(im("sys_platform"))
Comparison or logical expression expected
>>> print(im("sys_platform=="))
unexpected EOF while parsing (line 1)
>>> print(im("sys_platform=='win32'"))
False
>>> print(im("sys=='x'"))
Unknown name 'sys'
>>> print(im("(extra)"))
Comparison or logical expression expected
>>> print(im("(extra"))
unexpected EOF while parsing (line 1)
>>> print(im("os.open('foo')=='y'"))
Language feature not supported in environment markers
>>> print(im("'x'=='y' and os.open('foo')=='y'")) # no short-circuit!
Language feature not supported in environment markers
>>> print(im("'x'=='x' or os.open('foo')=='y'")) # no short-circuit!
Language feature not supported in environment markers
>>> print(im("'x' < 'y'"))
'<' operator not allowed in environment markers
>>> print(im("'x' < 'y' < 'z'"))
Chained comparison not allowed in environment markers
>>> print(im("r'x'=='x'"))
Only plain strings allowed in environment markers
>>> print(im("'''x'''=='x'"))
Only plain strings allowed in environment markers
>>> print(im('"""x"""=="x"'))
Only plain strings allowed in environment markers
>>> print(im(r"'x\n'=='x'"))
Only plain strings allowed in environment markers
>>> print(im("os.open=='y'"))
Language feature not supported in environment markers
>>> em('"x"=="x"')
True
>>> em('"x"=="y"')
False
>>> em('"x"=="y" and "x"=="x"')
False
>>> em('"x"=="y" or "x"=="x"')
True
>>> em('"x"=="y" and "x"=="q" or "z"=="z"')
True
>>> em('"x"=="y" and ("x"=="q" or "z"=="z")')
False
>>> em('"x"=="y" and "z"=="z" or "x"=="q"')
False
>>> em('"x"=="x" and "z"=="z" or "x"=="q"')
True
>>> em("sys_platform=='win32'") == (sys.platform=='win32')
True
>>> em("'x' in 'yx'")
True
>>> em("'yx' in 'x'")
False
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