diff options
Diffstat (limited to 'src')
65 files changed, 3328 insertions, 2125 deletions
diff --git a/src/pyramid/asset.py b/src/pyramid/asset.py index 9d7a3ee63..0d7575a85 100644 --- a/src/pyramid/asset.py +++ b/src/pyramid/asset.py @@ -3,14 +3,12 @@ import pkg_resources from pyramid.compat import string_types -from pyramid.path import ( - package_path, - package_name, - ) +from pyramid.path import package_path, package_name + def resolve_asset_spec(spec, pname='__main__'): if pname and not isinstance(pname, string_types): - pname = pname.__name__ # as package + pname = pname.__name__ # as package if os.path.isabs(spec): return None, spec filename = spec @@ -20,6 +18,7 @@ def resolve_asset_spec(spec, pname='__main__'): pname, filename = None, spec return pname, filename + def asset_spec_from_abspath(abspath, package): """ Try to convert an absolute path to a resource in a package to a resource specification if possible; otherwise return the @@ -28,11 +27,14 @@ def asset_spec_from_abspath(abspath, package): return abspath pp = package_path(package) + os.path.sep if abspath.startswith(pp): - relpath = abspath[len(pp):] - return '%s:%s' % (package_name(package), - relpath.replace(os.path.sep, '/')) + relpath = abspath[len(pp) :] + return '%s:%s' % ( + package_name(package), + relpath.replace(os.path.sep, '/'), + ) return abspath + # bw compat only; use pyramid.path.AssetResolver().resolve(spec).abspath() def abspath_from_asset_spec(spec, pname='__main__'): if pname is None: @@ -41,4 +43,3 @@ def abspath_from_asset_spec(spec, pname='__main__'): if pname is None: return filename return pkg_resources.resource_filename(pname, filename) - diff --git a/src/pyramid/authentication.py b/src/pyramid/authentication.py index a9604e336..f4c2b51ef 100644 --- a/src/pyramid/authentication.py +++ b/src/pyramid/authentication.py @@ -21,17 +21,11 @@ from pyramid.compat import ( bytes_, ascii_native_, native_, - ) +) -from pyramid.interfaces import ( - IAuthenticationPolicy, - IDebugLogger, - ) +from pyramid.interfaces import IAuthenticationPolicy, IDebugLogger -from pyramid.security import ( - Authenticated, - Everyone, - ) +from pyramid.security import Authenticated, Everyone from pyramid.util import strings_differ from pyramid.util import SimpleSerializer @@ -74,36 +68,41 @@ class CallbackAuthenticationPolicy(object): debug and self._log( 'call to unauthenticated_userid returned None; returning None', 'authenticated_userid', - request) + request, + ) return None if self._clean_principal(userid) is None: debug and self._log( - ('use of userid %r is disallowed by any built-in Pyramid ' - 'security policy, returning None' % userid), + ( + 'use of userid %r is disallowed by any built-in Pyramid ' + 'security policy, returning None' % userid + ), 'authenticated_userid', - request) + request, + ) return None if self.callback is None: debug and self._log( 'there was no groupfinder callback; returning %r' % (userid,), 'authenticated_userid', - request) + request, + ) return userid callback_ok = self.callback(userid, request) - if callback_ok is not None: # is not None! + if callback_ok is not None: # is not None! debug and self._log( - 'groupfinder callback returned %r; returning %r' % ( - callback_ok, userid), + 'groupfinder callback returned %r; returning %r' + % (callback_ok, userid), 'authenticated_userid', - request - ) + request, + ) return userid debug and self._log( 'groupfinder callback returned None; returning None', 'authenticated_userid', - request - ) + request, + ) def effective_principals(self, request): """ A list of effective principals derived from request. @@ -134,42 +133,45 @@ class CallbackAuthenticationPolicy(object): if userid is None: debug and self._log( - 'unauthenticated_userid returned %r; returning %r' % ( - userid, effective_principals), + 'unauthenticated_userid returned %r; returning %r' + % (userid, effective_principals), 'effective_principals', - request - ) + request, + ) return effective_principals if self._clean_principal(userid) is None: debug and self._log( - ('unauthenticated_userid returned disallowed %r; returning %r ' - 'as if it was None' % (userid, effective_principals)), + ( + 'unauthenticated_userid returned disallowed %r; returning %r ' + 'as if it was None' % (userid, effective_principals) + ), 'effective_principals', - request - ) + request, + ) return effective_principals if self.callback is None: debug and self._log( 'groupfinder callback is None, so groups is []', 'effective_principals', - request) + request, + ) groups = [] else: groups = self.callback(userid, request) debug and self._log( 'groupfinder callback returned %r as groups' % (groups,), 'effective_principals', - request) + request, + ) - if groups is None: # is None! + if groups is None: # is None! debug and self._log( - 'returning effective principals: %r' % ( - effective_principals,), + 'returning effective principals: %r' % (effective_principals,), 'effective_principals', - request - ) + request, + ) return effective_principals effective_principals.append(Authenticated) @@ -177,10 +179,9 @@ class CallbackAuthenticationPolicy(object): effective_principals.extend(groups) debug and self._log( - 'returning effective principals: %r' % ( - effective_principals,), + 'returning effective principals: %r' % (effective_principals,), 'effective_principals', - request + request, ) return effective_principals @@ -241,7 +242,8 @@ class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy): self.debug and self._log( 'repoze.who identity is None, returning None', 'authenticated_userid', - request) + request, + ) return None userid = identity['repoze.who.userid'] @@ -250,21 +252,25 @@ class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy): self.debug and self._log( 'repoze.who.userid is None, returning None' % userid, 'authenticated_userid', - request) + request, + ) return None if self._clean_principal(userid) is None: self.debug and self._log( - ('use of userid %r is disallowed by any built-in Pyramid ' - 'security policy, returning None' % userid), + ( + 'use of userid %r is disallowed by any built-in Pyramid ' + 'security policy, returning None' % userid + ), 'authenticated_userid', - request) + request, + ) return None if self.callback is None: return userid - if self.callback(identity, request) is not None: # is not None! + if self.callback(identity, request) is not None: # is not None! return userid def unauthenticated_userid(self, request): @@ -292,11 +298,13 @@ class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy): if identity is None: self.debug and self._log( - ('repoze.who identity was None; returning %r' % - effective_principals), + ( + 'repoze.who identity was None; returning %r' + % effective_principals + ), 'effective_principals', - request - ) + request, + ) return effective_principals if self.callback is None: @@ -304,33 +312,39 @@ class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy): else: groups = self.callback(identity, request) - if groups is None: # is None! + if groups is None: # is None! self.debug and self._log( - ('security policy groups callback returned None; returning %r' % - effective_principals), + ( + 'security policy groups callback returned None; returning %r' + % effective_principals + ), 'effective_principals', - request - ) + request, + ) return effective_principals userid = identity['repoze.who.userid'] if userid is None: self.debug and self._log( - ('repoze.who.userid was None; returning %r' % - effective_principals), + ( + 'repoze.who.userid was None; returning %r' + % effective_principals + ), 'effective_principals', - request - ) + request, + ) return effective_principals if self._clean_principal(userid) is None: self.debug and self._log( - ('unauthenticated_userid returned disallowed %r; returning %r ' - 'as if it was None' % (userid, effective_principals)), + ( + 'unauthenticated_userid returned disallowed %r; returning %r ' + 'as if it was None' % (userid, effective_principals) + ), 'effective_principals', - request - ) + request, + ) return effective_principals effective_principals.append(Authenticated) @@ -367,6 +381,7 @@ class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy): identity = self._get_identity(request) return identifier.forget(request.environ, identity) + @implementer(IAuthenticationPolicy) class RemoteUserAuthenticationPolicy(CallbackAuthenticationPolicy): """ A :app:`Pyramid` :term:`authentication policy` which @@ -419,6 +434,7 @@ class RemoteUserAuthenticationPolicy(CallbackAuthenticationPolicy): be done somewhere else or in a subclass.""" return [] + @implementer(IAuthenticationPolicy) class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy): """A :app:`Pyramid` :term:`authentication policy` which @@ -586,24 +602,25 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy): """ - def __init__(self, - secret, - callback=None, - cookie_name='auth_tkt', - secure=False, - include_ip=False, - timeout=None, - reissue_time=None, - max_age=None, - path="/", - http_only=False, - wild_domain=True, - debug=False, - hashalg='sha512', - parent_domain=False, - domain=None, - samesite='Lax', - ): + def __init__( + self, + secret, + callback=None, + cookie_name='auth_tkt', + secure=False, + include_ip=False, + timeout=None, + reissue_time=None, + max_age=None, + path="/", + http_only=False, + wild_domain=True, + debug=False, + hashalg='sha512', + parent_domain=False, + domain=None, + samesite='Lax', + ): self.cookie = AuthTktCookieHelper( secret, cookie_name=cookie_name, @@ -619,7 +636,7 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy): parent_domain=parent_domain, domain=domain, samesite=samesite, - ) + ) self.callback = callback self.debug = debug @@ -643,12 +660,15 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy): """ A list of headers which will delete appropriate cookies.""" return self.cookie.forget(request) + def b64encode(v): return base64.b64encode(bytes_(v)).strip().replace(b'\n', b'') + def b64decode(v): return base64.b64decode(bytes_(v)) + # this class licensed under the MIT license (stolen from Paste) class AuthTicket(object): """ @@ -670,9 +690,18 @@ class AuthTicket(object): """ - def __init__(self, secret, userid, ip, tokens=(), user_data='', - time=None, cookie_name='auth_tkt', secure=False, - hashalg='md5'): + def __init__( + self, + secret, + userid, + ip, + tokens=(), + user_data='', + time=None, + cookie_name='auth_tkt', + secure=False, + hashalg='md5', + ): self.secret = secret self.userid = userid self.ip = ip @@ -688,17 +717,27 @@ class AuthTicket(object): def digest(self): return calculate_digest( - self.ip, self.time, self.secret, self.userid, self.tokens, - self.user_data, self.hashalg) + self.ip, + self.time, + self.secret, + self.userid, + self.tokens, + self.user_data, + self.hashalg, + ) def cookie_value(self): - v = '%s%08x%s!' % (self.digest(), int(self.time), - url_quote(self.userid)) + v = '%s%08x%s!' % ( + self.digest(), + int(self.time), + url_quote(self.userid), + ) if self.tokens: v += self.tokens + '!' v += self.user_data return v + # this class licensed under the MIT license (stolen from Paste) class BadTicket(Exception): """ @@ -706,10 +745,12 @@ class BadTicket(Exception): determine what the expected digest should have been, expected is set. This should not be shown by default, but can be useful for debugging. """ + def __init__(self, msg, expected=None): self.expected = expected Exception.__init__(self, msg) + # this function licensed under the MIT license (stolen from Paste) def parse_ticket(secret, ticket, ip, hashalg='md5'): """ @@ -722,37 +763,41 @@ def parse_ticket(secret, ticket, ip, hashalg='md5'): digest_size = hashlib.new(hashalg).digest_size * 2 digest = ticket[:digest_size] try: - timestamp = int(ticket[digest_size:digest_size + 8], 16) + timestamp = int(ticket[digest_size : digest_size + 8], 16) except ValueError as e: raise BadTicket('Timestamp is not a hex integer: %s' % e) try: - userid, data = ticket[digest_size + 8:].split('!', 1) + userid, data = ticket[digest_size + 8 :].split('!', 1) except ValueError: raise BadTicket('userid is not followed by !') userid = url_unquote(userid) if '!' in data: tokens, user_data = data.split('!', 1) - else: # pragma: no cover (never generated) + else: # pragma: no cover (never generated) # @@: Is this the right order? tokens = '' user_data = data - expected = calculate_digest(ip, timestamp, secret, - userid, tokens, user_data, hashalg) + expected = calculate_digest( + ip, timestamp, secret, userid, tokens, user_data, hashalg + ) # Avoid timing attacks (see # http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf) if strings_differ(expected, digest): - raise BadTicket('Digest signature is not correct', - expected=(expected, digest)) + raise BadTicket( + 'Digest signature is not correct', expected=(expected, digest) + ) tokens = tokens.split(',') return (timestamp, userid, tokens, user_data) + # this function licensed under the MIT license (stolen from Paste) -def calculate_digest(ip, timestamp, secret, userid, tokens, user_data, - hashalg='md5'): +def calculate_digest( + ip, timestamp, secret, userid, tokens, user_data, hashalg='md5' +): secret = bytes_(secret, 'utf-8') userid = bytes_(userid, 'utf-8') tokens = bytes_(tokens, 'utf-8') @@ -767,24 +812,29 @@ def calculate_digest(ip, timestamp, secret, userid, tokens, user_data, # encode_ip_timestamp not required, left in for backwards compatibility ip_timestamp = encode_ip_timestamp(ip, timestamp) - hash_obj.update(ip_timestamp + secret + userid + b'\0' + - tokens + b'\0' + user_data) + hash_obj.update( + ip_timestamp + secret + userid + b'\0' + tokens + b'\0' + user_data + ) digest = hash_obj.hexdigest() hash_obj2 = hashlib.new(hashalg) hash_obj2.update(bytes_(digest) + secret) return hash_obj2.hexdigest() + # this function licensed under the MIT license (stolen from Paste) def encode_ip_timestamp(ip, timestamp): ip_chars = ''.join(map(chr, map(int, ip.split('.')))) t = int(timestamp) - ts = ((t & 0xff000000) >> 24, - (t & 0xff0000) >> 16, - (t & 0xff00) >> 8, - t & 0xff) + ts = ( + (t & 0xFF000000) >> 24, + (t & 0xFF0000) >> 16, + (t & 0xFF00) >> 8, + t & 0xFF, + ) ts_chars = ''.join(map(chr, ts)) return bytes_(ip_chars + ts_chars) + class AuthTktCookieHelper(object): """ A helper class for use in third-party authentication policy @@ -792,41 +842,43 @@ class AuthTktCookieHelper(object): :class:`pyramid.authentication.AuthTktAuthenticationPolicy` for the meanings of the constructor arguments. """ - parse_ticket = staticmethod(parse_ticket) # for tests - AuthTicket = AuthTicket # for tests - BadTicket = BadTicket # for tests - now = None # for tests + + parse_ticket = staticmethod(parse_ticket) # for tests + AuthTicket = AuthTicket # for tests + BadTicket = BadTicket # for tests + now = None # for tests userid_type_decoders = { - 'int':int, - 'unicode':lambda x: utf_8_decode(x)[0], # bw compat for old cookies + 'int': int, + 'unicode': lambda x: utf_8_decode(x)[0], # bw compat for old cookies 'b64unicode': lambda x: utf_8_decode(b64decode(x))[0], 'b64str': lambda x: b64decode(x), - } + } userid_type_encoders = { int: ('int', str), long: ('int', str), text_type: ('b64unicode', lambda x: b64encode(utf_8_encode(x)[0])), binary_type: ('b64str', lambda x: b64encode(x)), - } - - def __init__(self, - secret, - cookie_name='auth_tkt', - secure=False, - include_ip=False, - timeout=None, - reissue_time=None, - max_age=None, - http_only=False, - path="/", - wild_domain=True, - hashalg='md5', - parent_domain=False, - domain=None, - samesite='Lax', - ): + } + + def __init__( + self, + secret, + cookie_name='auth_tkt', + secure=False, + include_ip=False, + timeout=None, + reissue_time=None, + max_age=None, + http_only=False, + path="/", + wild_domain=True, + hashalg='md5', + parent_domain=False, + domain=None, + samesite='Lax', + ): serializer = SimpleSerializer() @@ -845,7 +897,9 @@ class AuthTktCookieHelper(object): self.secure = secure self.include_ip = include_ip self.timeout = timeout if timeout is None else int(timeout) - self.reissue_time = reissue_time if reissue_time is None else int(reissue_time) + self.reissue_time = ( + reissue_time if reissue_time is None else int(reissue_time) + ) self.max_age = max_age if max_age is None else int(max_age) self.wild_domain = wild_domain self.parent_domain = parent_domain @@ -893,16 +947,17 @@ class AuthTktCookieHelper(object): try: timestamp, userid, tokens, user_data = self.parse_ticket( - self.secret, cookie, remote_addr, self.hashalg) + self.secret, cookie, remote_addr, self.hashalg + ) except self.BadTicket: return None - now = self.now # service tests + now = self.now # service tests if now is None: now = time_mod.time() - if self.timeout and ( (timestamp + self.timeout) < now ): + if self.timeout and ((timestamp + self.timeout) < now): # the auth_tkt data has expired return None @@ -910,7 +965,7 @@ class AuthTktCookieHelper(object): user_data_info = user_data.split('|') for datum in filter(None, user_data_info): if datum.startswith(userid_typename): - userid_type = datum[len(userid_typename):] + userid_type = datum[len(userid_typename) :] decoder = self.userid_type_decoders.get(userid_type) if decoder: userid = decoder(userid) @@ -918,15 +973,18 @@ class AuthTktCookieHelper(object): reissue = self.reissue_time is not None if reissue and not hasattr(request, '_authtkt_reissued'): - if ( (now - timestamp) > self.reissue_time ): + if (now - timestamp) > self.reissue_time: # See https://github.com/Pylons/pyramid/issues#issue/108 tokens = list(filter(None, tokens)) - headers = self.remember(request, userid, max_age=self.max_age, - tokens=tokens) + headers = self.remember( + request, userid, max_age=self.max_age, tokens=tokens + ) + def reissue_authtkt(request, response): if not hasattr(request, '_authtkt_reissue_revoked'): for k, v in headers: response.headerlist.append((k, v)) + request.add_response_callback(reissue_authtkt) request._authtkt_reissued = True @@ -987,7 +1045,8 @@ class AuthTktCookieHelper(object): "AuthTktAuthenticationPolicy. Explicitly converting to string " "and storing as base64. Subsequent requests will receive a " "string as the userid, it will not be decoded back to the type " - "provided.".format(type(userid)), RuntimeWarning + "provided.".format(type(userid)), + RuntimeWarning, ) encoding, encoder = self.userid_type_encoders.get(text_type) userid = str(userid) @@ -1018,12 +1077,13 @@ class AuthTktCookieHelper(object): user_data=user_data, cookie_name=self.cookie_name, secure=self.secure, - hashalg=self.hashalg - ) + hashalg=self.hashalg, + ) cookie_value = ticket.cookie_value() return self._get_cookies(request, cookie_value, max_age) + @implementer(IAuthenticationPolicy) class SessionAuthenticationPolicy(CallbackAuthenticationPolicy): """ A :app:`Pyramid` authentication policy which gets its data from the @@ -1123,6 +1183,7 @@ class BasicAuthAuthenticationPolicy(CallbackAuthenticationPolicy): return response return HTTPForbidden() """ + def __init__(self, check, realm='Realm', debug=False): self.check = check self.realm = realm @@ -1158,7 +1219,8 @@ class BasicAuthAuthenticationPolicy(CallbackAuthenticationPolicy): HTTPBasicCredentials = namedtuple( - 'HTTPBasicCredentials', ['username', 'password']) + 'HTTPBasicCredentials', ['username', 'password'] +) def extract_http_basic_credentials(request): @@ -1183,7 +1245,7 @@ def extract_http_basic_credentials(request): try: authbytes = b64decode(auth.strip()) - except (TypeError, binascii.Error): # can't decode + except (TypeError, binascii.Error): # can't decode return None # try utf-8 first, then latin-1; see discussion in @@ -1195,7 +1257,7 @@ def extract_http_basic_credentials(request): try: username, password = auth.split(':', 1) - except ValueError: # not enough values to unpack + except ValueError: # not enough values to unpack return None return HTTPBasicCredentials(username, password) diff --git a/src/pyramid/authorization.py b/src/pyramid/authorization.py index 4845762ef..974748765 100644 --- a/src/pyramid/authorization.py +++ b/src/pyramid/authorization.py @@ -6,13 +6,8 @@ from pyramid.location import lineage from pyramid.compat import is_nonstr_iter -from pyramid.security import ( - ACLAllowed, - ACLDenied, - Allow, - Deny, - Everyone, - ) +from pyramid.security import ACLAllowed, ACLDenied, Allow, Deny, Everyone + @implementer(IAuthorizationPolicy) class ACLAuthorizationPolicy(object): @@ -90,20 +85,19 @@ class ACLAuthorizationPolicy(object): ace_permissions = [ace_permissions] if permission in ace_permissions: if ace_action == Allow: - return ACLAllowed(ace, acl, permission, - principals, location) + return ACLAllowed( + ace, acl, permission, principals, location + ) else: - return ACLDenied(ace, acl, permission, - principals, location) + return ACLDenied( + ace, acl, permission, principals, location + ) # default deny (if no ACL in lineage at all, or if none of the # principals were mentioned in any ACE we found) return ACLDenied( - '<default deny>', - acl, - permission, - principals, - context) + '<default deny>', acl, permission, principals, context + ) def principals_allowed_by_permission(self, context, permission): """ Return the set of principals explicitly granted the @@ -132,14 +126,14 @@ class ACLAuthorizationPolicy(object): if ace_principal not in denied_here: allowed_here.add(ace_principal) if (ace_action == Deny) and (permission in ace_permissions): - denied_here.add(ace_principal) - if ace_principal == Everyone: - # clear the entire allowed set, as we've hit a - # deny of Everyone ala (Deny, Everyone, ALL) - allowed = set() - break - elif ace_principal in allowed: - allowed.remove(ace_principal) + denied_here.add(ace_principal) + if ace_principal == Everyone: + # clear the entire allowed set, as we've hit a + # deny of Everyone ala (Deny, Everyone, ALL) + allowed = set() + break + elif ace_principal in allowed: + allowed.remove(ace_principal) allowed.update(allowed_here) diff --git a/src/pyramid/compat.py b/src/pyramid/compat.py index a7f9c1287..4df279e94 100644 --- a/src/pyramid/compat.py +++ b/src/pyramid/compat.py @@ -7,6 +7,7 @@ WIN = platform.system() == 'Windows' try: # pragma: no cover import __pypy__ + PYPY = True except: # pragma: no cover __pypy__ = None @@ -27,20 +28,21 @@ PY2 = sys.version_info[0] == 2 PY3 = sys.version_info[0] == 3 if PY2: - string_types = basestring, + string_types = (basestring,) integer_types = (int, long) class_types = (type, types.ClassType) text_type = unicode binary_type = str long = long else: - string_types = str, - integer_types = int, - class_types = type, + string_types = (str,) + integer_types = (int,) + class_types = (type,) text_type = str binary_type = bytes long = int + def text_(s, encoding='latin-1', errors='strict'): """ If ``s`` is an instance of ``binary_type``, return ``s.decode(encoding, errors)``, otherwise return ``s``""" @@ -48,6 +50,7 @@ def text_(s, encoding='latin-1', errors='strict'): return s.decode(encoding, errors) return s + def bytes_(s, encoding='latin-1', errors='strict'): """ If ``s`` is an instance of ``text_type``, return ``s.encode(encoding, errors)``, otherwise return ``s``""" @@ -55,17 +58,23 @@ def bytes_(s, encoding='latin-1', errors='strict'): return s.encode(encoding, errors) return s + if PY2: + def ascii_native_(s): if isinstance(s, text_type): s = s.encode('ascii') return str(s) + + else: + def ascii_native_(s): if isinstance(s, text_type): s = s.encode('ascii') return str(s, 'ascii', 'strict') + ascii_native_.__doc__ = """ Python 3: If ``s`` is an instance of ``text_type``, return ``s.encode('ascii')``, otherwise return ``str(s, 'ascii', 'strict')`` @@ -76,13 +85,17 @@ Python 2: If ``s`` is an instance of ``text_type``, return if PY2: + def native_(s, encoding='latin-1', errors='strict'): """ If ``s`` is an instance of ``text_type``, return ``s.encode(encoding, errors)``, otherwise return ``str(s)``""" if isinstance(s, text_type): return s.encode(encoding, errors) return str(s) + + else: + def native_(s, encoding='latin-1', errors='strict'): """ If ``s`` is an instance of ``text_type``, return ``s``, otherwise return ``str(s, encoding, errors)``""" @@ -90,6 +103,7 @@ else: return s return str(s, encoding, errors) + native_.__doc__ = """ Python 3: If ``s`` is an instance of ``text_type``, return ``s``, otherwise return ``str(s, encoding, errors)`` @@ -106,25 +120,34 @@ if PY2: from urllib import urlencode as url_encode from urllib2 import urlopen as url_open - def url_unquote_text(v, encoding='utf-8', errors='replace'): # pragma: no cover + def url_unquote_text( + v, encoding='utf-8', errors='replace' + ): # pragma: no cover v = url_unquote(v) return v.decode(encoding, errors) - def url_unquote_native(v, encoding='utf-8', errors='replace'): # pragma: no cover + def url_unquote_native( + v, encoding='utf-8', errors='replace' + ): # pragma: no cover return native_(url_unquote_text(v, encoding, errors)) + + else: from urllib import parse + urlparse = parse from urllib.parse import quote as url_quote from urllib.parse import quote_plus as url_quote_plus from urllib.parse import unquote as url_unquote from urllib.parse import urlencode as url_encode from urllib.request import urlopen as url_open + url_unquote_text = url_unquote url_unquote_native = url_unquote if PY2: # pragma: no cover + def exec_(code, globs=None, locs=None): """Execute code in a namespace.""" if globs is None: @@ -137,12 +160,15 @@ if PY2: # pragma: no cover locs = globs exec("""exec code in globs, locs""") - exec_("""def reraise(tp, value, tb=None): + exec_( + """def reraise(tp, value, tb=None): raise tp, value, tb -""") +""" + ) else: # pragma: no cover import builtins + exec_ = getattr(builtins, "exec") def reraise(tp, value, tb=None): @@ -156,6 +182,7 @@ else: # pragma: no cover if PY2: # pragma: no cover + def iteritems_(d): return d.iteritems() @@ -164,7 +191,10 @@ if PY2: # pragma: no cover def iterkeys_(d): return d.iterkeys() + + else: # pragma: no cover + def iteritems_(d): return d.items() @@ -178,18 +208,25 @@ else: # pragma: no cover if PY2: map_ = map else: + def map_(*arg): return list(map(*arg)) + if PY2: + def is_nonstr_iter(v): return hasattr(v, '__iter__') + + else: + def is_nonstr_iter(v): if isinstance(v, str): return False return hasattr(v, '__iter__') + if PY2: im_func = 'im_func' im_self = 'im_self' @@ -227,21 +264,27 @@ else: import json if PY2: + def decode_path_info(path): return path.decode('utf-8') + + else: # see PEP 3333 for why we encode WSGI PATH_INFO to latin-1 before # decoding it to utf-8 def decode_path_info(path): return path.encode('latin-1').decode('utf-8') + if PY2: from urlparse import unquote as unquote_to_bytes def unquote_bytes_to_wsgi(bytestring): return unquote_to_bytes(bytestring) + + else: - # see PEP 3333 for why we decode the path to latin-1 + # see PEP 3333 for why we decode the path to latin-1 from urllib.parse import unquote_to_bytes def unquote_bytes_to_wsgi(bytestring): @@ -251,6 +294,7 @@ else: def is_bound_method(ob): return inspect.ismethod(ob) and getattr(ob, im_self, None) is not None + # support annotations and keyword-only arguments in PY3 if PY2: from inspect import getargspec @@ -262,6 +306,7 @@ if PY2: else: from itertools import zip_longest + def is_unbound_method(fn): """ This consistently verifies that the callable is bound to a diff --git a/src/pyramid/config/__init__.py b/src/pyramid/config/__init__.py index 2f4e133f0..ab5edfefe 100644 --- a/src/pyramid/config/__init__.py +++ b/src/pyramid/config/__init__.py @@ -17,17 +17,13 @@ from pyramid.interfaces import ( PHASE1_CONFIG, PHASE2_CONFIG, PHASE3_CONFIG, - ) +) from pyramid.asset import resolve_asset_spec from pyramid.authorization import ACLAuthorizationPolicy -from pyramid.compat import ( - text_, - reraise, - string_types, - ) +from pyramid.compat import text_, reraise, string_types from pyramid.events import ApplicationCreated @@ -35,21 +31,13 @@ from pyramid.exceptions import ( ConfigurationConflictError, ConfigurationError, ConfigurationExecutionError, - ) +) from pyramid.httpexceptions import default_exceptionresponse_view -from pyramid.path import ( - caller_package, - package_of, - ) +from pyramid.path import caller_package, package_of -from pyramid.registry import ( - Introspectable, - Introspector, - Registry, - undefer, - ) +from pyramid.registry import Introspectable, Introspector, Registry, undefer from pyramid.router import Router @@ -57,17 +45,9 @@ from pyramid.settings import aslist from pyramid.threadlocal import manager -from pyramid.util import ( - WeakOrderedSet, - object_description, - ) +from pyramid.util import WeakOrderedSet, object_description -from pyramid.config.util import ( - ActionInfo, - PredicateList, - action_method, - not_, -) +from pyramid.config.util import ActionInfo, PredicateList, action_method, not_ from pyramid.config.adapters import AdaptersConfiguratorMixin from pyramid.config.assets import AssetsConfiguratorMixin @@ -94,6 +74,7 @@ PHASE1_CONFIG = PHASE1_CONFIG # api PHASE2_CONFIG = PHASE2_CONFIG # api PHASE3_CONFIG = PHASE3_CONFIG # api + class Configurator( TestingConfiguratorMixin, TweensConfiguratorMixin, @@ -107,7 +88,7 @@ class Configurator( SettingsConfiguratorMixin, FactoriesConfiguratorMixin, AdaptersConfiguratorMixin, - ): +): """ A Configurator is used to configure a :app:`Pyramid` :term:`application registry`. @@ -284,8 +265,9 @@ class Configurator( ``with``-statement to make threadlocal configuration available for further configuration with an implicit commit. """ - manager = manager # for testing injection - venusian = venusian # for testing injection + + manager = manager # for testing injection + venusian = venusian # for testing injection _ainfo = None basepath = None includepath = () @@ -294,27 +276,28 @@ class Configurator( introspectable = Introspectable inspect = inspect - def __init__(self, - registry=None, - package=None, - settings=None, - root_factory=None, - authentication_policy=None, - authorization_policy=None, - renderers=None, - debug_logger=None, - locale_negotiator=None, - request_factory=None, - response_factory=None, - default_permission=None, - session_factory=None, - default_view_mapper=None, - autocommit=False, - exceptionresponse_view=default_exceptionresponse_view, - route_prefix=None, - introspection=True, - root_package=None, - ): + def __init__( + self, + registry=None, + package=None, + settings=None, + root_factory=None, + authentication_policy=None, + authorization_policy=None, + renderers=None, + debug_logger=None, + locale_negotiator=None, + request_factory=None, + response_factory=None, + default_permission=None, + session_factory=None, + default_view_mapper=None, + autocommit=False, + exceptionresponse_view=default_exceptionresponse_view, + route_prefix=None, + introspection=True, + root_package=None, + ): if package is None: package = caller_package() if root_package is None: @@ -345,23 +328,24 @@ class Configurator( session_factory=session_factory, default_view_mapper=default_view_mapper, exceptionresponse_view=exceptionresponse_view, - ) + ) - def setup_registry(self, - settings=None, - root_factory=None, - authentication_policy=None, - authorization_policy=None, - renderers=None, - debug_logger=None, - locale_negotiator=None, - request_factory=None, - response_factory=None, - default_permission=None, - session_factory=None, - default_view_mapper=None, - exceptionresponse_view=default_exceptionresponse_view, - ): + def setup_registry( + self, + settings=None, + root_factory=None, + authentication_policy=None, + authorization_policy=None, + renderers=None, + debug_logger=None, + locale_negotiator=None, + request_factory=None, + response_factory=None, + default_permission=None, + session_factory=None, + default_view_mapper=None, + exceptionresponse_view=default_exceptionresponse_view, + ): """ When you pass a non-``None`` ``registry`` argument to the :term:`Configurator` constructor, no initial setup is performed against the registry. This is because the registry you pass in may @@ -404,7 +388,9 @@ class Configurator( if exceptionresponse_view is not None: exceptionresponse_view = self.maybe_dotted(exceptionresponse_view) self.add_view(exceptionresponse_view, context=IExceptionResponse) - self.add_view(exceptionresponse_view,context=WebobWSGIHTTPException) + self.add_view( + exceptionresponse_view, context=WebobWSGIHTTPException + ) # commit below because: # @@ -424,7 +410,7 @@ class Configurator( # automatic conflict resolution. if authentication_policy and not authorization_policy: - authorization_policy = ACLAuthorizationPolicy() # default + authorization_policy = ACLAuthorizationPolicy() # default if authorization_policy: self.set_authorization_policy(authorization_policy) @@ -468,7 +454,7 @@ class Configurator( def _make_spec(self, path_or_spec): package, filename = resolve_asset_spec(path_or_spec, self.package_name) if package is None: - return filename # absolute filename + return filename # absolute filename return '%s:%s' % (package, filename) def _fix_registry(self): @@ -480,38 +466,55 @@ class Configurator( _registry = self.registry if not hasattr(_registry, 'notify'): + def notify(*events): - [ _ for _ in _registry.subscribers(events, None) ] + [_ for _ in _registry.subscribers(events, None)] + _registry.notify = notify if not hasattr(_registry, 'has_listeners'): _registry.has_listeners = True if not hasattr(_registry, 'queryAdapterOrSelf'): + def queryAdapterOrSelf(object, interface, default=None): if not interface.providedBy(object): - return _registry.queryAdapter(object, interface, - default=default) + return _registry.queryAdapter( + object, interface, default=default + ) return object + _registry.queryAdapterOrSelf = queryAdapterOrSelf if not hasattr(_registry, 'registerSelfAdapter'): - def registerSelfAdapter(required=None, provided=None, - name=empty, info=empty, event=True): - return _registry.registerAdapter(lambda x: x, - required=required, - provided=provided, name=name, - info=info, event=event) + + def registerSelfAdapter( + required=None, + provided=None, + name=empty, + info=empty, + event=True, + ): + return _registry.registerAdapter( + lambda x: x, + required=required, + provided=provided, + name=name, + info=info, + event=event, + ) + _registry.registerSelfAdapter = registerSelfAdapter if not hasattr(_registry, '_lock'): _registry._lock = threading.Lock() if not hasattr(_registry, '_clear_view_lookup_cache'): + def _clear_view_lookup_cache(): _registry._view_lookup_cache = {} - _registry._clear_view_lookup_cache = _clear_view_lookup_cache + _registry._clear_view_lookup_cache = _clear_view_lookup_cache # API @@ -530,7 +533,7 @@ class Configurator( introspector = property( _get_introspector, _set_introspector, _del_introspector - ) + ) def get_predlist(self, name): predlist = self.registry.queryUtility(IPredicateList, name=name) @@ -539,30 +542,41 @@ class Configurator( self.registry.registerUtility(predlist, IPredicateList, name=name) return predlist - - def _add_predicate(self, type, name, factory, weighs_more_than=None, - weighs_less_than=None): + def _add_predicate( + self, type, name, factory, weighs_more_than=None, weighs_less_than=None + ): factory = self.maybe_dotted(factory) discriminator = ('%s option' % type, name) intr = self.introspectable( '%s predicates' % type, discriminator, '%s predicate named %s' % (type, name), - '%s predicate' % type) + '%s predicate' % type, + ) intr['name'] = name intr['factory'] = factory intr['weighs_more_than'] = weighs_more_than intr['weighs_less_than'] = weighs_less_than + def register(): predlist = self.get_predlist(type) - predlist.add(name, factory, weighs_more_than=weighs_more_than, - weighs_less_than=weighs_less_than) - self.action(discriminator, register, introspectables=(intr,), - order=PHASE1_CONFIG) # must be registered early + predlist.add( + name, + factory, + weighs_more_than=weighs_more_than, + weighs_less_than=weighs_less_than, + ) + + self.action( + discriminator, + register, + introspectables=(intr,), + order=PHASE1_CONFIG, + ) # must be registered early @property def action_info(self): - info = self.info # usually a ZCML action (ParserInfo) if self.info + info = self.info # usually a ZCML action (ParserInfo) if self.info if not info: # Try to provide more accurate info for conflict reports if self._ainfo: @@ -571,8 +585,16 @@ class Configurator( info = ActionInfo(None, 0, '', '') return info - def action(self, discriminator, callable=None, args=(), kw=None, order=0, - introspectables=(), **extra): + def action( + self, + discriminator, + callable=None, + args=(), + kw=None, + order=0, + introspectables=(), + **extra + ): """ Register an action which will be executed when :meth:`pyramid.config.Configurator.commit` is called (or executed immediately if ``autocommit`` is ``True``). @@ -607,7 +629,7 @@ class Configurator( """ # catch nonhashable discriminators here; most unit tests use # autocommit=False, which won't catch unhashable discriminators - assert hash(discriminator) + assert hash(discriminator) if kw is None: kw = {} @@ -645,8 +667,8 @@ class Configurator( info=action_info, includepath=self.includepath, introspectables=introspectables, - ) ) + ) self.action_state.action(**action) def _get_action_state(self): @@ -663,7 +685,7 @@ class Configurator( action_state = property(_get_action_state, _set_action_state) - _ctx = action_state # bw compat + _ctx = action_state # bw compat def commit(self): """ @@ -687,7 +709,7 @@ class Configurator( self.action_state.execute_actions(introspector=self.introspector) finally: self.end() - self.action_state = ActionState() # old actions have been processed + self.action_state = ActionState() # old actions have been processed def include(self, callable, route_prefix=None): """Include a configuration callable, to support imperative @@ -800,17 +822,18 @@ class Configurator( c = getattr(module, 'includeme') except AttributeError: raise ConfigurationError( - "module %r has no attribute 'includeme'" % (module.__name__) - ) - + "module %r has no attribute 'includeme'" + % (module.__name__) + ) + spec = module.__name__ + ':' + c.__name__ sourcefile = self.inspect.getsourcefile(c) if sourcefile is None: raise ConfigurationError( 'No source file for module %r (.py file must exist, ' - 'refusing to use orphan .pyc or .pyo file).' % module.__name__) - + 'refusing to use orphan .pyc or .pyo file).' % module.__name__ + ) if action_state.processSpec(spec): with self.route_prefix_context(route_prefix): @@ -820,7 +843,7 @@ class Configurator( root_package=self.root_package, autocommit=self.autocommit, route_prefix=self.route_prefix, - ) + ) configurator.basepath = os.path.dirname(sourcefile) configurator.includepath = self.includepath + (spec,) @@ -886,7 +909,7 @@ class Configurator( autocommit=self.autocommit, route_prefix=self.route_prefix, introspection=self.introspection, - ) + ) configurator.basepath = self.basepath configurator.includepath = self.includepath configurator.info = self.info @@ -914,7 +937,7 @@ class Configurator( return relative_spec return self._make_spec(relative_spec) - absolute_resource_spec = absolute_asset_spec # b/w compat forever + absolute_resource_spec = absolute_asset_spec # b/w compat forever def begin(self, request=_marker): """ Indicate that application or test configuration has begun. @@ -940,7 +963,7 @@ class Configurator( request = current['request'] else: request = None - self.manager.push({'registry':self.registry, 'request':request}) + self.manager.push({'registry': self.registry, 'request': request}) def end(self): """ Indicate that application or test configuration has ended. @@ -961,8 +984,9 @@ class Configurator( self.commit() # this is *not* an action method (uses caller_package) - def scan(self, package=None, categories=None, onerror=None, ignore=None, - **kw): + def scan( + self, package=None, categories=None, onerror=None, ignore=None, **kw + ): """Scan a Python package and any of its subpackages for objects marked with :term:`configuration decoration` such as :class:`pyramid.view.view_config`. Any decorated object found will @@ -1021,7 +1045,7 @@ class Configurator( """ package = self.maybe_dotted(package) - if package is None: # pragma: no cover + if package is None: # pragma: no cover package = caller_package() ctorkw = {'config': self} @@ -1029,8 +1053,9 @@ class Configurator( scanner = self.venusian.Scanner(**ctorkw) - scanner.scan(package, categories=categories, onerror=onerror, - ignore=ignore) + scanner.scan( + package, categories=categories, onerror=onerror, ignore=ignore + ) def make_wsgi_app(self): """ Commits any pending configuration statements, sends a @@ -1079,8 +1104,18 @@ class ActionState(object): self._seen_files.add(spec) return True - def action(self, discriminator, callable=None, args=(), kw=None, order=0, - includepath=(), info=None, introspectables=(), **extra): + def action( + self, + discriminator, + callable=None, + args=(), + kw=None, + order=0, + includepath=(), + info=None, + introspectables=(), + **extra + ): """Add an action with the given discriminator, callable and arguments """ if kw is None: @@ -1096,8 +1131,8 @@ class ActionState(object): info=info, order=order, introspectables=introspectables, - ) ) + ) self.actions.append(action) def execute_actions(self, clear=True, introspector=None): @@ -1179,8 +1214,7 @@ class ActionState(object): if self.actions: all_actions.extend(self.actions) action_iter = resolveConflicts( - self.actions, - state=conflict_state, + self.actions, state=conflict_state ) self.actions = [] @@ -1203,9 +1237,11 @@ class ActionState(object): except Exception: t, v, tb = sys.exc_info() try: - reraise(ConfigurationExecutionError, - ConfigurationExecutionError(t, v, info), - tb) + reraise( + ConfigurationExecutionError, + ConfigurationExecutionError(t, v, info), + tb, + ) finally: del t, v, tb @@ -1290,9 +1326,12 @@ def resolveConflicts(actions, state=None): # error out if we went backward in order if state.min_order is not None and order < state.min_order: - r = ['Actions were added to order={0} after execution had moved ' - 'on to order={1}. Conflicting actions: ' - .format(order, state.min_order)] + r = [ + 'Actions were added to order={0} after execution had moved ' + 'on to order={1}. Conflicting actions: '.format( + order, state.min_order + ) + ] for i, action in actiongroup: for line in str(action['info']).rstrip().split('\n'): r.append(" " + line) @@ -1348,8 +1387,10 @@ def resolveConflicts(actions, state=None): # if the new action conflicts with the resolved action then # note the conflict, otherwise drop the action as it's # effectively overriden by the previous action - if (includepath[:len(basepath)] != basepath or - includepath == basepath): + if ( + includepath[: len(basepath)] != basepath + or includepath == basepath + ): L = conflicts.setdefault(discriminator, [baseinfo]) L.append(action['info']) @@ -1360,8 +1401,10 @@ def resolveConflicts(actions, state=None): for _, action in rest: includepath = action['includepath'] # Test whether path is a prefix of opath - if (includepath[:len(basepath)] != basepath or # not a prefix - includepath == basepath): + if ( + includepath[: len(basepath)] != basepath + or includepath == basepath # not a prefix + ): L = conflicts.setdefault(discriminator, [baseinfo]) L.append(action['info']) @@ -1389,8 +1432,14 @@ def normalize_actions(actions): def expand_action_tuple( - discriminator, callable=None, args=(), kw=None, includepath=(), - info=None, order=0, introspectables=(), + discriminator, + callable=None, + args=(), + kw=None, + includepath=(), + info=None, + order=0, + introspectables=(), ): if kw is None: kw = {} @@ -1403,7 +1452,7 @@ def expand_action_tuple( info=info, order=order, introspectables=introspectables, - ) + ) global_registries = WeakOrderedSet() diff --git a/src/pyramid/config/adapters.py b/src/pyramid/config/adapters.py index 945faa3c6..e5668c40e 100644 --- a/src/pyramid/config/adapters.py +++ b/src/pyramid/config/adapters.py @@ -4,11 +4,7 @@ from functools import update_wrapper from zope.interface import Interface -from pyramid.interfaces import ( - IResponse, - ITraverser, - IResourceURL, - ) +from pyramid.interfaces import IResponse, ITraverser, IResourceURL from pyramid.util import takes_one_arg @@ -53,33 +49,33 @@ class AdaptersConfiguratorMixin(object): predlist = self.get_predlist('subscriber') order, preds, phash = predlist.make(self, **predicates) - derived_predicates = [ self._derive_predicate(p) for p in preds ] + derived_predicates = [self._derive_predicate(p) for p in preds] derived_subscriber = self._derive_subscriber( - subscriber, - derived_predicates, - ) + subscriber, derived_predicates + ) intr.update( - {'phash':phash, - 'order':order, - 'predicates':preds, - 'derived_predicates':derived_predicates, - 'derived_subscriber':derived_subscriber, - } - ) + { + 'phash': phash, + 'order': order, + 'predicates': preds, + 'derived_predicates': derived_predicates, + 'derived_subscriber': derived_subscriber, + } + ) self.registry.registerHandler(derived_subscriber, iface) - + intr = self.introspectable( 'subscribers', id(subscriber), self.object_description(subscriber), - 'subscriber' - ) - + 'subscriber', + ) + intr['subscriber'] = subscriber intr['interfaces'] = iface - + self.action(None, register, introspectables=(intr,)) return subscriber @@ -87,8 +83,10 @@ class AdaptersConfiguratorMixin(object): derived_predicate = predicate if eventonly(predicate): + def derived_predicate(*arg): return predicate(arg[0]) + # seems pointless to try to fix __doc__, __module__, etc as # predicate will invariably be an instance @@ -98,8 +96,10 @@ class AdaptersConfiguratorMixin(object): derived_subscriber = subscriber if eventonly(subscriber): + def derived_subscriber(*arg): return subscriber(arg[0]) + if hasattr(subscriber, '__name__'): update_wrapper(derived_subscriber, subscriber) @@ -132,10 +132,11 @@ class AdaptersConfiguratorMixin(object): update_wrapper(subscriber_wrapper, subscriber) return subscriber_wrapper - + @action_method - def add_subscriber_predicate(self, name, factory, weighs_more_than=None, - weighs_less_than=None): + def add_subscriber_predicate( + self, name, factory, weighs_more_than=None, weighs_less_than=None + ): """ .. versionadded:: 1.4 @@ -159,8 +160,8 @@ class AdaptersConfiguratorMixin(object): name, factory, weighs_more_than=weighs_more_than, - weighs_less_than=weighs_less_than - ) + weighs_less_than=weighs_less_than, + ) @action_method def add_response_adapter(self, adapter, type_or_iface): @@ -178,18 +179,21 @@ class AdaptersConfiguratorMixin(object): See :ref:`using_iresponse` for more information.""" adapter = self.maybe_dotted(adapter) type_or_iface = self.maybe_dotted(type_or_iface) + def register(): reg = self.registry if adapter is None: reg.registerSelfAdapter((type_or_iface,), IResponse) else: reg.registerAdapter(adapter, (type_or_iface,), IResponse) + discriminator = (IResponse, type_or_iface) intr = self.introspectable( 'response adapters', discriminator, self.object_description(adapter), - 'response adapter') + 'response adapter', + ) intr['adapter'] = adapter intr['type'] = type_or_iface self.action(discriminator, register, introspectables=(intr,)) @@ -255,17 +259,19 @@ class AdaptersConfiguratorMixin(object): """ iface = self.maybe_dotted(iface) adapter = self.maybe_dotted(adapter) + def register(iface=iface): if iface is None: iface = Interface self.registry.registerAdapter(adapter, (iface,), ITraverser) + discriminator = ('traverser', iface) intr = self.introspectable( - 'traversers', + 'traversers', discriminator, 'traverser for %r' % iface, 'traverser', - ) + ) intr['adapter'] = adapter intr['iface'] = iface self.action(discriminator, register, introspectables=(intr,)) @@ -303,24 +309,25 @@ class AdaptersConfiguratorMixin(object): """ adapter = self.maybe_dotted(adapter) resource_iface = self.maybe_dotted(resource_iface) + def register(resource_iface=resource_iface): if resource_iface is None: resource_iface = Interface self.registry.registerAdapter( - adapter, - (resource_iface, Interface), - IResourceURL, - ) + adapter, (resource_iface, Interface), IResourceURL + ) + discriminator = ('resource url adapter', resource_iface) intr = self.introspectable( - 'resource url adapters', + 'resource url adapters', discriminator, 'resource url adapter for resource iface %r' % resource_iface, 'resource url adapter', - ) + ) intr['adapter'] = adapter intr['resource_iface'] = resource_iface self.action(discriminator, register, introspectables=(intr,)) + def eventonly(callee): return takes_one_arg(callee, argname='event') diff --git a/src/pyramid/config/assets.py b/src/pyramid/config/assets.py index b9536df42..fd8b2ee49 100644 --- a/src/pyramid/config/assets.py +++ b/src/pyramid/config/assets.py @@ -4,16 +4,14 @@ import sys from zope.interface import implementer -from pyramid.interfaces import ( - IPackageOverrides, - PHASE1_CONFIG, -) +from pyramid.interfaces import IPackageOverrides, PHASE1_CONFIG from pyramid.exceptions import ConfigurationError from pyramid.threadlocal import get_current_registry from pyramid.config.util import action_method + class OverrideProvider(pkg_resources.DefaultProvider): def __init__(self, module): pkg_resources.DefaultProvider.__init__(self, module) @@ -35,7 +33,8 @@ class OverrideProvider(pkg_resources.DefaultProvider): if filename is not None: return filename return pkg_resources.DefaultProvider.get_resource_filename( - self, manager, resource_name) + self, manager, resource_name + ) def get_resource_stream(self, manager, resource_name): """ Return a readable file-like object for resource_name.""" @@ -45,7 +44,8 @@ class OverrideProvider(pkg_resources.DefaultProvider): if stream is not None: return stream return pkg_resources.DefaultProvider.get_resource_stream( - self, manager, resource_name) + self, manager, resource_name + ) def get_resource_string(self, manager, resource_name): """ Return a string containing the contents of resource_name.""" @@ -55,7 +55,8 @@ class OverrideProvider(pkg_resources.DefaultProvider): if string is not None: return string return pkg_resources.DefaultProvider.get_resource_string( - self, manager, resource_name) + self, manager, resource_name + ) def has_resource(self, resource_name): overrides = self._get_overrides() @@ -63,8 +64,7 @@ class OverrideProvider(pkg_resources.DefaultProvider): result = overrides.has_resource(resource_name) if result is not None: return result - return pkg_resources.DefaultProvider.has_resource( - self, resource_name) + return pkg_resources.DefaultProvider.has_resource(self, resource_name) def resource_isdir(self, resource_name): overrides = self._get_overrides() @@ -73,7 +73,8 @@ class OverrideProvider(pkg_resources.DefaultProvider): if result is not None: return result return pkg_resources.DefaultProvider.resource_isdir( - self, resource_name) + self, resource_name + ) def resource_listdir(self, resource_name): overrides = self._get_overrides() @@ -82,7 +83,8 @@ class OverrideProvider(pkg_resources.DefaultProvider): if result is not None: return result return pkg_resources.DefaultProvider.resource_listdir( - self, resource_name) + self, resource_name + ) @implementer(IPackageOverrides) @@ -193,9 +195,10 @@ class DirectoryOverride: def __call__(self, resource_name): if resource_name.startswith(self.path): - new_path = resource_name[self.pathlen:] + new_path = resource_name[self.pathlen :] return self.source, new_path + class FileOverride: def __init__(self, path, source): self.path = path @@ -215,6 +218,7 @@ class PackageAssetSource(object): the empty string, as returned by the ``FileOverride``. """ + def __init__(self, package, prefix): self.package = package if hasattr(package, '__name__'): @@ -262,6 +266,7 @@ class FSAssetSource(object): An asset source relative to a path in the filesystem. """ + def __init__(self, prefix): self.prefix = prefix @@ -305,14 +310,16 @@ class FSAssetSource(object): class AssetsConfiguratorMixin(object): - def _override(self, package, path, override_source, - PackageOverrides=PackageOverrides): + def _override( + self, package, path, override_source, PackageOverrides=PackageOverrides + ): pkg_name = package.__name__ override = self.registry.queryUtility(IPackageOverrides, name=pkg_name) if override is None: override = PackageOverrides(package) - self.registry.registerUtility(override, IPackageOverrides, - name=pkg_name) + self.registry.registerUtility( + override, IPackageOverrides, name=pkg_name + ) override.insert(path, override_source) @action_method @@ -331,7 +338,8 @@ class AssetsConfiguratorMixin(object): information about asset overrides.""" if to_override == override_with: raise ConfigurationError( - 'You cannot override an asset with itself') + 'You cannot override an asset with itself' + ) package = to_override path = '' @@ -346,7 +354,8 @@ class AssetsConfiguratorMixin(object): if not os.path.exists(override_with): raise ConfigurationError( 'Cannot override asset with an absolute path that does ' - 'not exist') + 'not exist' + ) override_isdir = os.path.isdir(override_with) override_package = None override_prefix = override_with @@ -360,22 +369,23 @@ class AssetsConfiguratorMixin(object): to_package = sys.modules[override_package] override_source = PackageAssetSource(to_package, override_prefix) - override_isdir = ( - override_prefix == '' or - override_with.endswith('/') + override_isdir = override_prefix == '' or override_with.endswith( + '/' ) if overridden_isdir and (not override_isdir): raise ConfigurationError( 'A directory cannot be overridden with a file (put a ' - 'slash at the end of override_with if necessary)') + 'slash at the end of override_with if necessary)' + ) if (not overridden_isdir) and override_isdir: raise ConfigurationError( 'A file cannot be overridden with a directory (put a ' - 'slash at the end of to_override if necessary)') + 'slash at the end of to_override if necessary)' + ) - override = _override or self._override # test jig + override = _override or self._override # test jig def register(): __import__(package) @@ -387,10 +397,11 @@ class AssetsConfiguratorMixin(object): (package, override_package, path, override_prefix), '%s -> %s' % (to_override, override_with), 'asset override', - ) + ) intr['to_override'] = to_override intr['override_with'] = override_with - self.action(None, register, introspectables=(intr,), - order=PHASE1_CONFIG) + self.action( + None, register, introspectables=(intr,), order=PHASE1_CONFIG + ) - override_resource = override_asset # bw compat + override_resource = override_asset # bw compat diff --git a/src/pyramid/config/factories.py b/src/pyramid/config/factories.py index 52248269d..2ec1558a6 100644 --- a/src/pyramid/config/factories.py +++ b/src/pyramid/config/factories.py @@ -8,18 +8,16 @@ from pyramid.interfaces import ( IRequestExtensions, IRootFactory, ISessionFactory, - ) +) from pyramid.router import default_execution_policy from pyramid.traversal import DefaultRootFactory -from pyramid.util import ( - get_callable_name, - InstancePropertyHelper, - ) +from pyramid.util import get_callable_name, InstancePropertyHelper from pyramid.config.util import action_method + class FactoriesConfiguratorMixin(object): @action_method def set_root_factory(self, factory): @@ -41,10 +39,12 @@ class FactoriesConfiguratorMixin(object): self.registry.registerUtility(factory, IRootFactory) self.registry.registerUtility(factory, IDefaultRootFactory) # b/c - intr = self.introspectable('root factories', - None, - self.object_description(factory), - 'root factory') + intr = self.introspectable( + 'root factories', + None, + self.object_description(factory), + 'root factory', + ) intr['factory'] = factory self.action(IRootFactory, register, introspectables=(intr,)) @@ -67,9 +67,13 @@ class FactoriesConfiguratorMixin(object): def register(): self.registry.registerUtility(factory, ISessionFactory) - intr = self.introspectable('session factory', None, - self.object_description(factory), - 'session factory') + + intr = self.introspectable( + 'session factory', + None, + self.object_description(factory), + 'session factory', + ) intr['factory'] = factory self.action(ISessionFactory, register, introspectables=(intr,)) @@ -97,9 +101,13 @@ class FactoriesConfiguratorMixin(object): def register(): self.registry.registerUtility(factory, IRequestFactory) - intr = self.introspectable('request factory', None, - self.object_description(factory), - 'request factory') + + intr = self.introspectable( + 'request factory', + None, + self.object_description(factory), + 'request factory', + ) intr['factory'] = factory self.action(IRequestFactory, register, introspectables=(intr,)) @@ -122,18 +130,19 @@ class FactoriesConfiguratorMixin(object): def register(): self.registry.registerUtility(factory, IResponseFactory) - intr = self.introspectable('response factory', None, - self.object_description(factory), - 'response factory') + intr = self.introspectable( + 'response factory', + None, + self.object_description(factory), + 'response factory', + ) intr['factory'] = factory self.action(IResponseFactory, register, introspectables=(intr,)) @action_method - def add_request_method(self, - callable=None, - name=None, - property=False, - reify=False): + def add_request_method( + self, callable=None, name=None, property=False, reify=False + ): """ Add a property or method to the request object. When adding a method to the request, ``callable`` may be any @@ -177,7 +186,8 @@ class FactoriesConfiguratorMixin(object): property = property or reify if property: name, callable = InstancePropertyHelper.make_property( - callable, name=name, reify=reify) + callable, name=name, reify=reify + ) elif name is None: name = callable.__name__ else: @@ -196,23 +206,31 @@ class FactoriesConfiguratorMixin(object): if callable is None: self.action(('request extensions', name), None) elif property: - intr = self.introspectable('request extensions', name, - self.object_description(callable), - 'request property') + intr = self.introspectable( + 'request extensions', + name, + self.object_description(callable), + 'request property', + ) intr['callable'] = callable intr['property'] = True intr['reify'] = reify - self.action(('request extensions', name), register, - introspectables=(intr,)) + self.action( + ('request extensions', name), register, introspectables=(intr,) + ) else: - intr = self.introspectable('request extensions', name, - self.object_description(callable), - 'request method') + intr = self.introspectable( + 'request extensions', + name, + self.object_description(callable), + 'request method', + ) intr['callable'] = callable intr['property'] = False intr['reify'] = False - self.action(('request extensions', name), register, - introspectables=(intr,)) + self.action( + ('request extensions', name), register, introspectables=(intr,) + ) @action_method def set_execution_policy(self, policy): @@ -231,9 +249,12 @@ class FactoriesConfiguratorMixin(object): def register(): self.registry.registerUtility(policy, IExecutionPolicy) - intr = self.introspectable('execution policy', None, - self.object_description(policy), - 'execution policy') + intr = self.introspectable( + 'execution policy', + None, + self.object_description(policy), + 'execution policy', + ) intr['policy'] = policy self.action(IExecutionPolicy, register, introspectables=(intr,)) diff --git a/src/pyramid/config/i18n.py b/src/pyramid/config/i18n.py index 5dabe2845..6e7334448 100644 --- a/src/pyramid/config/i18n.py +++ b/src/pyramid/config/i18n.py @@ -1,13 +1,11 @@ -from pyramid.interfaces import ( - ILocaleNegotiator, - ITranslationDirectories, - ) +from pyramid.interfaces import ILocaleNegotiator, ITranslationDirectories from pyramid.exceptions import ConfigurationError from pyramid.path import AssetResolver from pyramid.config.util import action_method + class I18NConfiguratorMixin(object): @action_method def set_locale_negotiator(self, negotiator): @@ -30,11 +28,16 @@ class I18NConfiguratorMixin(object): :class:`pyramid.config.Configurator` constructor can be used to achieve the same purpose. """ + def register(): self._set_locale_negotiator(negotiator) - intr = self.introspectable('locale negotiator', None, - self.object_description(negotiator), - 'locale negotiator') + + intr = self.introspectable( + 'locale negotiator', + None, + self.object_description(negotiator), + 'locale negotiator', + ) intr['negotiator'] = negotiator self.action(ILocaleNegotiator, register, introspectables=(intr,)) @@ -97,10 +100,15 @@ class I18NConfiguratorMixin(object): asset = resolver.resolve(spec) directory = asset.abspath() if not asset.isdir(): - raise ConfigurationError('"%s" is not a directory' % - directory) - intr = self.introspectable('translation directories', directory, - spec, 'translation directory') + raise ConfigurationError( + '"%s" is not a directory' % directory + ) + intr = self.introspectable( + 'translation directories', + directory, + spec, + 'translation directory', + ) intr['directory'] = directory intr['spec'] = spec introspectables.append(intr) @@ -117,4 +125,3 @@ class I18NConfiguratorMixin(object): tdirs.insert(0, directory) self.action(None, register, introspectables=introspectables) - diff --git a/src/pyramid/config/predicates.py b/src/pyramid/config/predicates.py index bda763161..cdbf68ca4 100644 --- a/src/pyramid/config/predicates.py +++ b/src/pyramid/config/predicates.py @@ -1,2 +1,3 @@ import zope.deprecation + zope.deprecation.moved('pyramid.predicates', 'Pyramid 1.10') diff --git a/src/pyramid/config/rendering.py b/src/pyramid/config/rendering.py index 0d55c41e8..948199636 100644 --- a/src/pyramid/config/rendering.py +++ b/src/pyramid/config/rendering.py @@ -1,7 +1,4 @@ -from pyramid.interfaces import ( - IRendererFactory, - PHASE1_CONFIG, - ) +from pyramid.interfaces import IRendererFactory, PHASE1_CONFIG from pyramid import renderers from pyramid.config.util import action_method @@ -9,13 +6,14 @@ from pyramid.config.util import action_method DEFAULT_RENDERERS = ( ('json', renderers.json_renderer_factory), ('string', renderers.string_renderer_factory), - ) +) + class RenderingConfiguratorMixin(object): def add_default_renderers(self): for name, renderer in DEFAULT_RENDERERS: self.add_renderer(name, renderer) - + @action_method def add_renderer(self, name, factory): """ @@ -36,16 +34,23 @@ class RenderingConfiguratorMixin(object): # as a name if not name: name = '' + def register(): self.registry.registerUtility(factory, IRendererFactory, name=name) - intr = self.introspectable('renderer factories', - name, - self.object_description(factory), - 'renderer factory') + + intr = self.introspectable( + 'renderer factories', + name, + self.object_description(factory), + 'renderer factory', + ) intr['factory'] = factory intr['name'] = name # we need to register renderers early (in phase 1) because they are # used during view configuration (which happens in phase 3) - self.action((IRendererFactory, name), register, order=PHASE1_CONFIG, - introspectables=(intr,)) - + self.action( + (IRendererFactory, name), + register, + order=PHASE1_CONFIG, + introspectables=(intr,), + ) diff --git a/src/pyramid/config/routes.py b/src/pyramid/config/routes.py index 5d05429a7..46b8921cd 100644 --- a/src/pyramid/config/routes.py +++ b/src/pyramid/config/routes.py @@ -7,16 +7,13 @@ from pyramid.interfaces import ( IRouteRequest, IRoutesMapper, PHASE2_CONFIG, - ) +) from pyramid.exceptions import ConfigurationError from pyramid.request import route_request_iface from pyramid.urldispatch import RoutesMapper -from pyramid.util import ( - as_sorted_tuple, - is_nonstr_iter, -) +from pyramid.util import as_sorted_tuple, is_nonstr_iter import pyramid.predicates @@ -26,26 +23,29 @@ from pyramid.config.util import ( predvalseq, ) + class RoutesConfiguratorMixin(object): @action_method - def add_route(self, - name, - pattern=None, - factory=None, - for_=None, - header=None, - xhr=None, - accept=None, - path_info=None, - request_method=None, - request_param=None, - traverse=None, - custom_predicates=(), - use_global_views=False, - path=None, - pregenerator=None, - static=False, - **predicates): + def add_route( + self, + name, + pattern=None, + factory=None, + for_=None, + header=None, + xhr=None, + accept=None, + path_info=None, + request_method=None, + request_param=None, + traverse=None, + custom_predicates=(), + use_global_views=False, + path=None, + pregenerator=None, + static=False, + **predicates + ): """ Add a :term:`route configuration` to the current configuration state, as well as possibly a :term:`view configuration` to be used to specify a :term:`view callable` @@ -297,27 +297,31 @@ class RoutesConfiguratorMixin(object): """ if custom_predicates: warnings.warn( - ('The "custom_predicates" argument to Configurator.add_route ' - 'is deprecated as of Pyramid 1.5. Use ' - '"config.add_route_predicate" and use the registered ' - 'route predicate as a predicate argument to add_route ' - 'instead. See "Adding A Third Party View, Route, or ' - 'Subscriber Predicate" in the "Hooks" chapter of the ' - 'documentation for more information.'), + ( + 'The "custom_predicates" argument to Configurator.add_route ' + 'is deprecated as of Pyramid 1.5. Use ' + '"config.add_route_predicate" and use the registered ' + 'route predicate as a predicate argument to add_route ' + 'instead. See "Adding A Third Party View, Route, or ' + 'Subscriber Predicate" in the "Hooks" chapter of the ' + 'documentation for more information.' + ), DeprecationWarning, - stacklevel=3 - ) + stacklevel=3, + ) if accept is not None: if not is_nonstr_iter(accept): if '*' in accept: warnings.warn( - ('Passing a media range to the "accept" argument of ' - 'Configurator.add_route is deprecated as of Pyramid ' - '1.10. Use a list of explicit media types.'), + ( + 'Passing a media range to the "accept" argument of ' + 'Configurator.add_route is deprecated as of Pyramid ' + '1.10. Use a list of explicit media types.' + ), DeprecationWarning, stacklevel=3, - ) + ) # XXX switch this to False when range support is dropped accept = [normalize_accept_offer(accept, allow_range=True)] @@ -347,15 +351,16 @@ class RoutesConfiguratorMixin(object): pattern = parsed.path original_pregenerator = pregenerator + def external_url_pregenerator(request, elements, kw): if '_app_url' in kw: raise ValueError( 'You cannot generate a path to an external route ' 'pattern via request.route_path nor pass an _app_url ' 'to request.route_url when generating a URL for an ' - 'external route pattern (pattern was "%s") ' % - (pattern,) - ) + 'external route pattern (pattern was "%s") ' + % (pattern,) + ) if '_scheme' in kw: scheme = kw['_scheme'] elif parsed.scheme: @@ -365,8 +370,7 @@ class RoutesConfiguratorMixin(object): kw['_app_url'] = '{0}://{1}'.format(scheme, parsed.netloc) if original_pregenerator: - elements, kw = original_pregenerator( - request, elements, kw) + elements, kw = original_pregenerator(request, elements, kw) return elements, kw pregenerator = external_url_pregenerator @@ -379,10 +383,9 @@ class RoutesConfiguratorMixin(object): introspectables = [] - intr = self.introspectable('routes', - name, - '%s (pattern: %r)' % (name, pattern), - 'route') + intr = self.introspectable( + 'routes', name, '%s (pattern: %r)' % (name, pattern), 'route' + ) intr['name'] = name intr['pattern'] = pattern intr['factory'] = factory @@ -404,17 +407,21 @@ class RoutesConfiguratorMixin(object): introspectables.append(intr) if factory: - factory_intr = self.introspectable('root factories', - name, - self.object_description(factory), - 'root factory') + factory_intr = self.introspectable( + 'root factories', + name, + self.object_description(factory), + 'root factory', + ) factory_intr['factory'] = factory factory_intr['route_name'] = name factory_intr.relate('routes', name) introspectables.append(factory_intr) def register_route_request_iface(): - request_iface = self.registry.queryUtility(IRouteRequest, name=name) + request_iface = self.registry.queryUtility( + IRouteRequest, name=name + ) if request_iface is None: if use_global_views: bases = (IRequest,) @@ -422,7 +429,8 @@ class RoutesConfiguratorMixin(object): bases = () request_iface = route_request_iface(name, bases) self.registry.registerUtility( - request_iface, IRouteRequest, name=name) + request_iface, IRouteRequest, name=name + ) def register_connect(): pvals = predicates.copy() @@ -436,15 +444,19 @@ class RoutesConfiguratorMixin(object): accept=accept, traverse=traverse, custom=predvalseq(custom_predicates), - ) ) + ) predlist = self.get_predlist('route') _, preds, _ = predlist.make(self, **pvals) route = mapper.connect( - name, pattern, factory, predicates=preds, - pregenerator=pregenerator, static=static - ) + name, + pattern, + factory, + predicates=preds, + pregenerator=pregenerator, + static=static, + ) intr['object'] = route return route @@ -455,12 +467,17 @@ class RoutesConfiguratorMixin(object): # But IRouteRequest interfaces must be registered before we begin to # process view registrations (in phase 3) - self.action(('route', name), register_route_request_iface, - order=PHASE2_CONFIG, introspectables=introspectables) + self.action( + ('route', name), + register_route_request_iface, + order=PHASE2_CONFIG, + introspectables=introspectables, + ) @action_method - def add_route_predicate(self, name, factory, weighs_more_than=None, - weighs_less_than=None): + def add_route_predicate( + self, name, factory, weighs_more_than=None, weighs_less_than=None + ): """ Adds a route predicate factory. The view predicate can later be named as a keyword argument to :meth:`pyramid.config.Configurator.add_route`. @@ -481,8 +498,8 @@ class RoutesConfiguratorMixin(object): name, factory, weighs_more_than=weighs_more_than, - weighs_less_than=weighs_less_than - ) + weighs_less_than=weighs_less_than, + ) def add_default_route_predicates(self): p = pyramid.predicates @@ -496,7 +513,7 @@ class RoutesConfiguratorMixin(object): ('effective_principals', p.EffectivePrincipalsPredicate), ('custom', p.CustomPredicate), ('traverse', p.TraversePredicate), - ): + ): self.add_route_predicate(name, factory) def get_routes_mapper(self): @@ -541,8 +558,7 @@ class RoutesConfiguratorMixin(object): old_route_prefix = '' route_prefix = '{}/{}'.format( - old_route_prefix.rstrip('/'), - route_prefix.lstrip('/'), + old_route_prefix.rstrip('/'), route_prefix.lstrip('/') ) route_prefix = route_prefix.strip('/') diff --git a/src/pyramid/config/security.py b/src/pyramid/config/security.py index c7afbcf4e..3b55c41d7 100644 --- a/src/pyramid/config/security.py +++ b/src/pyramid/config/security.py @@ -8,7 +8,7 @@ from pyramid.interfaces import ( IDefaultPermission, PHASE1_CONFIG, PHASE2_CONFIG, - ) +) from pyramid.csrf import LegacySessionCSRFStoragePolicy from pyramid.exceptions import ConfigurationError @@ -16,8 +16,8 @@ from pyramid.util import as_sorted_tuple from pyramid.config.util import action_method -class SecurityConfiguratorMixin(object): +class SecurityConfiguratorMixin(object): def add_default_security(self): self.set_csrf_storage_policy(LegacySessionCSRFStoragePolicy()) @@ -35,20 +35,30 @@ class SecurityConfiguratorMixin(object): achieve the same purpose. """ + def register(): self._set_authentication_policy(policy) if self.registry.queryUtility(IAuthorizationPolicy) is None: raise ConfigurationError( 'Cannot configure an authentication policy without ' 'also configuring an authorization policy ' - '(use the set_authorization_policy method)') - intr = self.introspectable('authentication policy', None, - self.object_description(policy), - 'authentication policy') + '(use the set_authorization_policy method)' + ) + + intr = self.introspectable( + 'authentication policy', + None, + self.object_description(policy), + 'authentication policy', + ) intr['policy'] = policy # authentication policy used by view config (phase 3) - self.action(IAuthenticationPolicy, register, order=PHASE2_CONFIG, - introspectables=(intr,)) + self.action( + IAuthenticationPolicy, + register, + order=PHASE2_CONFIG, + introspectables=(intr,), + ) def _set_authentication_policy(self, policy): policy = self.maybe_dotted(policy) @@ -67,8 +77,10 @@ class SecurityConfiguratorMixin(object): :class:`pyramid.config.Configurator` constructor can be used to achieve the same purpose. """ + def register(): self._set_authorization_policy(policy) + def ensure(): if self.autocommit: return @@ -76,16 +88,24 @@ class SecurityConfiguratorMixin(object): raise ConfigurationError( 'Cannot configure an authorization policy without ' 'also configuring an authentication policy ' - '(use the set_authorization_policy method)') + '(use the set_authorization_policy method)' + ) - intr = self.introspectable('authorization policy', None, - self.object_description(policy), - 'authorization policy') + intr = self.introspectable( + 'authorization policy', + None, + self.object_description(policy), + 'authorization policy', + ) intr['policy'] = policy # authorization policy used by view config (phase 3) and # authentication policy (phase 2) - self.action(IAuthorizationPolicy, register, order=PHASE1_CONFIG, - introspectables=(intr,)) + self.action( + IAuthorizationPolicy, + register, + order=PHASE1_CONFIG, + introspectables=(intr,), + ) self.action(None, ensure) def _set_authorization_policy(self, policy): @@ -133,21 +153,25 @@ class SecurityConfiguratorMixin(object): :class:`pyramid.config.Configurator` constructor can be used to achieve the same purpose. """ + def register(): self.registry.registerUtility(permission, IDefaultPermission) - intr = self.introspectable('default permission', - None, - permission, - 'default permission') + + intr = self.introspectable( + 'default permission', None, permission, 'default permission' + ) intr['value'] = permission - perm_intr = self.introspectable('permissions', - permission, - permission, - 'permission') + perm_intr = self.introspectable( + 'permissions', permission, permission, 'permission' + ) perm_intr['value'] = permission # default permission used during view registration (phase 3) - self.action(IDefaultPermission, register, order=PHASE1_CONFIG, - introspectables=(intr, perm_intr,)) + self.action( + IDefaultPermission, + register, + order=PHASE1_CONFIG, + introspectables=(intr, perm_intr), + ) def add_permission(self, permission_name): """ @@ -161,11 +185,8 @@ class SecurityConfiguratorMixin(object): config.add_permission('view') """ intr = self.introspectable( - 'permissions', - permission_name, - permission_name, - 'permission' - ) + 'permissions', permission_name, permission_name, 'permission' + ) intr['value'] = permission_name self.action(None, introspectables=(intr,)) @@ -217,22 +238,30 @@ class SecurityConfiguratorMixin(object): """ options = DefaultCSRFOptions( - require_csrf, token, header, safe_methods, callback, + require_csrf, token, header, safe_methods, callback ) + def register(): self.registry.registerUtility(options, IDefaultCSRFOptions) - intr = self.introspectable('default csrf view options', - None, - options, - 'default csrf view options') + + intr = self.introspectable( + 'default csrf view options', + None, + options, + 'default csrf view options', + ) intr['require_csrf'] = require_csrf intr['token'] = token intr['header'] = header intr['safe_methods'] = as_sorted_tuple(safe_methods) intr['callback'] = callback - self.action(IDefaultCSRFOptions, register, order=PHASE1_CONFIG, - introspectables=(intr,)) + self.action( + IDefaultCSRFOptions, + register, + order=PHASE1_CONFIG, + introspectables=(intr,), + ) @action_method def set_csrf_storage_policy(self, policy): @@ -245,12 +274,13 @@ class SecurityConfiguratorMixin(object): how to generate and persist CSRF tokens. """ + def register(): self.registry.registerUtility(policy, ICSRFStoragePolicy) - intr = self.introspectable('csrf storage policy', - None, - policy, - 'csrf storage policy') + + intr = self.introspectable( + 'csrf storage policy', None, policy, 'csrf storage policy' + ) intr['policy'] = policy self.action(ICSRFStoragePolicy, register, introspectables=(intr,)) diff --git a/src/pyramid/config/settings.py b/src/pyramid/config/settings.py index 11a1f7d8c..07b469c29 100644 --- a/src/pyramid/config/settings.py +++ b/src/pyramid/config/settings.py @@ -2,6 +2,7 @@ import os from pyramid.settings import asbool, aslist + class SettingsConfiguratorMixin(object): def _set_settings(self, mapping): if mapping is None: @@ -60,11 +61,13 @@ def Settings(d=None, _environ_=os.environ, **kw): d.update(**kw) eget = _environ_.get + def expand_key(key): keys = [key] if not key.startswith('pyramid.'): keys.append('pyramid.' + key) return keys + def S(settings_key, env_key=None, type_=str, default=False): value = default keys = expand_key(settings_key) @@ -74,6 +77,7 @@ def Settings(d=None, _environ_=os.environ, **kw): value = eget(env_key, value) value = type_(value) d.update({k: value for k in keys}) + def O(settings_key, override_key): # noqa: E743 for key in expand_key(settings_key): d[key] = d[key] or d[override_key] diff --git a/src/pyramid/config/testing.py b/src/pyramid/config/testing.py index 1daf5cdeb..1655df52c 100644 --- a/src/pyramid/config/testing.py +++ b/src/pyramid/config/testing.py @@ -5,22 +5,25 @@ from pyramid.interfaces import ( IAuthorizationPolicy, IAuthenticationPolicy, IRendererFactory, - ) +) from pyramid.renderers import RendererHelper -from pyramid.traversal import ( - decode_path_info, - split_path_info, - ) +from pyramid.traversal import decode_path_info, split_path_info from pyramid.config.util import action_method + class TestingConfiguratorMixin(object): # testing API - def testing_securitypolicy(self, userid=None, groupids=(), - permissive=True, remember_result=None, - forget_result=None): + def testing_securitypolicy( + self, + userid=None, + groupids=(), + permissive=True, + remember_result=None, + forget_result=None, + ): """Unit/integration testing helper: Registers a pair of faux :app:`Pyramid` security policies: a :term:`authentication policy` and a :term:`authorization policy`. @@ -64,9 +67,10 @@ class TestingConfiguratorMixin(object): The ``forget_result`` argument. """ from pyramid.testing import DummySecurityPolicy + policy = DummySecurityPolicy( userid, groupids, permissive, remember_result, forget_result - ) + ) self.registry.registerUtility(policy, IAuthorizationPolicy) self.registry.registerUtility(policy, IAuthenticationPolicy) return policy @@ -85,6 +89,7 @@ class TestingConfiguratorMixin(object): :func:`pyramid.traversal.find_resource` is called with an equivalent path string or tuple. """ + class DummyTraverserFactory: def __init__(self, context): self.context = context @@ -93,14 +98,22 @@ class TestingConfiguratorMixin(object): path = decode_path_info(request.environ['PATH_INFO']) ob = resources[path] traversed = split_path_info(path) - return {'context':ob, 'view_name':'','subpath':(), - 'traversed':traversed, 'virtual_root':ob, - 'virtual_root_path':(), 'root':ob} - self.registry.registerAdapter(DummyTraverserFactory, (Interface,), - ITraverser) + return { + 'context': ob, + 'view_name': '', + 'subpath': (), + 'traversed': traversed, + 'virtual_root': ob, + 'virtual_root_path': (), + 'root': ob, + } + + self.registry.registerAdapter( + DummyTraverserFactory, (Interface,), ITraverser + ) return resources - testing_models = testing_resources # b/w compat + testing_models = testing_resources # b/w compat @action_method def testing_add_subscriber(self, event_iface=None): @@ -122,8 +135,10 @@ class TestingConfiguratorMixin(object): """ event_iface = self.maybe_dotted(event_iface) L = [] + def subscriber(*event): L.extend(event) + self.add_subscriber(subscriber, event_iface) return L @@ -149,19 +164,22 @@ class TestingConfiguratorMixin(object): """ from pyramid.testing import DummyRendererFactory + helper = RendererHelper(name=path, registry=self.registry) - factory = self.registry.queryUtility(IRendererFactory, name=helper.type) + factory = self.registry.queryUtility( + IRendererFactory, name=helper.type + ) if not isinstance(factory, DummyRendererFactory): factory = DummyRendererFactory(helper.type, factory) - self.registry.registerUtility(factory, IRendererFactory, - name=helper.type) + self.registry.registerUtility( + factory, IRendererFactory, name=helper.type + ) from pyramid.testing import DummyTemplateRenderer + if renderer is None: renderer = DummyTemplateRenderer() factory.add(path, renderer) return renderer testing_add_template = testing_add_renderer - - diff --git a/src/pyramid/config/tweens.py b/src/pyramid/config/tweens.py index 8bf21cf71..a90008784 100644 --- a/src/pyramid/config/tweens.py +++ b/src/pyramid/config/tweens.py @@ -2,26 +2,17 @@ from zope.interface import implementer from pyramid.interfaces import ITweens -from pyramid.compat import ( - string_types, - is_nonstr_iter, - ) +from pyramid.compat import string_types, is_nonstr_iter from pyramid.exceptions import ConfigurationError -from pyramid.tweens import ( - MAIN, - INGRESS, - EXCVIEW, - ) +from pyramid.tweens import MAIN, INGRESS, EXCVIEW -from pyramid.util import ( - is_string_or_iterable, - TopologicalSorter, - ) +from pyramid.util import is_string_or_iterable, TopologicalSorter from pyramid.config.util import action_method + class TweensConfiguratorMixin(object): def add_tween(self, tween_factory, under=None, over=None): """ @@ -104,8 +95,9 @@ class TweensConfiguratorMixin(object): For more information, see :ref:`registering_tweens`. """ - return self._add_tween(tween_factory, under=under, over=over, - explicit=False) + return self._add_tween( + tween_factory, under=under, over=over, explicit=False + ) def add_default_tweens(self): self.add_tween(EXCVIEW) @@ -116,8 +108,9 @@ class TweensConfiguratorMixin(object): if not isinstance(tween_factory, string_types): raise ConfigurationError( 'The "tween_factory" argument to add_tween must be a ' - 'dotted name to a globally importable object, not %r' % - tween_factory) + 'dotted name to a globally importable object, not %r' + % tween_factory + ) name = tween_factory @@ -130,7 +123,8 @@ class TweensConfiguratorMixin(object): if p is not None: if not is_string_or_iterable(p): raise ConfigurationError( - '"%s" must be a string or iterable, not %s' % (t, p)) + '"%s" must be a string or iterable, not %s' % (t, p) + ) if over is INGRESS or is_nonstr_iter(over) and INGRESS in over: raise ConfigurationError('%s cannot be over INGRESS' % name) @@ -150,15 +144,16 @@ class TweensConfiguratorMixin(object): if explicit: tweens.add_explicit(name, tween_factory) else: - tweens.add_implicit(name, tween_factory, under=under, over=over) + tweens.add_implicit( + name, tween_factory, under=under, over=over + ) discriminator = ('tween', name, explicit) tween_type = explicit and 'explicit' or 'implicit' - intr = self.introspectable('tweens', - discriminator, - name, - '%s tween' % tween_type) + intr = self.introspectable( + 'tweens', discriminator, name, '%s tween' % tween_type + ) intr['name'] = name intr['factory'] = tween_factory intr['type'] = tween_type @@ -167,6 +162,7 @@ class TweensConfiguratorMixin(object): introspectables.append(intr) self.action(discriminator, register, introspectables=introspectables) + @implementer(ITweens) class Tweens(object): def __init__(self): @@ -174,7 +170,8 @@ class Tweens(object): default_before=None, default_after=INGRESS, first=INGRESS, - last=MAIN) + last=MAIN, + ) self.explicit = [] def add_explicit(self, name, factory): diff --git a/src/pyramid/config/util.py b/src/pyramid/config/util.py index 05d810f6f..627b78b6f 100644 --- a/src/pyramid/config/util.py +++ b/src/pyramid/config/util.py @@ -4,23 +4,18 @@ import traceback from webob.acceptparse import Accept from zope.interface import implementer -from pyramid.compat import ( - bytes_, - is_nonstr_iter -) +from pyramid.compat import bytes_, is_nonstr_iter from pyramid.interfaces import IActionInfo from pyramid.exceptions import ConfigurationError from pyramid.predicates import Notted from pyramid.registry import predvalseq -from pyramid.util import ( - TopologicalSorter, - takes_one_arg, -) +from pyramid.util import TopologicalSorter, takes_one_arg TopologicalSorter = TopologicalSorter # support bw-compat imports takes_one_arg = takes_one_arg # support bw-compat imports + @implementer(IActionInfo) class ActionInfo(object): def __init__(self, file, line, function, src): @@ -34,10 +29,12 @@ class ActionInfo(object): src = '\n'.join(' %s' % x for x in srclines) return 'Line %s of file %s:\n%s' % (self.line, self.file, src) + def action_method(wrapped): """ Wrapper to provide the right conflict info report data when a method that calls Configurator.action calls another that does the same. Not a documented API but used by some external systems.""" + def wrapper(self, *arg, **kw): if self._ainfo is None: self._ainfo = [] @@ -55,10 +52,10 @@ def action_method(wrapped): # extra stack frame. This should no longer be necessary in # Python 3.5.1 last_frame = ActionInfo(*f[-1]) - if last_frame.function == 'extract_stack': # pragma: no cover + if last_frame.function == 'extract_stack': # pragma: no cover f.pop() info = ActionInfo(*f[-backframes]) - except Exception: # pragma: no cover + except Exception: # pragma: no cover info = ActionInfo(None, 0, '', '') self._ainfo.append(info) try: @@ -112,6 +109,7 @@ class not_(object): .. versionadded:: 1.5 """ + def __init__(self, value): self.value = value @@ -119,8 +117,8 @@ class not_(object): # under = after # over = before -class PredicateList(object): +class PredicateList(object): def __init__(self): self.sorter = TopologicalSorter() self.last_added = None @@ -133,11 +131,8 @@ class PredicateList(object): ## weighs_less_than = LAST self.last_added = name self.sorter.add( - name, - factory, - after=weighs_more_than, - before=weighs_less_than, - ) + name, factory, after=weighs_more_than, before=weighs_less_than + ) def names(self): # Return the list of valid predicate names. @@ -161,7 +156,7 @@ class PredicateList(object): preds = [] for n, (name, predicate_factory) in enumerate(ordered): vals = kw.pop(name, None) - if vals is None: # XXX should this be a sentinel other than None? + if vals is None: # XXX should this be a sentinel other than None? continue if not isinstance(vals, predvalseq): vals = (vals,) @@ -183,8 +178,9 @@ class PredicateList(object): preds.append(pred) if kw: from difflib import get_close_matches + closest = [] - names = [ name for name, _ in ordered ] + names = [name for name, _ in ordered] for name in kw: closest.extend(get_close_matches(name, names, 3)) @@ -266,8 +262,7 @@ def sort_accept_offers(offers, order=None): parsed = Accept.parse_offer(value) type_w = find_order_index( - parsed.type + '/' + parsed.subtype, - max_weight, + parsed.type + '/' + parsed.subtype, max_weight ) if parsed.params: diff --git a/src/pyramid/config/views.py b/src/pyramid/config/views.py index e6baa7c17..277b207fd 100644 --- a/src/pyramid/config/views.py +++ b/src/pyramid/config/views.py @@ -6,11 +6,7 @@ import os import warnings from webob.acceptparse import Accept -from zope.interface import ( - Interface, - implementedBy, - implementer, - ) +from zope.interface import Interface, implementedBy, implementer from zope.interface.interfaces import IInterface from pyramid.interfaces import ( @@ -31,7 +27,7 @@ from pyramid.interfaces import ( IViewDeriverInfo, IViewMapperFactory, PHASE1_CONFIG, - ) +) from pyramid import renderers @@ -42,20 +38,17 @@ from pyramid.compat import ( url_quote, WIN, is_nonstr_iter, - ) +) from pyramid.decorator import reify -from pyramid.exceptions import ( - ConfigurationError, - PredicateMismatch, - ) +from pyramid.exceptions import ConfigurationError, PredicateMismatch from pyramid.httpexceptions import ( HTTPForbidden, HTTPNotFound, default_exceptionresponse_view, - ) +) from pyramid.registry import Deferred @@ -66,10 +59,7 @@ from pyramid.url import parse_url_overrides from pyramid.view import AppendSlashNotFoundViewFactory -from pyramid.util import ( - as_sorted_tuple, - TopologicalSorter, - ) +from pyramid.util import as_sorted_tuple, TopologicalSorter import pyramid.predicates import pyramid.viewderivers @@ -91,19 +81,19 @@ from pyramid.config.util import ( normalize_accept_offer, predvalseq, sort_accept_offers, - ) +) urljoin = urlparse.urljoin url_parse = urlparse.urlparse -DefaultViewMapper = DefaultViewMapper # bw-compat -preserve_view_attrs = preserve_view_attrs # bw-compat -requestonly = requestonly # bw-compat -view_description = view_description # bw-compat +DefaultViewMapper = DefaultViewMapper # bw-compat +preserve_view_attrs = preserve_view_attrs # bw-compat +requestonly = requestonly # bw-compat +view_description = view_description # bw-compat + @implementer(IMultiView) class MultiView(object): - def __init__(self, name): self.name = name self.media_views = {} @@ -181,21 +171,22 @@ class MultiView(object): continue raise PredicateMismatch(self.name) + def attr_wrapped_view(view, info): - accept, order, phash = (info.options.get('accept', None), - getattr(info, 'order', MAX_ORDER), - getattr(info, 'phash', DEFAULT_PHASH)) + accept, order, phash = ( + info.options.get('accept', None), + getattr(info, 'order', MAX_ORDER), + getattr(info, 'phash', DEFAULT_PHASH), + ) # this is a little silly but we don't want to decorate the original # function with attributes that indicate accept, order, and phash, # so we use a wrapper - if ( - (accept is None) and - (order == MAX_ORDER) and - (phash == DEFAULT_PHASH) - ): - return view # defaults + if (accept is None) and (order == MAX_ORDER) and (phash == DEFAULT_PHASH): + return view # defaults + def attr_view(context, request): return view(context, request) + attr_view.__accept__ = accept attr_view.__order__ = order attr_view.__phash__ = phash @@ -203,31 +194,38 @@ def attr_wrapped_view(view, info): attr_view.__permission__ = info.options.get('permission') return attr_view + attr_wrapped_view.options = ('accept', 'attr', 'permission') + def predicated_view(view, info): preds = info.predicates if not preds: return view + def predicate_wrapper(context, request): for predicate in preds: if not predicate(context, request): view_name = getattr(view, '__name__', view) raise PredicateMismatch( - 'predicate mismatch for view %s (%s)' % ( - view_name, predicate.text())) + 'predicate mismatch for view %s (%s)' + % (view_name, predicate.text()) + ) return view(context, request) + def checker(context, request): - return all((predicate(context, request) for predicate in - preds)) + return all((predicate(context, request) for predicate in preds)) + predicate_wrapper.__predicated__ = checker predicate_wrapper.__predicates__ = preds return predicate_wrapper + def viewdefaults(wrapped): """ Decorator for add_view-like methods which takes into account __view_defaults__ attached to view it is passed. Not a documented API but used by some external systems.""" + def wrapper(self, *arg, **kw): defaults = {} if arg: @@ -238,19 +236,23 @@ def viewdefaults(wrapped): if inspect.isclass(view): defaults = getattr(view, '__view_defaults__', {}).copy() if '_backframes' not in kw: - kw['_backframes'] = 1 # for action_method + kw['_backframes'] = 1 # for action_method defaults.update(kw) return wrapped(self, *arg, **defaults) + return functools.wraps(wrapped)(wrapper) + def combine_decorators(*decorators): def decorated(view_callable): # reversed() allows a more natural ordering in the api for decorator in reversed(decorators): view_callable = decorator(view_callable) return view_callable + return decorated + class ViewsConfiguratorMixin(object): @viewdefaults @action_method @@ -281,7 +283,8 @@ class ViewsConfiguratorMixin(object): check_csrf=None, require_csrf=None, exception_only=False, - **view_options): + **view_options + ): """ Add a :term:`view configuration` to the current configuration state. Arguments to ``add_view`` are broken down below into *predicate* arguments and *non-predicate* @@ -804,42 +807,48 @@ class ViewsConfiguratorMixin(object): """ if custom_predicates: warnings.warn( - ('The "custom_predicates" argument to Configurator.add_view ' - 'is deprecated as of Pyramid 1.5. Use ' - '"config.add_view_predicate" and use the registered ' - 'view predicate as a predicate argument to add_view instead. ' - 'See "Adding A Third Party View, Route, or Subscriber ' - 'Predicate" in the "Hooks" chapter of the documentation ' - 'for more information.'), + ( + 'The "custom_predicates" argument to Configurator.add_view ' + 'is deprecated as of Pyramid 1.5. Use ' + '"config.add_view_predicate" and use the registered ' + 'view predicate as a predicate argument to add_view instead. ' + 'See "Adding A Third Party View, Route, or Subscriber ' + 'Predicate" in the "Hooks" chapter of the documentation ' + 'for more information.' + ), DeprecationWarning, stacklevel=4, - ) + ) if check_csrf is not None: warnings.warn( - ('The "check_csrf" argument to Configurator.add_view is ' - 'deprecated as of Pyramid 1.7. Use the "require_csrf" option ' - 'instead or see "Checking CSRF Tokens Automatically" in the ' - '"Sessions" chapter of the documentation for more ' - 'information.'), + ( + 'The "check_csrf" argument to Configurator.add_view is ' + 'deprecated as of Pyramid 1.7. Use the "require_csrf" option ' + 'instead or see "Checking CSRF Tokens Automatically" in the ' + '"Sessions" chapter of the documentation for more ' + 'information.' + ), DeprecationWarning, stacklevel=4, - ) + ) if accept is not None: if is_nonstr_iter(accept): raise ConfigurationError( - 'A list is not supported in the "accept" view predicate.', + 'A list is not supported in the "accept" view predicate.' ) if '*' in accept: warnings.warn( - ('Passing a media range to the "accept" argument of ' - 'Configurator.add_view is deprecated as of Pyramid 1.10. ' - 'Use explicit media types to avoid ambiguities in ' - 'content negotiation that may impact your users.'), + ( + 'Passing a media range to the "accept" argument of ' + 'Configurator.add_view is deprecated as of Pyramid 1.10. ' + 'Use explicit media types to avoid ambiguities in ' + 'content negotiation that may impact your users.' + ), DeprecationWarning, stacklevel=4, - ) + ) # XXX when media ranges are gone, switch allow_range=False accept = normalize_accept_offer(accept, allow_range=True) @@ -856,17 +865,21 @@ class ViewsConfiguratorMixin(object): if not view: if renderer: + def view(context, request): return {} + else: - raise ConfigurationError('"view" was not specified and ' - 'no "renderer" specified') + raise ConfigurationError( + '"view" was not specified and ' 'no "renderer" specified' + ) if request_type is not None: request_type = self.maybe_dotted(request_type) if not IInterface.providedBy(request_type): raise ConfigurationError( - 'request_type must be an interface, not %s' % request_type) + 'request_type must be an interface, not %s' % request_type + ) if context is None: context = for_ @@ -875,7 +888,8 @@ class ViewsConfiguratorMixin(object): if exception_only and not isexc: raise ConfigurationError( 'view "context" must be an exception type when ' - '"exception_only" is True') + '"exception_only" is True' + ) r_context = context if r_context is None: @@ -885,24 +899,26 @@ class ViewsConfiguratorMixin(object): if isinstance(renderer, string_types): renderer = renderers.RendererHelper( - name=renderer, package=self.package, - registry=self.registry) + name=renderer, package=self.package, registry=self.registry + ) introspectables = [] ovals = view_options.copy() - ovals.update(dict( - xhr=xhr, - request_method=request_method, - path_info=path_info, - request_param=request_param, - header=header, - accept=accept, - containment=containment, - request_type=request_type, - match_param=match_param, - check_csrf=check_csrf, - custom=predvalseq(custom_predicates), - )) + ovals.update( + dict( + xhr=xhr, + request_method=request_method, + path_info=path_info, + request_param=request_param, + header=header, + accept=accept, + containment=containment, + request_type=request_type, + match_param=match_param, + check_csrf=check_csrf, + custom=predvalseq(custom_predicates), + ) + ) def discrim_func(): # We need to defer the discriminator until we know what the phash @@ -924,80 +940,82 @@ class ViewsConfiguratorMixin(object): order, preds, phash = predlist.make(self, **pvals) - view_intr.update({ - 'phash': phash, - 'order': order, - 'predicates': preds, - }) + view_intr.update( + {'phash': phash, 'order': order, 'predicates': preds} + ) return ('view', context, name, route_name, phash) discriminator = Deferred(discrim_func) if inspect.isclass(view) and attr: view_desc = 'method %r of %s' % ( - attr, self.object_description(view)) + attr, + self.object_description(view), + ) else: view_desc = self.object_description(view) tmpl_intr = None - view_intr = self.introspectable('views', - discriminator, - view_desc, - 'view') - view_intr.update(dict( - name=name, - context=context, - exception_only=exception_only, - containment=containment, - request_param=request_param, - request_methods=request_method, - route_name=route_name, - attr=attr, - xhr=xhr, - accept=accept, - header=header, - path_info=path_info, - match_param=match_param, - check_csrf=check_csrf, - http_cache=http_cache, - require_csrf=require_csrf, - callable=view, - mapper=mapper, - decorator=decorator, - )) + view_intr = self.introspectable( + 'views', discriminator, view_desc, 'view' + ) + view_intr.update( + dict( + name=name, + context=context, + exception_only=exception_only, + containment=containment, + request_param=request_param, + request_methods=request_method, + route_name=route_name, + attr=attr, + xhr=xhr, + accept=accept, + header=header, + path_info=path_info, + match_param=match_param, + check_csrf=check_csrf, + http_cache=http_cache, + require_csrf=require_csrf, + callable=view, + mapper=mapper, + decorator=decorator, + ) + ) view_intr.update(view_options) introspectables.append(view_intr) def register(permission=permission, renderer=renderer): request_iface = IRequest if route_name is not None: - request_iface = self.registry.queryUtility(IRouteRequest, - name=route_name) + request_iface = self.registry.queryUtility( + IRouteRequest, name=route_name + ) if request_iface is None: # route configuration should have already happened in # phase 2 raise ConfigurationError( - 'No route named %s found for view registration' % - route_name) + 'No route named %s found for view registration' + % route_name + ) if renderer is None: # use default renderer if one exists (reg'd in phase 1) if self.registry.queryUtility(IRendererFactory) is not None: renderer = renderers.RendererHelper( - name=None, - package=self.package, - registry=self.registry - ) + name=None, package=self.package, registry=self.registry + ) renderer_type = getattr(renderer, 'type', None) intrspc = self.introspector if ( - renderer_type is not None and - tmpl_intr is not None and - intrspc is not None and - intrspc.get('renderer factories', renderer_type) is not None - ): + renderer_type is not None + and tmpl_intr is not None + and intrspc is not None + and intrspc.get('renderer factories', renderer_type) + is not None + ): # allow failure of registered template factories to be deferred # until view execution, like other bad renderer factories; if # we tried to relate this to an existing renderer factory @@ -1013,8 +1031,9 @@ class ViewsConfiguratorMixin(object): register_view(IViewClassifier, request_iface, derived_view) if isexc: derived_exc_view = derive_view(True, renderer) - register_view(IExceptionViewClassifier, request_iface, - derived_exc_view) + register_view( + IExceptionViewClassifier, request_iface, derived_exc_view + ) if exception_only: derived_view = derived_exc_view @@ -1085,8 +1104,8 @@ class ViewsConfiguratorMixin(object): for view_type in (IView, ISecuredView, IMultiView): old_view = registered( - (classifier, request_iface, r_context), - view_type, name) + (classifier, request_iface, r_context), view_type, name + ) if old_view is not None: break @@ -1109,8 +1128,8 @@ class ViewsConfiguratorMixin(object): derived_view, (classifier, request_iface, context), view_iface, - name - ) + name, + ) else: # - A view or multiview was already registered for this @@ -1136,32 +1155,33 @@ class ViewsConfiguratorMixin(object): # unregister any existing views self.registry.adapters.unregister( (classifier, request_iface, r_context), - view_type, name=name) + view_type, + name=name, + ) self.registry.registerAdapter( multiview, (classifier, request_iface, context), - IMultiView, name=name) + IMultiView, + name=name, + ) if mapper: mapper_intr = self.introspectable( 'view mappers', discriminator, 'view mapper for %s' % view_desc, - 'view mapper' - ) + 'view mapper', + ) mapper_intr['mapper'] = mapper mapper_intr.relate('views', discriminator) introspectables.append(mapper_intr) if route_name: - view_intr.relate('routes', route_name) # see add_route + view_intr.relate('routes', route_name) # see add_route if renderer is not None and renderer.name and '.' in renderer.name: # the renderer is a template tmpl_intr = self.introspectable( - 'templates', - discriminator, - renderer.name, - 'template' - ) + 'templates', discriminator, renderer.name, 'template' + ) tmpl_intr.relate('views', discriminator) tmpl_intr['name'] = renderer.name tmpl_intr['type'] = renderer.type @@ -1170,11 +1190,8 @@ class ViewsConfiguratorMixin(object): if permission is not None: # if a permission exists, register a permission introspectable perm_intr = self.introspectable( - 'permissions', - permission, - permission, - 'permission' - ) + 'permissions', permission, permission, 'permission' + ) perm_intr['value'] = permission perm_intr.relate('views', discriminator) introspectables.append(perm_intr) @@ -1192,8 +1209,10 @@ class ViewsConfiguratorMixin(object): def _apply_view_derivers(self, info): # These derivers are not really derivers and so have fixed order - outer_derivers = [('attr_wrapped_view', attr_wrapped_view), - ('predicated_view', predicated_view)] + outer_derivers = [ + ('attr_wrapped_view', attr_wrapped_view), + ('predicated_view', predicated_view), + ] view = info.original_view derivers = self.registry.getUtility(IViewDerivers) @@ -1202,8 +1221,9 @@ class ViewsConfiguratorMixin(object): return view @action_method - def add_view_predicate(self, name, factory, weighs_more_than=None, - weighs_less_than=None): + def add_view_predicate( + self, name, factory, weighs_more_than=None, weighs_less_than=None + ): """ .. versionadded:: 1.4 @@ -1226,8 +1246,8 @@ class ViewsConfiguratorMixin(object): name, factory, weighs_more_than=weighs_more_than, - weighs_less_than=weighs_less_than - ) + weighs_less_than=weighs_less_than, + ) def add_default_view_predicates(self): p = pyramid.predicates @@ -1245,7 +1265,7 @@ class ViewsConfiguratorMixin(object): ('physical_path', p.PhysicalPathPredicate), ('effective_principals', p.EffectivePrincipalsPredicate), ('custom', p.CustomPredicate), - ): + ): self.add_view_predicate(name, factory) def add_default_accept_view_order(self): @@ -1261,10 +1281,7 @@ class ViewsConfiguratorMixin(object): @action_method def add_accept_view_order( - self, - value, - weighs_more_than=None, - weighs_less_than=None, + self, value, weighs_more_than=None, weighs_less_than=None ): """ Specify an ordering preference for the ``accept`` view option used @@ -1293,19 +1310,22 @@ class ViewsConfiguratorMixin(object): .. versionadded:: 1.10 """ + def check_type(than): than_type, than_subtype, than_params = Accept.parse_offer(than) # text/plain vs text/html;charset=utf8 if bool(offer_params) ^ bool(than_params): raise ConfigurationError( 'cannot compare a media type with params to one without ' - 'params') + 'params' + ) # text/plain;charset=utf8 vs text/html;charset=utf8 if offer_params and ( offer_subtype != than_subtype or offer_type != than_type ): raise ConfigurationError( - 'cannot compare params across different media types') + 'cannot compare params across different media types' + ) def normalize_types(thans): thans = [normalize_accept_offer(than) for than in thans] @@ -1328,25 +1348,27 @@ class ViewsConfiguratorMixin(object): discriminator = ('accept view order', value) intr = self.introspectable( - 'accept view order', - value, - value, - 'accept view order') + 'accept view order', value, value, 'accept view order' + ) intr['value'] = value intr['weighs_more_than'] = weighs_more_than intr['weighs_less_than'] = weighs_less_than + def register(): sorter = self.registry.queryUtility(IAcceptOrder) if sorter is None: sorter = TopologicalSorter() self.registry.registerUtility(sorter, IAcceptOrder) sorter.add( - value, value, - before=weighs_more_than, - after=weighs_less_than, + value, value, before=weighs_more_than, after=weighs_less_than ) - self.action(discriminator, register, introspectables=(intr,), - order=PHASE1_CONFIG) # must be registered before add_view + + self.action( + discriminator, + register, + introspectables=(intr,), + order=PHASE1_CONFIG, + ) # must be registered before add_view @action_method def add_view_deriver(self, deriver, name=None, under=None, over=None): @@ -1390,8 +1412,9 @@ class ViewsConfiguratorMixin(object): name = deriver.__name__ if name in (INGRESS, VIEW): - raise ConfigurationError('%s is a reserved view deriver name' - % name) + raise ConfigurationError( + '%s is a reserved view deriver name' % name + ) if under is None: under = 'decorated_view' @@ -1415,15 +1438,12 @@ class ViewsConfiguratorMixin(object): raise ConfigurationError('%s cannot be under "mapped_view"' % name) discriminator = ('view deriver', name) - intr = self.introspectable( - 'view derivers', - name, - name, - 'view deriver') + intr = self.introspectable('view derivers', name, name, 'view deriver') intr['name'] = name intr['deriver'] = deriver intr['under'] = under intr['over'] = over + def register(): derivers = self.registry.queryUtility(IViewDerivers) if derivers is None: @@ -1435,8 +1455,13 @@ class ViewsConfiguratorMixin(object): ) self.registry.registerUtility(derivers, IViewDerivers) derivers.add(name, deriver, before=over, after=under) - self.action(discriminator, register, introspectables=(intr,), - order=PHASE1_CONFIG) # must be registered before add_view + + self.action( + discriminator, + register, + introspectables=(intr,), + order=PHASE1_CONFIG, + ) # must be registered before add_view def add_default_view_derivers(self): d = pyramid.viewderivers @@ -1450,12 +1475,7 @@ class ViewsConfiguratorMixin(object): ] last = INGRESS for name, deriver in derivers: - self.add_view_deriver( - deriver, - name=name, - under=last, - over=VIEW, - ) + self.add_view_deriver(deriver, name=name, under=last, over=VIEW) last = name # leave the csrf_view loosely coupled to the rest of the pipeline @@ -1547,26 +1567,39 @@ class ViewsConfiguratorMixin(object): return self._derive_view(view, attr=attr, renderer=renderer) # b/w compat - def _derive_view(self, view, permission=None, predicates=(), - attr=None, renderer=None, wrapper_viewname=None, - viewname=None, accept=None, order=MAX_ORDER, - phash=DEFAULT_PHASH, decorator=None, route_name=None, - mapper=None, http_cache=None, context=None, - require_csrf=None, exception_only=False, - extra_options=None): + def _derive_view( + self, + view, + permission=None, + predicates=(), + attr=None, + renderer=None, + wrapper_viewname=None, + viewname=None, + accept=None, + order=MAX_ORDER, + phash=DEFAULT_PHASH, + decorator=None, + route_name=None, + mapper=None, + http_cache=None, + context=None, + require_csrf=None, + exception_only=False, + extra_options=None, + ): view = self.maybe_dotted(view) mapper = self.maybe_dotted(mapper) if isinstance(renderer, string_types): renderer = renderers.RendererHelper( - name=renderer, package=self.package, - registry=self.registry) + name=renderer, package=self.package, registry=self.registry + ) if renderer is None: # use default renderer if one exists if self.registry.queryUtility(IRendererFactory) is not None: renderer = renderers.RendererHelper( - name=None, - package=self.package, - registry=self.registry) + name=None, package=self.package, registry=self.registry + ) options = dict( view=view, @@ -1581,7 +1614,7 @@ class ViewsConfiguratorMixin(object): decorator=decorator, http_cache=http_cache, require_csrf=require_csrf, - route_name=route_name + route_name=route_name, ) if extra_options: options.update(extra_options) @@ -1624,7 +1657,7 @@ class ViewsConfiguratorMixin(object): mapper=None, match_param=None, **view_options - ): + ): """ Add a forbidden view to the current configuration state. The view will be called when Pyramid or application code raises a :exc:`pyramid.httpexceptions.HTTPForbidden` exception and the set of @@ -1658,13 +1691,18 @@ class ViewsConfiguratorMixin(object): The view is created using ``exception_only=True``. """ for arg in ( - 'name', 'permission', 'context', 'for_', 'require_csrf', + 'name', + 'permission', + 'context', + 'for_', + 'require_csrf', 'exception_only', ): if arg in view_options: raise ConfigurationError( '%s may not be used as an argument to add_forbidden_view' - % (arg,)) + % (arg,) + ) if view is None: view = default_exceptionresponse_view @@ -1691,11 +1729,11 @@ class ViewsConfiguratorMixin(object): require_csrf=False, attr=attr, renderer=renderer, - ) + ) settings.update(view_options) return self.add_view(**settings) - set_forbidden_view = add_forbidden_view # deprecated sorta-bw-compat alias + set_forbidden_view = add_forbidden_view # deprecated sorta-bw-compat alias @viewdefaults @action_method @@ -1720,7 +1758,7 @@ class ViewsConfiguratorMixin(object): match_param=None, append_slash=False, **view_options - ): + ): """ Add a default :term:`Not Found View` to the current configuration state. The view will be called when Pyramid or application code raises an :exc:`pyramid.httpexceptions.HTTPNotFound` exception (e.g., when a @@ -1794,13 +1832,18 @@ class ViewsConfiguratorMixin(object): """ for arg in ( - 'name', 'permission', 'context', 'for_', 'require_csrf', + 'name', + 'permission', + 'context', + 'for_', + 'require_csrf', 'exception_only', ): if arg in view_options: raise ConfigurationError( '%s may not be used as an argument to add_notfound_view' - % (arg,)) + % (arg,) + ) if view is None: view = default_exceptionresponse_view @@ -1825,13 +1868,13 @@ class ViewsConfiguratorMixin(object): route_name=route_name, permission=NO_PERMISSION_REQUIRED, require_csrf=False, - ) + ) settings.update(view_options) if append_slash: view = self._derive_view(view, attr=attr, renderer=renderer) if IResponse.implementedBy(append_slash): view = AppendSlashNotFoundViewFactory( - view, redirect_class=append_slash, + view, redirect_class=append_slash ) else: view = AppendSlashNotFoundViewFactory(view) @@ -1841,7 +1884,7 @@ class ViewsConfiguratorMixin(object): settings['renderer'] = renderer return self.add_view(**settings) - set_notfound_view = add_notfound_view # deprecated sorta-bw-compat alias + set_notfound_view = add_notfound_view # deprecated sorta-bw-compat alias @viewdefaults @action_method @@ -1851,7 +1894,7 @@ class ViewsConfiguratorMixin(object): context=None, # force all other arguments to be specified as key=value **view_options - ): + ): """ Add an :term:`exception view` for the specified ``exception`` to the current configuration state. The view will be called when Pyramid or application code raises the given exception. @@ -1867,21 +1910,28 @@ class ViewsConfiguratorMixin(object): .. versionadded:: 1.8 """ for arg in ( - 'name', 'for_', 'exception_only', 'require_csrf', 'permission', + 'name', + 'for_', + 'exception_only', + 'require_csrf', + 'permission', ): if arg in view_options: raise ConfigurationError( '%s may not be used as an argument to add_exception_view' - % (arg,)) + % (arg,) + ) if context is None: context = Exception - view_options.update(dict( - view=view, - context=context, - exception_only=True, - permission=NO_PERMISSION_REQUIRED, - require_csrf=False, - )) + view_options.update( + dict( + view=view, + context=context, + exception_only=True, + permission=NO_PERMISSION_REQUIRED, + require_csrf=False, + ) + ) return self.add_view(**view_options) @action_method @@ -1909,17 +1959,25 @@ class ViewsConfiguratorMixin(object): can be used to achieve the same purpose. """ mapper = self.maybe_dotted(mapper) + def register(): self.registry.registerUtility(mapper, IViewMapperFactory) + # IViewMapperFactory is looked up as the result of view config # in phase 3 - intr = self.introspectable('view mappers', - IViewMapperFactory, - self.object_description(mapper), - 'default view mapper') + intr = self.introspectable( + 'view mappers', + IViewMapperFactory, + self.object_description(mapper), + 'default view mapper', + ) intr['mapper'] = mapper - self.action(IViewMapperFactory, register, order=PHASE1_CONFIG, - introspectables=(intr,)) + self.action( + IViewMapperFactory, + register, + order=PHASE1_CONFIG, + introspectables=(intr,), + ) @action_method def add_static_view(self, name, path, **kw): @@ -2054,14 +2112,15 @@ class ViewsConfiguratorMixin(object): self.registry.registerUtility(info, IStaticURLInfo) return info + def isexception(o): if IInterface.providedBy(o): if IException.isEqualOrExtendedBy(o): return True - return ( - isinstance(o, Exception) or - (inspect.isclass(o) and (issubclass(o, Exception))) - ) + return isinstance(o, Exception) or ( + inspect.isclass(o) and (issubclass(o, Exception)) + ) + def runtime_exc_view(view, excview): # create a view callable which can pretend to be both a normal view @@ -2094,6 +2153,7 @@ def runtime_exc_view(view, excview): fn = getattr(selected_view, attr, None) if fn is not None: return fn(context, request) + return wrapper # these methods are dynamic per-request and should dispatch to their @@ -2104,16 +2164,12 @@ def runtime_exc_view(view, excview): wrapper_view.__predicates__ = wrap_fn('__predicates__') return wrapper_view + @implementer(IViewDeriverInfo) class ViewDeriverInfo(object): - def __init__(self, - view, - registry, - package, - predicates, - exception_only, - options, - ): + def __init__( + self, view, registry, package, predicates, exception_only, options + ): self.original_view = view self.registry = registry self.package = package @@ -2125,6 +2181,7 @@ class ViewDeriverInfo(object): def settings(self): return self.registry.settings + @implementer(IStaticURLInfo) class StaticURLInfo(object): def __init__(self): @@ -2134,12 +2191,13 @@ class StaticURLInfo(object): def generate(self, path, request, **kw): for (url, spec, route_name) in self.registrations: if path.startswith(spec): - subpath = path[len(spec):] - if WIN: # pragma: no cover - subpath = subpath.replace('\\', '/') # windows + subpath = path[len(spec) :] + if WIN: # pragma: no cover + subpath = subpath.replace('\\', '/') # windows if self.cache_busters: subpath, kw = self._bust_asset_path( - request, spec, subpath, kw) + request, spec, subpath, kw + ) if url is None: kw['subpath'] = subpath return request.route_url(route_name, **kw) @@ -2147,8 +2205,11 @@ class StaticURLInfo(object): app_url, qs, anchor = parse_url_overrides(request, kw) parsed = url_parse(url) if not parsed.scheme: - url = urlparse.urlunparse(parsed._replace( - scheme=request.environ['wsgi.url_scheme'])) + url = urlparse.urlunparse( + parsed._replace( + scheme=request.environ['wsgi.url_scheme'] + ) + ) subpath = url_quote(subpath) result = urljoin(url, subpath) return result + qs + anchor @@ -2161,7 +2222,7 @@ class StaticURLInfo(object): # appending a slash here if the spec doesn't have one is # required for proper prefix matching done in ``generate`` # (``subpath = path[len(spec):]``). - if os.path.isabs(spec): # FBO windows + if os.path.isabs(spec): # FBO windows sep = os.sep else: sep = '/' @@ -2189,8 +2250,9 @@ class StaticURLInfo(object): cache_max_age = extra.pop('cache_max_age', None) # create a view - view = static_view(spec, cache_max_age=cache_max_age, - use_subpath=True) + view = static_view( + spec, cache_max_age=cache_max_age, use_subpath=True + ) # Mutate extra to allow factory, etc to be passed through here. # Treat permission specially because we'd like to default to @@ -2207,7 +2269,7 @@ class StaticURLInfo(object): # register a route using the computed view, permission, and # pattern, plus any extras passed to us via add_static_view - pattern = "%s*subpath" % name # name already ends with slash + pattern = "%s*subpath" % name # name already ends with slash if config.route_prefix: route_name = '__%s/%s' % (config.route_prefix, name) else: @@ -2233,10 +2295,9 @@ class StaticURLInfo(object): # url, spec, route_name registrations.append((url, spec, route_name)) - intr = config.introspectable('static views', - name, - 'static view for %r' % name, - 'static view') + intr = config.introspectable( + 'static views', name, 'static view for %r' % name, 'static view' + ) intr['name'] = name intr['spec'] = spec @@ -2245,7 +2306,7 @@ class StaticURLInfo(object): def add_cache_buster(self, config, spec, cachebust, explicit=False): # ensure the spec always has a trailing slash as we only support # adding cache busters to folders, not files - if os.path.isabs(spec): # FBO windows + if os.path.isabs(spec): # FBO windows sep = os.sep else: sep = '/' @@ -2282,10 +2343,9 @@ class StaticURLInfo(object): cache_busters.insert(new_idx, (spec, cachebust, explicit)) - intr = config.introspectable('cache busters', - spec, - 'cache buster for %r' % spec, - 'cache buster') + intr = config.introspectable( + 'cache busters', spec, 'cache buster for %r' % spec, 'cache buster' + ) intr['cachebust'] = cachebust intr['path'] = spec intr['explicit'] = explicit @@ -2318,9 +2378,8 @@ class StaticURLInfo(object): kw['pathspec'] = pathspec kw['rawspec'] = rawspec for spec_, cachebust, explicit in reversed(self.cache_busters): - if ( - (explicit and rawspec.startswith(spec_)) or - (not explicit and pathspec.startswith(spec_)) + if (explicit and rawspec.startswith(spec_)) or ( + not explicit and pathspec.startswith(spec_) ): subpath, kw = cachebust(request, subpath, kw) break diff --git a/src/pyramid/config/zca.py b/src/pyramid/config/zca.py index bcd5c31e3..7bf637632 100644 --- a/src/pyramid/config/zca.py +++ b/src/pyramid/config/zca.py @@ -1,5 +1,6 @@ from pyramid.threadlocal import get_current_registry + class ZCAConfiguratorMixin(object): def hook_zca(self): """ Call :func:`zope.component.getSiteManager.sethook` with the @@ -10,11 +11,12 @@ class ZCAConfiguratorMixin(object): :app:`Pyramid` :term:`application registry` rather than the Zope 'global' registry.""" from zope.component import getSiteManager + getSiteManager.sethook(get_current_registry) def unhook_zca(self): """ Call :func:`zope.component.getSiteManager.reset` to undo the action of :meth:`pyramid.config.Configurator.hook_zca`.""" from zope.component import getSiteManager - getSiteManager.reset() + getSiteManager.reset() diff --git a/src/pyramid/csrf.py b/src/pyramid/csrf.py index da171d9af..582d6a641 100644 --- a/src/pyramid/csrf.py +++ b/src/pyramid/csrf.py @@ -4,22 +4,11 @@ from webob.cookies import CookieProfile from zope.interface import implementer -from pyramid.compat import ( - bytes_, - urlparse, - text_, -) -from pyramid.exceptions import ( - BadCSRFOrigin, - BadCSRFToken, -) +from pyramid.compat import bytes_, urlparse, text_ +from pyramid.exceptions import BadCSRFOrigin, BadCSRFToken from pyramid.interfaces import ICSRFStoragePolicy from pyramid.settings import aslist -from pyramid.util import ( - SimpleSerializer, - is_same_domain, - strings_differ -) +from pyramid.util import SimpleSerializer, is_same_domain, strings_differ @implementer(ICSRFStoragePolicy) @@ -37,6 +26,7 @@ class LegacySessionCSRFStoragePolicy(object): .. versionadded:: 1.9 """ + def new_csrf_token(self, request): """ Sets a new CSRF token into the session and returns it. """ return request.session.new_csrf_token() @@ -50,7 +40,8 @@ class LegacySessionCSRFStoragePolicy(object): """ Returns ``True`` if the ``supplied_token`` is valid.""" expected_token = self.get_csrf_token(request) return not strings_differ( - bytes_(expected_token), bytes_(supplied_token)) + bytes_(expected_token), bytes_(supplied_token) + ) @implementer(ICSRFStoragePolicy) @@ -68,6 +59,7 @@ class SessionCSRFStoragePolicy(object): .. versionadded:: 1.9 """ + _token_factory = staticmethod(lambda: text_(uuid.uuid4().hex)) def __init__(self, key='_csrft_'): @@ -91,7 +83,8 @@ class SessionCSRFStoragePolicy(object): """ Returns ``True`` if the ``supplied_token`` is valid.""" expected_token = self.get_csrf_token(request) return not strings_differ( - bytes_(expected_token), bytes_(supplied_token)) + bytes_(expected_token), bytes_(supplied_token) + ) @implementer(ICSRFStoragePolicy) @@ -111,10 +104,19 @@ class CookieCSRFStoragePolicy(object): Added the ``samesite`` option and made the default ``'Lax'``. """ + _token_factory = staticmethod(lambda: text_(uuid.uuid4().hex)) - def __init__(self, cookie_name='csrf_token', secure=False, httponly=False, - domain=None, max_age=None, path='/', samesite='Lax'): + def __init__( + self, + cookie_name='csrf_token', + secure=False, + httponly=False, + domain=None, + max_age=None, + path='/', + samesite='Lax', + ): serializer = SimpleSerializer() self.cookie_profile = CookieProfile( cookie_name=cookie_name, @@ -132,11 +134,10 @@ class CookieCSRFStoragePolicy(object): """ Sets a new CSRF token into the request and returns it. """ token = self._token_factory() request.cookies[self.cookie_name] = token + def set_cookie(request, response): - self.cookie_profile.set_cookies( - response, - token, - ) + self.cookie_profile.set_cookies(response, token) + request.add_response_callback(set_cookie) return token @@ -153,7 +154,8 @@ class CookieCSRFStoragePolicy(object): """ Returns ``True`` if the ``supplied_token`` is valid.""" expected_token = self.get_csrf_token(request) return not strings_differ( - bytes_(expected_token), bytes_(supplied_token)) + bytes_(expected_token), bytes_(supplied_token) + ) def get_csrf_token(request): @@ -182,10 +184,9 @@ def new_csrf_token(request): return csrf.new_csrf_token(request) -def check_csrf_token(request, - token='csrf_token', - header='X-CSRF-Token', - raises=True): +def check_csrf_token( + request, token='csrf_token', header='X-CSRF-Token', raises=True +): """ Check the CSRF token returned by the :class:`pyramid.interfaces.ICSRFStoragePolicy` implementation against the value in ``request.POST.get(token)`` (if a POST request) or @@ -267,6 +268,7 @@ def check_csrf_origin(request, trusted_origins=None, raises=True): Moved from :mod:`pyramid.session` to :mod:`pyramid.csrf` """ + def _fail(reason): if raises: raise BadCSRFOrigin(reason) @@ -315,7 +317,8 @@ def check_csrf_origin(request, trusted_origins=None, raises=True): if trusted_origins is None: trusted_origins = aslist( request.registry.settings.get( - "pyramid.csrf_trusted_origins", []) + "pyramid.csrf_trusted_origins", [] + ) ) if request.host_port not in set(["80", "443"]): @@ -325,8 +328,9 @@ def check_csrf_origin(request, trusted_origins=None, raises=True): # Actually check to see if the request's origin matches any of our # trusted origins. - if not any(is_same_domain(originp.netloc, host) - for host in trusted_origins): + if not any( + is_same_domain(originp.netloc, host) for host in trusted_origins + ): reason = ( "Referer checking failed - {0} does not match any trusted " "origins." diff --git a/src/pyramid/decorator.py b/src/pyramid/decorator.py index 065a3feed..89cbb0f6e 100644 --- a/src/pyramid/decorator.py +++ b/src/pyramid/decorator.py @@ -32,6 +32,7 @@ class reify(object): >>> f.jammy 2 """ + def __init__(self, wrapped): self.wrapped = wrapped update_wrapper(self, wrapped) @@ -42,4 +43,3 @@ class reify(object): val = self.wrapped(inst) setattr(inst, self.wrapped.__name__, val) return val - diff --git a/src/pyramid/encode.py b/src/pyramid/encode.py index 73ff14e62..2cf2247da 100644 --- a/src/pyramid/encode.py +++ b/src/pyramid/encode.py @@ -4,9 +4,10 @@ from pyramid.compat import ( is_nonstr_iter, url_quote as _url_quote, url_quote_plus as _quote_plus, - ) +) -def url_quote(val, safe=''): # bw compat api + +def url_quote(val, safe=''): # bw compat api cls = val.__class__ if cls is text_type: val = val.encode('utf-8') @@ -14,6 +15,7 @@ def url_quote(val, safe=''): # bw compat api val = str(val).encode('utf-8') return _url_quote(val, safe=safe) + # bw compat api (dnr) def quote_plus(val, safe=''): cls = val.__class__ @@ -23,6 +25,7 @@ def quote_plus(val, safe=''): val = str(val).encode('utf-8') return _quote_plus(val, safe=safe) + def urlencode(query, doseq=True, quote_via=quote_plus): """ An alternate implementation of Python's stdlib diff --git a/src/pyramid/events.py b/src/pyramid/events.py index 93fc127a1..fb3730f63 100644 --- a/src/pyramid/events.py +++ b/src/pyramid/events.py @@ -1,9 +1,6 @@ import venusian -from zope.interface import ( - implementer, - Interface - ) +from zope.interface import implementer, Interface from pyramid.interfaces import ( IContextFound, @@ -12,7 +9,8 @@ from pyramid.interfaces import ( IApplicationCreated, IBeforeRender, IBeforeTraversal, - ) +) + class subscriber(object): """ Decorator activated via a :term:`scan` which treats the function @@ -89,7 +87,8 @@ class subscriber(object): Added the ``_depth`` and ``_category`` arguments. """ - venusian = venusian # for unit testing + + venusian = venusian # for unit testing def __init__(self, *ifaces, **predicates): self.ifaces = ifaces @@ -103,10 +102,15 @@ class subscriber(object): config.add_subscriber(wrapped, iface, **self.predicates) def __call__(self, wrapped): - self.venusian.attach(wrapped, self.register, category=self.category, - depth=self.depth + 1) + self.venusian.attach( + wrapped, + self.register, + category=self.category, + depth=self.depth + 1, + ) return wrapped + @implementer(INewRequest) class NewRequest(object): """ An instance of this class is emitted as an :term:`event` @@ -114,9 +118,11 @@ class NewRequest(object): event instance has an attribute, ``request``, which is a :term:`request` object. This event class implements the :class:`pyramid.interfaces.INewRequest` interface.""" + def __init__(self, request): self.request = request + @implementer(INewResponse) class NewResponse(object): """ An instance of this class is emitted as an :term:`event` @@ -149,10 +155,12 @@ class NewResponse(object): almost purely for symmetry with the :class:`pyramid.interfaces.INewRequest` event. """ + def __init__(self, request, response): self.request = request self.response = response + @implementer(IBeforeTraversal) class BeforeTraversal(object): """ @@ -173,6 +181,7 @@ class BeforeTraversal(object): def __init__(self, request): self.request = request + @implementer(IContextFound) class ContextFound(object): """ An instance of this class is emitted as an :term:`event` after @@ -194,10 +203,13 @@ class ContextFound(object): As of :app:`Pyramid` 1.0, for backwards compatibility purposes, this event may also be imported as :class:`pyramid.events.AfterTraversal`. """ + def __init__(self, request): self.request = request -AfterTraversal = ContextFound # b/c as of 1.0 + +AfterTraversal = ContextFound # b/c as of 1.0 + @implementer(IApplicationCreated) class ApplicationCreated(object): @@ -214,11 +226,14 @@ class ApplicationCreated(object): :class:`pyramid.events.WSGIApplicationCreatedEvent`. This was the name of the event class before :app:`Pyramid` 1.0. """ + def __init__(self, app): self.app = app self.object = app -WSGIApplicationCreatedEvent = ApplicationCreated # b/c (as of 1.0) + +WSGIApplicationCreatedEvent = ApplicationCreated # b/c (as of 1.0) + @implementer(IBeforeRender) class BeforeRender(dict): @@ -283,7 +298,7 @@ class BeforeRender(dict): See also :class:`pyramid.interfaces.IBeforeRender`. """ + def __init__(self, system, rendering_val=None): dict.__init__(self, system) self.rendering_val = rendering_val - diff --git a/src/pyramid/exceptions.py b/src/pyramid/exceptions.py index c95922eb0..9a7054731 100644 --- a/src/pyramid/exceptions.py +++ b/src/pyramid/exceptions.py @@ -1,11 +1,7 @@ -from pyramid.httpexceptions import ( - HTTPBadRequest, - HTTPNotFound, - HTTPForbidden, - ) +from pyramid.httpexceptions import HTTPBadRequest, HTTPNotFound, HTTPForbidden -NotFound = HTTPNotFound # bw compat -Forbidden = HTTPForbidden # bw compat +NotFound = HTTPNotFound # bw compat +Forbidden = HTTPForbidden # bw compat CR = '\n' @@ -15,6 +11,7 @@ class BadCSRFOrigin(HTTPBadRequest): This exception indicates the request has failed cross-site request forgery origin validation. """ + title = "Bad CSRF Origin" explanation = ( "Access is denied. This server can not verify that the origin or " @@ -29,6 +26,7 @@ class BadCSRFToken(HTTPBadRequest): This exception indicates the request has failed cross-site request forgery token validation. """ + title = 'Bad CSRF Token' explanation = ( 'Access is denied. This server can not verify that your cross-site ' @@ -36,7 +34,9 @@ class BadCSRFToken(HTTPBadRequest): 'supplied the wrong cross-site request forgery token or your session ' 'no longer exists. This may be due to session timeout or because ' 'browser is not supplying the credentials required, as can happen ' - 'when the browser has cookies turned off.') + 'when the browser has cookies turned off.' + ) + class PredicateMismatch(HTTPNotFound): """ @@ -65,6 +65,7 @@ class PredicateMismatch(HTTPNotFound): exception view. """ + class URLDecodeError(UnicodeDecodeError): """ This exception is raised when :app:`Pyramid` cannot @@ -76,10 +77,12 @@ class URLDecodeError(UnicodeDecodeError): decoded. """ + class ConfigurationError(Exception): """ Raised when inappropriate input values are supplied to an API method of a :term:`Configurator`""" + class ConfigurationConflictError(ConfigurationError): """ Raised when a configuration conflict is detected during action processing""" @@ -91,7 +94,7 @@ class ConfigurationConflictError(ConfigurationError): r = ["Conflicting configuration actions"] items = sorted(self._conflicts.items()) for discriminator, infos in items: - r.append(" For: %s" % (discriminator, )) + r.append(" For: %s" % (discriminator,)) for info in infos: for line in str(info).rstrip().split(CR): r.append(" " + line) @@ -113,6 +116,7 @@ class ConfigurationExecutionError(ConfigurationError): class CyclicDependencyError(Exception): """ The exception raised when the Pyramid topological sorter detects a cyclic dependency.""" + def __init__(self, cycles): self.cycles = cycles diff --git a/src/pyramid/httpexceptions.py b/src/pyramid/httpexceptions.py index bef8420b1..359601399 100644 --- a/src/pyramid/httpexceptions.py +++ b/src/pyramid/httpexceptions.py @@ -137,16 +137,12 @@ from zope.interface import implementer from webob import html_escape as _html_escape from webob.acceptparse import create_accept_header -from pyramid.compat import ( - class_types, - text_type, - binary_type, - text_, - ) +from pyramid.compat import class_types, text_type, binary_type, text_ from pyramid.interfaces import IExceptionResponse from pyramid.response import Response + def _no_escape(value): if value is None: return '' @@ -159,6 +155,7 @@ def _no_escape(value): value = text_type(value) return value + @implementer(IExceptionResponse) class HTTPException(Response, Exception): @@ -195,18 +192,23 @@ class HTTPException(Response, Exception): code = 520 title = 'Unknown Error' explanation = '' - body_template_obj = Template('''\ + body_template_obj = Template( + '''\ ${explanation}${br}${br} ${detail} ${html_comment} -''') +''' + ) - plain_template_obj = Template('''\ + plain_template_obj = Template( + '''\ ${status} -${body}''') +${body}''' + ) - html_template_obj = Template('''\ + html_template_obj = Template( + '''\ <html> <head> <title>${status}</title> @@ -215,13 +217,21 @@ ${body}''') <h1>${status}</h1> ${body} </body> -</html>''') +</html>''' + ) ## Set this to True for responses that should have no request body empty_body = False - def __init__(self, detail=None, headers=None, comment=None, - body_template=None, json_formatter=None, **kw): + def __init__( + self, + detail=None, + headers=None, + comment=None, + body_template=None, + json_formatter=None, + **kw + ): status = '%s %s' % (self.code, self.title) Response.__init__(self, status=status, **kw) Exception.__init__(self, detail) @@ -243,9 +253,7 @@ ${body}''') return str(self.detail) if self.detail else self.explanation def _json_formatter(self, status, body, title, environ): - return {'message': body, - 'code': status, - 'title': self.title} + return {'message': body, 'code': status, 'title': self.title} def prepare(self, environ): if not self.has_body and not self.empty_body: @@ -255,7 +263,9 @@ ${body}''') accept = create_accept_header(accept_value) # Attempt to match text/html or application/json, if those don't # match, we will fall through to defaulting to text/plain - acceptable = accept.acceptable_offers(['text/html', 'application/json']) + acceptable = accept.acceptable_offers( + ['text/html', 'application/json'] + ) acceptable = [offer[0] for offer in acceptable] + ['text/plain'] match = acceptable[0] @@ -281,8 +291,10 @@ ${body}''') def substitute(self, status, body): jsonbody = self.excobj._json_formatter( status=status, - body=body, title=self.excobj.title, - environ=environ) + body=body, + title=self.excobj.title, + environ=environ, + ) return json.dumps(jsonbody) page_template = JsonPageTemplate(self) @@ -299,7 +311,7 @@ ${body}''') 'detail': escape(self.detail or ''), 'comment': escape(comment), 'html_comment': html_comment, - } + } body_tmpl = self.body_template_obj if HTTPException.body_template_obj is not body_tmpl: # Custom template; add headers to args @@ -324,7 +336,7 @@ ${body}''') # bw compat only return self - exception = wsgi_response # bw compat only + exception = wsgi_response # bw compat only def __call__(self, environ, start_response): # differences from webob.exc.WSGIHTTPException @@ -337,7 +349,9 @@ ${body}''') self.prepare(environ) return Response.__call__(self, environ, start_response) -WSGIHTTPException = HTTPException # b/c post 1.5 + +WSGIHTTPException = HTTPException # b/c post 1.5 + class HTTPError(HTTPException): """ @@ -347,6 +361,7 @@ class HTTPError(HTTPException): and that any work in progress should not be committed. """ + class HTTPRedirection(HTTPException): """ base class for exceptions with status codes in the 300s (redirections) @@ -357,16 +372,19 @@ class HTTPRedirection(HTTPException): condition. """ + class HTTPSuccessful(HTTPException): """ Base class for exceptions with status codes in the 200s (successful responses) """ + ############################################################ ## 2xx success ############################################################ + class HTTPOk(HTTPSuccessful): """ subclass of :class:`~HTTPSuccessful` @@ -375,9 +393,11 @@ class HTTPOk(HTTPSuccessful): code: 200, title: OK """ + code = 200 title = 'OK' + class HTTPCreated(HTTPSuccessful): """ subclass of :class:`~HTTPSuccessful` @@ -387,9 +407,11 @@ class HTTPCreated(HTTPSuccessful): code: 201, title: Created """ + code = 201 title = 'Created' + class HTTPAccepted(HTTPSuccessful): """ subclass of :class:`~HTTPSuccessful` @@ -399,10 +421,12 @@ class HTTPAccepted(HTTPSuccessful): code: 202, title: Accepted """ + code = 202 title = 'Accepted' explanation = 'The request is accepted for processing.' + class HTTPNonAuthoritativeInformation(HTTPSuccessful): """ subclass of :class:`~HTTPSuccessful` @@ -413,9 +437,11 @@ class HTTPNonAuthoritativeInformation(HTTPSuccessful): code: 203, title: Non-Authoritative Information """ + code = 203 title = 'Non-Authoritative Information' + class HTTPNoContent(HTTPSuccessful): """ subclass of :class:`~HTTPSuccessful` @@ -426,10 +452,12 @@ class HTTPNoContent(HTTPSuccessful): code: 204, title: No Content """ + code = 204 title = 'No Content' empty_body = True + class HTTPResetContent(HTTPSuccessful): """ subclass of :class:`~HTTPSuccessful` @@ -440,10 +468,12 @@ class HTTPResetContent(HTTPSuccessful): code: 205, title: Reset Content """ + code = 205 title = 'Reset Content' empty_body = True + class HTTPPartialContent(HTTPSuccessful): """ subclass of :class:`~HTTPSuccessful` @@ -453,15 +483,18 @@ class HTTPPartialContent(HTTPSuccessful): code: 206, title: Partial Content """ + code = 206 title = 'Partial Content' + ## FIXME: add 207 Multi-Status (but it's complicated) ############################################################ ## 3xx redirection ############################################################ + class _HTTPMove(HTTPRedirection): """ redirections which require a Location field @@ -472,6 +505,7 @@ class _HTTPMove(HTTPRedirection): You must provide a ``location`` keyword argument. """ + # differences from webob.exc._HTTPMove: # # - ${location} isn't wrapped in an <a> tag in body @@ -486,18 +520,33 @@ class _HTTPMove(HTTPRedirection): # - ``add_slash`` argument is no longer accepted: code that passes # add_slash argument to the constructor will receive an exception. explanation = 'The resource has been moved to' - body_template_obj = Template('''\ + body_template_obj = Template( + '''\ ${explanation} ${location}; you should be redirected automatically. ${detail} -${html_comment}''') +${html_comment}''' + ) - def __init__(self, location='', detail=None, headers=None, comment=None, - body_template=None, **kw): + def __init__( + self, + location='', + detail=None, + headers=None, + comment=None, + body_template=None, + **kw + ): if location is None: raise ValueError("HTTP redirects need a location to redirect to.") super(_HTTPMove, self).__init__( - detail=detail, headers=headers, comment=comment, - body_template=body_template, location=location, **kw) + detail=detail, + headers=headers, + comment=comment, + body_template=body_template, + location=location, + **kw + ) + class HTTPMultipleChoices(_HTTPMove): """ @@ -511,9 +560,11 @@ class HTTPMultipleChoices(_HTTPMove): code: 300, title: Multiple Choices """ + code = 300 title = 'Multiple Choices' + class HTTPMovedPermanently(_HTTPMove): """ subclass of :class:`~_HTTPMove` @@ -524,9 +575,11 @@ class HTTPMovedPermanently(_HTTPMove): code: 301, title: Moved Permanently """ + code = 301 title = 'Moved Permanently' + class HTTPFound(_HTTPMove): """ subclass of :class:`~_HTTPMove` @@ -536,10 +589,12 @@ class HTTPFound(_HTTPMove): code: 302, title: Found """ + code = 302 title = 'Found' explanation = 'The resource was found at' + # This one is safe after a POST (the redirected location will be # retrieved with GET): class HTTPSeeOther(_HTTPMove): @@ -552,9 +607,11 @@ class HTTPSeeOther(_HTTPMove): code: 303, title: See Other """ + code = 303 title = 'See Other' + class HTTPNotModified(HTTPRedirection): """ subclass of :class:`~HTTPRedirection` @@ -565,11 +622,13 @@ class HTTPNotModified(HTTPRedirection): code: 304, title: Not Modified """ + # FIXME: this should include a date or etag header code = 304 title = 'Not Modified' empty_body = True + class HTTPUseProxy(_HTTPMove): """ subclass of :class:`~_HTTPMove` @@ -579,11 +638,12 @@ class HTTPUseProxy(_HTTPMove): code: 305, title: Use Proxy """ + # Not a move, but looks a little like one code = 305 title = 'Use Proxy' - explanation = ( - 'The resource must be accessed through a proxy located at') + explanation = 'The resource must be accessed through a proxy located at' + class HTTPTemporaryRedirect(_HTTPMove): """ @@ -594,9 +654,11 @@ class HTTPTemporaryRedirect(_HTTPMove): code: 307, title: Temporary Redirect """ + code = 307 title = 'Temporary Redirect' + class HTTPPermanentRedirect(_HTTPMove): """ subclass of :class:`~_HTTPMove` @@ -607,13 +669,16 @@ class HTTPPermanentRedirect(_HTTPMove): code: 308, title: Permanent Redirect """ + code = 308 title = 'Permanent Redirect' + ############################################################ ## 4xx client error ############################################################ + class HTTPClientError(HTTPError): """ base class for the 400s, where the client is in error @@ -623,9 +688,11 @@ class HTTPClientError(HTTPError): a bug. A server-side traceback is not warranted. Unless specialized, this is a '400 Bad Request' """ + code = 400 title = 'Bad Request' + class HTTPBadRequest(HTTPClientError): """ subclass of :class:`~HTTPClientError` @@ -635,8 +702,12 @@ class HTTPBadRequest(HTTPClientError): code: 400, title: Bad Request """ - explanation = ('The server could not comply with the request since ' - 'it is either malformed or otherwise incorrect.') + + explanation = ( + 'The server could not comply with the request since ' + 'it is either malformed or otherwise incorrect.' + ) + class HTTPUnauthorized(HTTPClientError): """ @@ -646,13 +717,16 @@ class HTTPUnauthorized(HTTPClientError): code: 401, title: Unauthorized """ + code = 401 title = 'Unauthorized' explanation = ( 'This server could not verify that you are authorized to ' 'access the document you requested. Either you supplied the ' 'wrong credentials (e.g., bad password), or your browser ' - 'does not understand how to supply the credentials required.') + 'does not understand how to supply the credentials required.' + ) + class HTTPPaymentRequired(HTTPClientError): """ @@ -660,9 +734,11 @@ class HTTPPaymentRequired(HTTPClientError): code: 402, title: Payment Required """ + code = 402 title = 'Payment Required' - explanation = ('Access was denied for financial reasons.') + explanation = 'Access was denied for financial reasons.' + class HTTPForbidden(HTTPClientError): """ @@ -693,6 +769,7 @@ class HTTPForbidden(HTTPClientError): exception as necessary to provide extended information in an error report shown to a user. """ + # differences from webob.exc.HTTPForbidden: # # - accepts a ``result`` keyword argument @@ -705,14 +782,28 @@ class HTTPForbidden(HTTPClientError): # code = 403 title = 'Forbidden' - explanation = ('Access was denied to this resource.') - def __init__(self, detail=None, headers=None, comment=None, - body_template=None, result=None, **kw): - HTTPClientError.__init__(self, detail=detail, headers=headers, - comment=comment, body_template=body_template, - **kw) + explanation = 'Access was denied to this resource.' + + def __init__( + self, + detail=None, + headers=None, + comment=None, + body_template=None, + result=None, + **kw + ): + HTTPClientError.__init__( + self, + detail=detail, + headers=headers, + comment=comment, + body_template=body_template, + **kw + ) self.result = result + class HTTPNotFound(HTTPClientError): """ subclass of :class:`~HTTPClientError` @@ -732,9 +823,11 @@ class HTTPNotFound(HTTPClientError): string will be available as the ``message`` attribute of this exception, for availability to the :term:`Not Found View`. """ + code = 404 title = 'Not Found' - explanation = ('The resource could not be found.') + explanation = 'The resource could not be found.' + class HTTPMethodNotAllowed(HTTPClientError): """ @@ -745,14 +838,18 @@ class HTTPMethodNotAllowed(HTTPClientError): code: 405, title: Method Not Allowed """ + # differences from webob.exc.HTTPMethodNotAllowed: # # - body_template_obj uses ${br} instead of <br /> code = 405 title = 'Method Not Allowed' - body_template_obj = Template('''\ + body_template_obj = Template( + '''\ The method ${REQUEST_METHOD} is not allowed for this resource. ${br}${br} -${detail}''') +${detail}''' + ) + class HTTPNotAcceptable(HTTPClientError): """ @@ -765,12 +862,14 @@ class HTTPNotAcceptable(HTTPClientError): code: 406, title: Not Acceptable """ + # differences from webob.exc.HTTPNotAcceptable: # # - "template" attribute left off (useless, bug in webob?) code = 406 title = 'Not Acceptable' + class HTTPProxyAuthenticationRequired(HTTPClientError): """ subclass of :class:`~HTTPClientError` @@ -780,9 +879,11 @@ class HTTPProxyAuthenticationRequired(HTTPClientError): code: 407, title: Proxy Authentication Required """ + code = 407 title = 'Proxy Authentication Required' - explanation = ('Authentication with a local proxy is needed.') + explanation = 'Authentication with a local proxy is needed.' + class HTTPRequestTimeout(HTTPClientError): """ @@ -793,10 +894,14 @@ class HTTPRequestTimeout(HTTPClientError): code: 408, title: Request Timeout """ + code = 408 title = 'Request Timeout' - explanation = ('The server has waited too long for the request to ' - 'be sent by the client.') + explanation = ( + 'The server has waited too long for the request to ' + 'be sent by the client.' + ) + class HTTPConflict(HTTPClientError): """ @@ -807,10 +912,13 @@ class HTTPConflict(HTTPClientError): code: 409, title: Conflict """ + code = 409 title = 'Conflict' - explanation = ('There was a conflict when trying to complete ' - 'your request.') + explanation = ( + 'There was a conflict when trying to complete ' 'your request.' + ) + class HTTPGone(HTTPClientError): """ @@ -821,10 +929,14 @@ class HTTPGone(HTTPClientError): code: 410, title: Gone """ + code = 410 title = 'Gone' - explanation = ('This resource is no longer available. No forwarding ' - 'address is given.') + explanation = ( + 'This resource is no longer available. No forwarding ' + 'address is given.' + ) + class HTTPLengthRequired(HTTPClientError): """ @@ -835,9 +947,11 @@ class HTTPLengthRequired(HTTPClientError): code: 411, title: Length Required """ + code = 411 title = 'Length Required' - explanation = ('Content-Length header required.') + explanation = 'Content-Length header required.' + class HTTPPreconditionFailed(HTTPClientError): """ @@ -849,9 +963,11 @@ class HTTPPreconditionFailed(HTTPClientError): code: 412, title: Precondition Failed """ + code = 412 title = 'Precondition Failed' - explanation = ('Request precondition failed.') + explanation = 'Request precondition failed.' + class HTTPRequestEntityTooLarge(HTTPClientError): """ @@ -863,9 +979,11 @@ class HTTPRequestEntityTooLarge(HTTPClientError): code: 413, title: Request Entity Too Large """ + code = 413 title = 'Request Entity Too Large' - explanation = ('The body of your request was too large for this server.') + explanation = 'The body of your request was too large for this server.' + class HTTPRequestURITooLong(HTTPClientError): """ @@ -877,9 +995,11 @@ class HTTPRequestURITooLong(HTTPClientError): code: 414, title: Request-URI Too Long """ + code = 414 title = 'Request-URI Too Long' - explanation = ('The request URI was too long for this server.') + explanation = 'The request URI was too long for this server.' + class HTTPUnsupportedMediaType(HTTPClientError): """ @@ -891,12 +1011,14 @@ class HTTPUnsupportedMediaType(HTTPClientError): code: 415, title: Unsupported Media Type """ + # differences from webob.exc.HTTPUnsupportedMediaType: # # - "template_obj" attribute left off (useless, bug in webob?) code = 415 title = 'Unsupported Media Type' + class HTTPRequestRangeNotSatisfiable(HTTPClientError): """ subclass of :class:`~HTTPClientError` @@ -909,9 +1031,11 @@ class HTTPRequestRangeNotSatisfiable(HTTPClientError): code: 416, title: Request Range Not Satisfiable """ + code = 416 title = 'Request Range Not Satisfiable' - explanation = ('The Range requested is not available.') + explanation = 'The Range requested is not available.' + class HTTPExpectationFailed(HTTPClientError): """ @@ -922,9 +1046,11 @@ class HTTPExpectationFailed(HTTPClientError): code: 417, title: Expectation Failed """ + code = 417 title = 'Expectation Failed' - explanation = ('Expectation failed.') + explanation = 'Expectation failed.' + class HTTPUnprocessableEntity(HTTPClientError): """ @@ -940,11 +1066,13 @@ class HTTPUnprocessableEntity(HTTPClientError): code: 422, title: Unprocessable Entity """ + ## Note: from WebDAV code = 422 title = 'Unprocessable Entity' explanation = 'Unable to process the contained instructions' + class HTTPLocked(HTTPClientError): """ subclass of :class:`~HTTPClientError` @@ -953,10 +1081,12 @@ class HTTPLocked(HTTPClientError): code: 423, title: Locked """ + ## Note: from WebDAV code = 423 title = 'Locked' - explanation = ('The resource is locked') + explanation = 'The resource is locked' + class HTTPFailedDependency(HTTPClientError): """ @@ -967,12 +1097,15 @@ class HTTPFailedDependency(HTTPClientError): code: 424, title: Failed Dependency """ + ## Note: from WebDAV code = 424 title = 'Failed Dependency' explanation = ( 'The method could not be performed because the requested ' - 'action dependended on another action and that action failed') + 'action dependended on another action and that action failed' + ) + class HTTPPreconditionRequired(HTTPClientError): """ @@ -991,10 +1124,11 @@ class HTTPPreconditionRequired(HTTPClientError): code: 428, title: Precondition Required """ + code = 428 title = 'Precondition Required' - explanation = ( - 'The origin server requires the request to be conditional.') + explanation = 'The origin server requires the request to be conditional.' + class HTTPTooManyRequests(HTTPClientError): """ @@ -1007,11 +1141,14 @@ class HTTPTooManyRequests(HTTPClientError): code: 429, title: Too Many Requests """ + code = 429 title = 'Too Many Requests' explanation = ( 'The action could not be performed because there were too ' - 'many requests by the client.') + 'many requests by the client.' + ) + class HTTPRequestHeaderFieldsTooLarge(HTTPClientError): """ @@ -1025,10 +1162,11 @@ class HTTPRequestHeaderFieldsTooLarge(HTTPClientError): code: 431, title: Request Header Fields Too Large """ + code = 431 title = 'Request Header Fields Too Large' - explanation = ( - 'The requests header fields were too large.') + explanation = 'The requests header fields were too large.' + ############################################################ ## 5xx Server Error @@ -1041,6 +1179,7 @@ class HTTPRequestHeaderFieldsTooLarge(HTTPClientError): # agents SHOULD display any included entity to the user. These response # codes are applicable to any request method. + class HTTPServerError(HTTPError): """ base class for the 500s, where the server is in-error @@ -1048,9 +1187,11 @@ class HTTPServerError(HTTPError): This is an error condition in which the server is presumed to be in-error. Unless specialized, this is a '500 Internal Server Error'. """ + code = 500 title = 'Internal Server Error' + class HTTPInternalServerError(HTTPServerError): """ subclass of :class:`~HTTPServerError` @@ -1060,9 +1201,12 @@ class HTTPInternalServerError(HTTPServerError): code: 500, title: Internal Server Error """ + explanation = ( 'The server has either erred or is incapable of performing ' - 'the requested operation.') + 'the requested operation.' + ) + class HTTPNotImplemented(HTTPServerError): """ @@ -1073,12 +1217,14 @@ class HTTPNotImplemented(HTTPServerError): code: 501, title: Not Implemented """ + # differences from webob.exc.HTTPNotAcceptable: # # - "template" attr left off (useless, bug in webob?) code = 501 title = 'Not Implemented' + class HTTPBadGateway(HTTPServerError): """ subclass of :class:`~HTTPServerError` @@ -1089,9 +1235,11 @@ class HTTPBadGateway(HTTPServerError): code: 502, title: Bad Gateway """ + code = 502 title = 'Bad Gateway' - explanation = ('Bad gateway.') + explanation = 'Bad gateway.' + class HTTPServiceUnavailable(HTTPServerError): """ @@ -1102,10 +1250,14 @@ class HTTPServiceUnavailable(HTTPServerError): code: 503, title: Service Unavailable """ + code = 503 title = 'Service Unavailable' - explanation = ('The server is currently unavailable. ' - 'Please try again at a later time.') + explanation = ( + 'The server is currently unavailable. ' + 'Please try again at a later time.' + ) + class HTTPGatewayTimeout(HTTPServerError): """ @@ -1118,9 +1270,11 @@ class HTTPGatewayTimeout(HTTPServerError): code: 504, title: Gateway Timeout """ + code = 504 title = 'Gateway Timeout' - explanation = ('The gateway has timed out.') + explanation = 'The gateway has timed out.' + class HTTPVersionNotSupported(HTTPServerError): """ @@ -1132,9 +1286,11 @@ class HTTPVersionNotSupported(HTTPServerError): code: 505, title: HTTP Version Not Supported """ + code = 505 title = 'HTTP Version Not Supported' - explanation = ('The HTTP version is not supported.') + explanation = 'The HTTP version is not supported.' + class HTTPInsufficientStorage(HTTPServerError): """ @@ -1145,9 +1301,11 @@ class HTTPInsufficientStorage(HTTPServerError): code: 507, title: Insufficient Storage """ + code = 507 title = 'Insufficient Storage' - explanation = ('There was not enough space to save the resource') + explanation = 'There was not enough space to save the resource' + def exception_response(status_code, **kw): """Creates an HTTP exception based on a status code. Example:: @@ -1159,22 +1317,24 @@ def exception_response(status_code, **kw): exc = status_map[status_code](**kw) return exc + def default_exceptionresponse_view(context, request): if not isinstance(context, Exception): # backwards compat for an exception response view registered via # config.set_notfound_view or config.set_forbidden_view # instead of as a proper exception view context = request.exception or context - return context # assumed to be an IResponse + return context # assumed to be an IResponse + status_map = {} code = None for name, value in list(globals().items()): if ( - isinstance(value, class_types) and - issubclass(value, HTTPException) and - value not in {HTTPClientError, HTTPServerError} and - not name.startswith('_') + isinstance(value, class_types) + and issubclass(value, HTTPException) + and value not in {HTTPClientError, HTTPServerError} + and not name.startswith('_') ): code = getattr(value, 'code', None) if code: diff --git a/src/pyramid/i18n.py b/src/pyramid/i18n.py index 1d11adfe3..9f5f898be 100644 --- a/src/pyramid/i18n.py +++ b/src/pyramid/i18n.py @@ -4,9 +4,9 @@ import os from translationstring import ( Translator, Pluralizer, - TranslationString, # API - TranslationStringFactory, # API - ) + TranslationString, # API + TranslationStringFactory, # API +) from pyramid.compat import PY2 from pyramid.decorator import reify @@ -15,7 +15,7 @@ from pyramid.interfaces import ( ILocalizer, ITranslationDirectories, ILocaleNegotiator, - ) +) from pyramid.threadlocal import get_current_registry @@ -24,6 +24,7 @@ TranslationStringFactory = TranslationStringFactory # PyFlakes DEFAULT_PLURAL = lambda n: int(n != 1) + class Localizer(object): """ An object providing translation and pluralizations related to @@ -31,6 +32,7 @@ class Localizer(object): :class:`pyramid.i18n.Localizer` object is created using the :func:`pyramid.i18n.get_localizer` function. """ + def __init__(self, locale_name, translations): self.locale_name = locale_name self.translations = translations @@ -112,8 +114,9 @@ class Localizer(object): """ if self.pluralizer is None: self.pluralizer = Pluralizer(self.translations) - return self.pluralizer(singular, plural, n, domain=domain, - mapping=mapping) + return self.pluralizer( + singular, plural, n, domain=domain, mapping=mapping + ) def default_locale_negotiator(request): @@ -142,6 +145,7 @@ def default_locale_negotiator(request): locale_name = request.cookies.get(name) return locale_name + def negotiate_locale_name(request): """ Negotiate and return the :term:`locale name` associated with the current request.""" @@ -149,8 +153,9 @@ def negotiate_locale_name(request): registry = request.registry except AttributeError: registry = get_current_registry() - negotiator = registry.queryUtility(ILocaleNegotiator, - default=default_locale_negotiator) + negotiator = registry.queryUtility( + ILocaleNegotiator, default=default_locale_negotiator + ) locale_name = negotiator(request) if locale_name is None: @@ -159,6 +164,7 @@ def negotiate_locale_name(request): return locale_name + def get_locale_name(request): """ .. deprecated:: 1.5 @@ -167,6 +173,7 @@ def get_locale_name(request): """ return request.locale_name + def make_localizer(current_locale_name, translation_directories): """ Create a :class:`pyramid.i18n.Localizer` object corresponding to the provided locale name from the @@ -199,16 +206,17 @@ def make_localizer(current_locale_name, translation_directories): if not os.path.isdir(os.path.realpath(messages_dir)): continue for mofile in os.listdir(messages_dir): - mopath = os.path.realpath(os.path.join(messages_dir, - mofile)) + mopath = os.path.realpath(os.path.join(messages_dir, mofile)) if mofile.endswith('.mo') and os.path.isfile(mopath): with open(mopath, 'rb') as mofp: domain = mofile[:-3] dtrans = Translations(mofp, domain) translations.add(dtrans) - return Localizer(locale_name=current_locale_name, - translations=translations) + return Localizer( + locale_name=current_locale_name, translations=translations + ) + def get_localizer(request): """ @@ -219,6 +227,7 @@ def get_localizer(request): """ return request.localizer + class Translations(gettext.GNUTranslations, object): """An extended translation catalog class (ripped off from Babel) """ @@ -272,8 +281,10 @@ class Translations(gettext.GNUTranslations, object): return cls(fileobj=fp, domain=domain) def __repr__(self): - return '<%s: "%s">' % (type(self).__name__, - self._info.get('project-id-version')) + return '<%s: "%s">' % ( + type(self).__name__, + self._info.get('project-id-version'), + ) def add(self, translations, merge=True): """Add the given translations to the catalog. @@ -331,13 +342,13 @@ class Translations(gettext.GNUTranslations, object): domain. """ return self._domains.get(domain, self).gettext(message) - + def ldgettext(self, domain, message): """Like ``lgettext()``, but look the message up in the specified domain. - """ + """ return self._domains.get(domain, self).lgettext(message) - + def dugettext(self, domain, message): """Like ``ugettext()``, but look the message up in the specified domain. @@ -346,29 +357,32 @@ class Translations(gettext.GNUTranslations, object): return self._domains.get(domain, self).ugettext(message) else: return self._domains.get(domain, self).gettext(message) - + def dngettext(self, domain, singular, plural, num): """Like ``ngettext()``, but look the message up in the specified domain. """ return self._domains.get(domain, self).ngettext(singular, plural, num) - + def ldngettext(self, domain, singular, plural, num): """Like ``lngettext()``, but look the message up in the specified domain. """ return self._domains.get(domain, self).lngettext(singular, plural, num) - + def dungettext(self, domain, singular, plural, num): """Like ``ungettext()`` but look the message up in the specified domain. """ if PY2: return self._domains.get(domain, self).ungettext( - singular, plural, num) + singular, plural, num + ) else: return self._domains.get(domain, self).ngettext( - singular, plural, num) + singular, plural, num + ) + class LocalizerRequestMixin(object): @reify @@ -384,8 +398,9 @@ class LocalizerRequestMixin(object): tdirs = registry.queryUtility(ITranslationDirectories, default=[]) localizer = make_localizer(current_locale_name, tdirs) - registry.registerUtility(localizer, ILocalizer, - name=current_locale_name) + registry.registerUtility( + localizer, ILocalizer, name=current_locale_name + ) return localizer @@ -393,5 +408,3 @@ class LocalizerRequestMixin(object): def locale_name(self): locale_name = negotiate_locale_name(self) return locale_name - - diff --git a/src/pyramid/interfaces.py b/src/pyramid/interfaces.py index 4df5593f8..26a9d7025 100644 --- a/src/pyramid/interfaces.py +++ b/src/pyramid/interfaces.py @@ -1,14 +1,12 @@ from zope.deprecation import deprecated -from zope.interface import ( - Attribute, - Interface, - ) +from zope.interface import Attribute, Interface from pyramid.compat import PY2 # public API interfaces + class IContextFound(Interface): """ An event type that is emitted after :app:`Pyramid` finds a :term:`context` object but before it calls any view code. See the @@ -21,32 +19,41 @@ class IContextFound(Interface): :app:`Pyramid` before 1.0, this event interface can also be imported as :class:`pyramid.interfaces.IAfterTraversal`. """ + request = Attribute('The request object') + IAfterTraversal = IContextFound + class IBeforeTraversal(Interface): """ An event type that is emitted after :app:`Pyramid` attempted to find a route but before it calls any traversal or view code. See the documentation attached to :class:`pyramid.events.Routefound` for more information. """ + request = Attribute('The request object') + class INewRequest(Interface): """ An event type that is emitted whenever :app:`Pyramid` begins to process a new request. See the documentation attached to :class:`pyramid.events.NewRequest` for more information.""" + request = Attribute('The request object') + class INewResponse(Interface): """ An event type that is emitted whenever any :app:`Pyramid` view returns a response. See the documentation attached to :class:`pyramid.events.NewResponse` for more information.""" + request = Attribute('The request object') response = Attribute('The response object') + class IApplicationCreated(Interface): """ Event issued when the :meth:`pyramid.config.Configurator.make_wsgi_app` method @@ -60,9 +67,12 @@ class IApplicationCreated(Interface): versions before 1.0, this interface can also be imported as :class:`pyramid.interfaces.IWSGIApplicationCreatedEvent`. """ + app = Attribute("Created application") -IWSGIApplicationCreatedEvent = IApplicationCreated # b /c + +IWSGIApplicationCreatedEvent = IApplicationCreated # b /c + class IResponse(Interface): """ Represents a WSGI response using the WebOb response interface. @@ -74,7 +84,8 @@ class IResponse(Interface): :mod:`pyramid.httpexceptions`.""" RequestClass = Attribute( - """ Alias for :class:`pyramid.request.Request` """) + """ Alias for :class:`pyramid.request.Request` """ + ) def __call__(environ, start_response): """ :term:`WSGI` call interface, should call the start_response @@ -82,21 +93,25 @@ class IResponse(Interface): accept_ranges = Attribute( """Gets and sets and deletes the Accept-Ranges header. For more - information on Accept-Ranges see RFC 2616, section 14.5""") + information on Accept-Ranges see RFC 2616, section 14.5""" + ) age = Attribute( """Gets and sets and deletes the Age header. Converts using int. - For more information on Age see RFC 2616, section 14.6.""") + For more information on Age see RFC 2616, section 14.6.""" + ) allow = Attribute( """Gets and sets and deletes the Allow header. Converts using - list. For more information on Allow see RFC 2616, Section 14.7.""") + list. For more information on Allow see RFC 2616, Section 14.7.""" + ) app_iter = Attribute( """Returns the app_iter of the response. If body was set, this will create an app_iter from that body - (a single-item list)""") + (a single-item list)""" + ) def app_iter_range(start, stop): """ Return a new app_iter built from the response app_iter that @@ -104,21 +119,24 @@ class IResponse(Interface): body = Attribute( """The body of the response, as a str. This will read in the entire - app_iter if necessary.""") + app_iter if necessary.""" + ) body_file = Attribute( """A file-like object that can be used to write to the body. If you - passed in a list app_iter, that app_iter will be modified by writes.""") + passed in a list app_iter, that app_iter will be modified by writes.""" + ) cache_control = Attribute( - """Get/set/modify the Cache-Control header (RFC 2616 section 14.9)""") + """Get/set/modify the Cache-Control header (RFC 2616 section 14.9)""" + ) cache_expires = Attribute( """ Get/set the Cache-Control and Expires headers. This sets the - response to expire in the number of seconds passed when set. """) + response to expire in the number of seconds passed when set. """ + ) - charset = Attribute( - """Get/set the charset (in the Content-Type)""") + charset = Attribute("""Get/set the charset (in the Content-Type)""") def conditional_response_app(environ, start_response): """ Like the normal __call__ interface, but checks conditional @@ -133,52 +151,62 @@ class IResponse(Interface): content_disposition = Attribute( """Gets and sets and deletes the Content-Disposition header. For more information on Content-Disposition see RFC 2616 section - 19.5.1.""") + 19.5.1.""" + ) content_encoding = Attribute( """Gets and sets and deletes the Content-Encoding header. For more - information about Content-Encoding see RFC 2616 section 14.11.""") + information about Content-Encoding see RFC 2616 section 14.11.""" + ) content_language = Attribute( """Gets and sets and deletes the Content-Language header. Converts using list. For more information about Content-Language see RFC 2616 - section 14.12.""") + section 14.12.""" + ) content_length = Attribute( """Gets and sets and deletes the Content-Length header. For more information on Content-Length see RFC 2616 section 14.17. - Converts using int. """) + Converts using int. """ + ) content_location = Attribute( """Gets and sets and deletes the Content-Location header. For more - information on Content-Location see RFC 2616 section 14.14.""") + information on Content-Location see RFC 2616 section 14.14.""" + ) content_md5 = Attribute( """Gets and sets and deletes the Content-MD5 header. For more - information on Content-MD5 see RFC 2616 section 14.14.""") + information on Content-MD5 see RFC 2616 section 14.14.""" + ) content_range = Attribute( """Gets and sets and deletes the Content-Range header. For more information on Content-Range see section 14.16. Converts using - ContentRange object.""") + ContentRange object.""" + ) content_type = Attribute( """Get/set the Content-Type header (or None), without the charset or any parameters. If you include parameters (or ; at all) when setting the content_type, any existing parameters will be deleted; - otherwise they will be preserved.""") + otherwise they will be preserved.""" + ) content_type_params = Attribute( """A dictionary of all the parameters in the content type. This is not a view, set to change, modifications of the dict would not - be applied otherwise.""") + be applied otherwise.""" + ) def copy(): """ Makes a copy of the response and returns the copy. """ date = Attribute( """Gets and sets and deletes the Date header. For more information on - Date see RFC 2616 section 14.18. Converts using HTTP date.""") + Date see RFC 2616 section 14.18. Converts using HTTP date.""" + ) def delete_cookie(name, path='/', domain=None): """ Delete a cookie from the client. Note that path and domain must @@ -191,31 +219,34 @@ class IResponse(Interface): environ = Attribute( """Get/set the request environ associated with this response, - if any.""") + if any.""" + ) etag = Attribute( """ Gets and sets and deletes the ETag header. For more information - on ETag see RFC 2616 section 14.19. Converts using Entity tag.""") + on ETag see RFC 2616 section 14.19. Converts using Entity tag.""" + ) expires = Attribute( """ Gets and sets and deletes the Expires header. For more information on Expires see RFC 2616 section 14.21. Converts using - HTTP date.""") + HTTP date.""" + ) - headerlist = Attribute( - """ The list of response headers. """) + headerlist = Attribute(""" The list of response headers. """) - headers = Attribute( - """ The headers in a dictionary-like object """) + headers = Attribute(""" The headers in a dictionary-like object """) last_modified = Attribute( """ Gets and sets and deletes the Last-Modified header. For more information on Last-Modified see RFC 2616 section 14.29. Converts - using HTTP date.""") + using HTTP date.""" + ) location = Attribute( """ Gets and sets and deletes the Location header. For more - information on Location see RFC 2616 section 14.30.""") + information on Location see RFC 2616 section 14.30.""" + ) def md5_etag(body=None, set_content_md5=False): """ Generate an etag for the response object using an MD5 hash of the @@ -230,34 +261,46 @@ class IResponse(Interface): pragma = Attribute( """ Gets and sets and deletes the Pragma header. For more information - on Pragma see RFC 2616 section 14.32. """) + on Pragma see RFC 2616 section 14.32. """ + ) request = Attribute( - """ Return the request associated with this response if any. """) + """ Return the request associated with this response if any. """ + ) retry_after = Attribute( """ Gets and sets and deletes the Retry-After header. For more information on Retry-After see RFC 2616 section 14.37. Converts - using HTTP date or delta seconds.""") + using HTTP date or delta seconds.""" + ) server = Attribute( """ Gets and sets and deletes the Server header. For more information - on Server see RFC216 section 14.38. """) + on Server see RFC216 section 14.38. """ + ) - def set_cookie(name, value='', max_age=None, path='/', domain=None, - secure=False, httponly=False, comment=None, expires=None, - overwrite=False): + def set_cookie( + name, + value='', + max_age=None, + path='/', + domain=None, + secure=False, + httponly=False, + comment=None, + expires=None, + overwrite=False, + ): """ Set (add) a cookie for the response """ - status = Attribute( - """ The status string. """) + status = Attribute(""" The status string. """) - status_int = Attribute( - """ The status as an integer """) + status_int = Attribute(""" The status as an integer """) unicode_body = Attribute( """ Get/set the unicode value of the body (using the charset of - the Content-Type)""") + the Content-Type)""" + ) def unset_cookie(name, strict=True): """ Unset a cookie with the given name (remove it from the @@ -265,16 +308,20 @@ class IResponse(Interface): vary = Attribute( """Gets and sets and deletes the Vary header. For more information - on Vary see section 14.44. Converts using list.""") + on Vary see section 14.44. Converts using list.""" + ) www_authenticate = Attribute( """ Gets and sets and deletes the WWW-Authenticate header. For more information on WWW-Authenticate see RFC 2616 section 14.47. Converts - using 'parse_auth' and 'serialize_auth'. """) + using 'parse_auth' and 'serialize_auth'. """ + ) + -class IException(Interface): # not an API +class IException(Interface): # not an API """ An interface representing a generic exception """ + class IExceptionResponse(IException, IResponse): """ An interface representing a WSGI response which is also an exception object. Register an exception view using this interface as a ``context`` @@ -283,9 +330,11 @@ class IExceptionResponse(IException, IResponse): :class:`pyramid.response.Response`, including :class:`pyramid.httpexceptions.HTTPNotFound` and :class:`pyramid.httpexceptions.HTTPForbidden`).""" + def prepare(environ): """ Prepares the response for being called as a WSGI application """ + class IDict(Interface): # Documentation-only interface @@ -354,6 +403,7 @@ class IDict(Interface): def clear(): """ Clear all values from the dictionary """ + class IBeforeRender(IDict): """ Subscribers to this event may introspect and modify the set of @@ -373,26 +423,37 @@ class IBeforeRender(IDict): See also :ref:`beforerender_event`. """ - rendering_val = Attribute('The value returned by a view or passed to a ' - '``render`` method for this rendering. ' - 'This feature is new in Pyramid 1.2.') + + rendering_val = Attribute( + 'The value returned by a view or passed to a ' + '``render`` method for this rendering. ' + 'This feature is new in Pyramid 1.2.' + ) + class IRendererInfo(Interface): """ An object implementing this interface is passed to every :term:`renderer factory` constructor as its only argument (conventionally named ``info``)""" + name = Attribute('The value passed by the user as the renderer name') - package = Attribute('The "current package" when the renderer ' - 'configuration statement was found') + package = Attribute( + 'The "current package" when the renderer ' + 'configuration statement was found' + ) type = Attribute('The renderer type name') - registry = Attribute('The "current" application registry when the ' - 'renderer was created') - settings = Attribute('The deployment settings dictionary related ' - 'to the current application') + registry = Attribute( + 'The "current" application registry when the ' 'renderer was created' + ) + settings = Attribute( + 'The deployment settings dictionary related ' + 'to the current application' + ) def clone(): """ Return a shallow copy that does not share any mutable state.""" + class IRendererFactory(Interface): def __call__(info): """ Return an object that implements @@ -400,6 +461,7 @@ class IRendererFactory(Interface): object that implements :class:`pyramid.interfaces.IRendererInfo`. """ + class IRenderer(Interface): def __call__(value, system): """ Call the renderer with the result of the @@ -413,6 +475,7 @@ class IRenderer(Interface): view), and ``request`` (the request object passed to the view).""" + class ITemplateRenderer(IRenderer): def implementation(): """ Return the object that the underlying templating system @@ -420,12 +483,14 @@ class ITemplateRenderer(IRenderer): accepts arbitrary keyword arguments and returns a string or unicode object """ + deprecated( 'ITemplateRenderer', 'As of Pyramid 1.5 the, "pyramid.interfaces.ITemplateRenderer" interface ' 'is scheduled to be removed. It was used by the Mako and Chameleon ' - 'renderers which have been split into their own packages.' - ) + 'renderers which have been split into their own packages.', +) + class IViewMapper(Interface): def __call__(self, object): @@ -435,6 +500,7 @@ class IViewMapper(Interface): object. An IViewMapper is returned by :class:`pyramid.interfaces.IViewMapperFactory`.""" + class IViewMapperFactory(Interface): def __call__(self, **kw): """ @@ -448,6 +514,7 @@ class IViewMapperFactory(Interface): invocation signatures and response values. """ + class IAuthenticationPolicy(Interface): """ An object representing a Pyramid authentication policy. """ @@ -500,8 +567,10 @@ class IAuthenticationPolicy(Interface): """ + class IAuthorizationPolicy(Interface): """ An object representing a Pyramid authorization policy. """ + def permits(context, principals, permission): """ Return an instance of :class:`pyramid.security.Allowed` if any of the ``principals`` is allowed the ``permission`` in the current @@ -518,7 +587,8 @@ class IAuthorizationPolicy(Interface): ``pyramid.security.principals_allowed_by_permission`` API is used.""" -class IMultiDict(IDict): # docs-only interface + +class IMultiDict(IDict): # docs-only interface """ An ordered dictionary that can have multiple values for each key. A multidict adds the methods ``getall``, ``getone``, ``mixed``, ``extend``, @@ -556,36 +626,46 @@ class IMultiDict(IDict): # docs-only interface dictionary. This is similar to the kind of dictionary often used to represent the variables in a web request. """ + # internal interfaces + class IRequest(Interface): """ Request type interface attached to all request objects """ + class ITweens(Interface): """ Marker interface for utility registration representing the ordered set of a configuration's tween factories""" + class IRequestHandler(Interface): """ """ + def __call__(self, request): """ Must return a tuple of IReqest, IResponse or raise an exception. The ``request`` argument will be an instance of an object that provides IRequest.""" -IRequest.combined = IRequest # for exception view lookups + +IRequest.combined = IRequest # for exception view lookups + class IRequestExtensions(Interface): """ Marker interface for storing request extensions (properties and methods) which will be added to the request object.""" + descriptors = Attribute( - """A list of descriptors that will be added to each request.""") - methods = Attribute( - """A list of methods to be added to each request.""") + """A list of descriptors that will be added to each request.""" + ) + methods = Attribute("""A list of methods to be added to each request.""") + class IRouteRequest(Interface): """ *internal only* interface used as in a utility lookup to find route-specific interfaces. Not an API.""" + class IAcceptOrder(Interface): """ Marker interface for a list of accept headers with the most important @@ -593,8 +673,10 @@ class IAcceptOrder(Interface): """ + class IStaticURLInfo(Interface): """ A policy for generating URLs to static assets """ + def add(config, name, spec, **extra): """ Add a new static info registration """ @@ -604,15 +686,19 @@ class IStaticURLInfo(Interface): def add_cache_buster(config, spec, cache_buster): """ Add a new cache buster to a particular set of assets """ + class IResponseFactory(Interface): """ A utility which generates a response """ + def __call__(request): """ Return a response object implementing IResponse, e.g. :class:`pyramid.response.Response`). It should handle the case when ``request`` is ``None``.""" + class IRequestFactory(Interface): """ A utility which generates a request """ + def __call__(environ): """ Return an instance of ``pyramid.request.Request``""" @@ -620,18 +706,23 @@ class IRequestFactory(Interface): """ Return an empty request object (see :meth:`pyramid.request.Request.blank`)""" + class IViewClassifier(Interface): """ *Internal only* marker interface for views.""" + class IExceptionViewClassifier(Interface): """ *Internal only* marker interface for exception views.""" + class IView(Interface): def __call__(context, request): """ Must return an object that implements IResponse. """ + class ISecuredView(IView): """ *Internal only* interface. Not an API. """ + def __call_permissive__(context, request): """ Guaranteed-permissive version of __call__ """ @@ -639,21 +730,26 @@ class ISecuredView(IView): """ Return True if view execution will be permitted using the context and request, False otherwise""" + class IMultiView(ISecuredView): """ *internal only*. A multiview is a secured view that is a collection of other views. Each of the views is associated with zero or more predicates. Not an API.""" + def add(view, predicates, order, accept=None, phash=None): """ Add a view to the multiview. """ + class IRootFactory(Interface): def __call__(request): """ Return a root object based on the request """ + class IDefaultRootFactory(Interface): def __call__(request): """ Return the *default* root object for an application """ + class ITraverser(Interface): def __call__(request): """ Return a dictionary with (at least) the keys ``root``, @@ -678,21 +774,26 @@ class ITraverser(Interface): as attributes of the ``request`` object by the :term:`router`. """ -ITraverserFactory = ITraverser # b / c for 1.0 code + +ITraverserFactory = ITraverser # b / c for 1.0 code + class IViewPermission(Interface): def __call__(context, request): """ Return True if the permission allows, return False if it denies. """ + class IRouter(Interface): """ WSGI application which routes requests to 'view' code based on a view registry. """ + registry = Attribute( - """Component architecture registry local to this application.""") + """Component architecture registry local to this application.""" + ) def request_context(environ): """ @@ -734,6 +835,7 @@ class IRouter(Interface): """ + class IExecutionPolicy(Interface): def __call__(environ, router): """ @@ -762,22 +864,28 @@ class IExecutionPolicy(Interface): return request.invoke_exception_view(reraise=True) """ + class ISettings(IDict): """ Runtime settings utility for pyramid; represents the deployment settings for the application. Implements a mapping interface.""" + # this interface, even if it becomes unused within Pyramid, is # imported by other packages (such as traversalwrapper) class ILocation(Interface): """Objects that have a structural location""" + __parent__ = Attribute("The parent in the location hierarchy") __name__ = Attribute("The name within the parent") + class IDebugLogger(Interface): """ Interface representing a PEP 282 logger """ -ILogger = IDebugLogger # b/c + +ILogger = IDebugLogger # b/c + class IRoutePregenerator(Interface): def __call__(request, elements, kw): @@ -804,21 +912,27 @@ class IRoutePregenerator(Interface): """ + class IRoute(Interface): """ Interface representing the type of object returned from ``IRoutesMapper.get_route``""" + name = Attribute('The route name') pattern = Attribute('The route pattern') factory = Attribute( 'The :term:`root factory` used by the :app:`Pyramid` router ' - 'when this route matches (or ``None``)') + 'when this route matches (or ``None``)' + ) predicates = Attribute( 'A sequence of :term:`route predicate` objects used to ' 'determine if a request matches this route or not after ' - 'basic pattern matching has been completed.') - pregenerator = Attribute('This attribute should either be ``None`` or ' - 'a callable object implementing the ' - '``IRoutePregenerator`` interface') + 'basic pattern matching has been completed.' + ) + pregenerator = Attribute( + 'This attribute should either be ``None`` or ' + 'a callable object implementing the ' + '``IRoutePregenerator`` interface' + ) def match(path): """ @@ -831,14 +945,17 @@ class IRoute(Interface): If the ``path`` passed to this function cannot be matched by the ``pattern`` of this route, return ``None``. """ + def generate(kw): """ Generate a URL based on filling in the dynamic segment markers in the pattern using the ``kw`` dictionary provided. """ + class IRoutesMapper(Interface): """ Interface representing a Routes ``Mapper`` object """ + def get_routes(): """ Return a sequence of Route objects registered in the mapper. Static routes will not be returned in this sequence.""" @@ -850,8 +967,14 @@ class IRoutesMapper(Interface): """ Returns an ``IRoute`` object if a route with the name ``name`` was registered, otherwise return ``None``.""" - def connect(name, pattern, factory=None, predicates=(), pregenerator=None, - static=True): + def connect( + name, + pattern, + factory=None, + predicates=(), + pregenerator=None, + static=True, + ): """ Add a new route. """ def generate(name, kw): @@ -865,23 +988,26 @@ class IRoutesMapper(Interface): ``match`` key will be the matchdict or ``None`` if no route matched. Static routes will not be considered for matching. """ + class IResourceURL(Interface): virtual_path = Attribute( 'The virtual url path of the resource as a string.' - ) + ) physical_path = Attribute( 'The physical url path of the resource as a string.' - ) + ) virtual_path_tuple = Attribute( 'The virtual url path of the resource as a tuple. (New in 1.5)' - ) + ) physical_path_tuple = Attribute( 'The physical url path of the resource as a tuple. (New in 1.5)' - ) + ) + class IPEP302Loader(Interface): """ See http://www.python.org/dev/peps/pep-0302/#id30. """ + def get_data(path): """ Retrieve data for and arbitrary "files" from storage backend. @@ -924,43 +1050,54 @@ class IPEP302Loader(Interface): class IPackageOverrides(IPEP302Loader): """ Utility for pkg_resources overrides """ + # VH_ROOT_KEY is an interface; its imported from other packages (e.g. # traversalwrapper) VH_ROOT_KEY = 'HTTP_X_VHM_ROOT' + class ILocalizer(Interface): """ Localizer for a specific language """ + class ILocaleNegotiator(Interface): def __call__(request): """ Return a locale name """ + class ITranslationDirectories(Interface): """ A list object representing all known translation directories for an application""" + class IDefaultPermission(Interface): """ A string object representing the default permission to be used for all view configurations which do not explicitly declare their own.""" + class IDefaultCSRFOptions(Interface): """ An object representing the default CSRF settings to be used for all view configurations which do not explicitly declare their own.""" + require_csrf = Attribute( 'Boolean attribute. If ``True``, then CSRF checks will be enabled by ' - 'default for the view unless overridden.') + 'default for the view unless overridden.' + ) token = Attribute('The key to be matched in the body of the request.') header = Attribute('The header to be matched with the CSRF token.') safe_methods = Attribute('A set of safe methods that skip CSRF checks.') callback = Attribute('A callback to disable CSRF checks per-request.') + class ISessionFactory(Interface): """ An interface representing a factory which accepts a request object and returns an ISession object """ + def __call__(request): """ Return an ISession object """ + class ISession(IDict): """ An interface representing a session (a web session object, usually accessed via ``request.session``. @@ -1158,16 +1295,21 @@ class IIntrospectable(Interface): title = Attribute('Text title describing this introspectable') type_name = Attribute('Text type name describing this introspectable') - order = Attribute('integer order in which registered with introspector ' - '(managed by introspector, usually)') + order = Attribute( + 'integer order in which registered with introspector ' + '(managed by introspector, usually)' + ) category_name = Attribute('introspection category name') - discriminator = Attribute('introspectable discriminator (within category) ' - '(must be hashable)') + discriminator = Attribute( + 'introspectable discriminator (within category) ' '(must be hashable)' + ) discriminator_hash = Attribute('an integer hash of the discriminator') - action_info = Attribute('An IActionInfo object representing the caller ' - 'that invoked the creation of this introspectable ' - '(usually a sentinel until updated during ' - 'self.register)') + action_info = Attribute( + 'An IActionInfo object representing the caller ' + 'that invoked the creation of this introspectable ' + '(usually a sentinel until updated during ' + 'self.register)' + ) def relate(category_name, discriminator): """ Indicate an intent to relate this IIntrospectable with another @@ -1209,19 +1351,22 @@ class IIntrospectable(Interface): return hash((self.category_name,) + (self.discriminator,)) """ + class IActionInfo(Interface): """ Class which provides code introspection capability associated with an action. The ParserInfo class used by ZCML implements the same interface.""" - file = Attribute( - 'Filename of action-invoking code as a string') + + file = Attribute('Filename of action-invoking code as a string') line = Attribute( 'Starting line number in file (as an integer) of action-invoking code.' - 'This will be ``None`` if the value could not be determined.') + 'This will be ``None`` if the value could not be determined.' + ) def __str__(): """ Return a representation of the action information (including source code from file, if possible) """ + class IAssetDescriptor(Interface): """ Describes an :term:`asset`. @@ -1260,19 +1405,24 @@ class IAssetDescriptor(Interface): Returns True if asset exists, otherwise returns False. """ + class IJSONAdapter(Interface): """ Marker interface for objects that can convert an arbitrary object into a JSON-serializable primitive. """ + class IPredicateList(Interface): """ Interface representing a predicate list """ + class IViewDeriver(Interface): - options = Attribute('A list of supported options to be passed to ' - ':meth:`pyramid.config.Configurator.add_view`. ' - 'This attribute is optional.') + options = Attribute( + 'A list of supported options to be passed to ' + ':meth:`pyramid.config.Configurator.add_view`. ' + 'This attribute is optional.' + ) def __call__(view, info): """ @@ -1285,24 +1435,35 @@ class IViewDeriver(Interface): """ + class IViewDeriverInfo(Interface): """ An object implementing this interface is passed to every :term:`view deriver` during configuration.""" - registry = Attribute('The "current" application registry where the ' - 'view was created') - package = Attribute('The "current package" where the view ' - 'configuration statement was found') - settings = Attribute('The deployment settings dictionary related ' - 'to the current application') - options = Attribute('The view options passed to the view, including any ' - 'default values that were not overriden') + + registry = Attribute( + 'The "current" application registry where the ' 'view was created' + ) + package = Attribute( + 'The "current package" where the view ' + 'configuration statement was found' + ) + settings = Attribute( + 'The deployment settings dictionary related ' + 'to the current application' + ) + options = Attribute( + 'The view options passed to the view, including any ' + 'default values that were not overriden' + ) predicates = Attribute('The list of predicates active on the view') original_view = Attribute('The original view object being wrapped') exception_only = Attribute('The view will only be invoked for exceptions') + class IViewDerivers(Interface): """ Interface for view derivers list """ + class ICacheBuster(Interface): """ A cache buster modifies the URL generation machinery for @@ -1310,6 +1471,7 @@ class ICacheBuster(Interface): .. versionadded:: 1.6 """ + def __call__(request, subpath, kw): """ Modifies a subpath and/or keyword arguments from which a static asset @@ -1344,6 +1506,7 @@ class ICacheBuster(Interface): ``config.override_asset('myapp:static/foo.png', 'themepkg:bar.png')``. """ + # configuration phases: a lower phase number means the actions associated # with this phase will be executed earlier than those with later phase # numbers. The default phase number is 0, FTR. diff --git a/src/pyramid/location.py b/src/pyramid/location.py index 4124895a5..b8542d1b9 100644 --- a/src/pyramid/location.py +++ b/src/pyramid/location.py @@ -12,6 +12,7 @@ # ############################################################################## + def inside(resource1, resource2): """Is ``resource1`` 'inside' ``resource2``? Return ``True`` if so, else ``False``. @@ -28,6 +29,7 @@ def inside(resource1, resource2): return False + def lineage(resource): """ Return a generator representing the :term:`lineage` of the @@ -63,4 +65,3 @@ def lineage(resource): resource = resource.__parent__ except AttributeError: resource = None - diff --git a/src/pyramid/paster.py b/src/pyramid/paster.py index f7544f0c5..eda479476 100644 --- a/src/pyramid/paster.py +++ b/src/pyramid/paster.py @@ -1,6 +1,7 @@ from pyramid.scripting import prepare from pyramid.scripts.common import get_config_loader + def setup_logging(config_uri, global_conf=None): """ Set up Python logging with the filename specified via ``config_uri`` @@ -11,6 +12,7 @@ def setup_logging(config_uri, global_conf=None): loader = get_config_loader(config_uri) loader.setup_logging(global_conf) + def get_app(config_uri, name=None, options=None): """ Return the WSGI application named ``name`` in the PasteDeploy config file specified by ``config_uri``. @@ -27,6 +29,7 @@ def get_app(config_uri, name=None, options=None): loader = get_config_loader(config_uri) return loader.get_wsgi_app(name, options) + def get_appsettings(config_uri, name=None, options=None): """ Return a dictionary representing the key/value pairs in an ``app`` section within the file represented by ``config_uri``. @@ -43,6 +46,7 @@ def get_appsettings(config_uri, name=None, options=None): loader = get_config_loader(config_uri) return loader.get_wsgi_app_settings(name, options) + def bootstrap(config_uri, request=None, options=None): """ Load a WSGI application from the PasteDeploy config file specified by ``config_uri``. The environment will be configured as if it is @@ -108,4 +112,3 @@ def bootstrap(config_uri, request=None, options=None): env = prepare(request) env['app'] = app return env - diff --git a/src/pyramid/path.py b/src/pyramid/path.py index 3fac7e940..c70be99db 100644 --- a/src/pyramid/path.py +++ b/src/pyramid/path.py @@ -9,9 +9,13 @@ from pyramid.interfaces import IAssetDescriptor from pyramid.compat import string_types -ignore_types = [ imp.C_EXTENSION, imp.C_BUILTIN ] -init_names = [ '__init__%s' % x[0] for x in imp.get_suffixes() if - x[0] and x[2] not in ignore_types ] +ignore_types = [imp.C_EXTENSION, imp.C_BUILTIN] +init_names = [ + '__init__%s' % x[0] + for x in imp.get_suffixes() + if x[0] and x[2] not in ignore_types +] + def caller_path(path, level=2): if not os.path.isabs(path): @@ -20,12 +24,14 @@ def caller_path(path, level=2): path = os.path.join(prefix, path) return path + def caller_module(level=2, sys=sys): module_globals = sys._getframe(level).f_globals module_name = module_globals.get('__name__') or '__main__' module = sys.modules[module_name] return module + def package_name(pkg_or_module): """ If this function is passed a module, return the dotted Python package name of the package in which the module lives. If this @@ -45,23 +51,26 @@ def package_name(pkg_or_module): return pkg_name return pkg_name.rsplit('.', 1)[0] + def package_of(pkg_or_module): """ Return the package of a module or return the package itself """ pkg_name = package_name(pkg_or_module) __import__(pkg_name) return sys.modules[pkg_name] + def caller_package(level=2, caller_module=caller_module): # caller_module in arglist for tests module = caller_module(level + 1) f = getattr(module, '__file__', '') - if (('__init__.py' in f) or ('__init__$py' in f)): # empty at >>> + if ('__init__.py' in f) or ('__init__$py' in f): # empty at >>> # Module is a package return module # Go up one level to get package package_name = module.__name__.rsplit('.', 1)[0] return sys.modules[package_name] + def package_path(package): # computing the abspath is actually kinda expensive so we memoize # the result @@ -78,12 +87,15 @@ def package_path(package): pass return prefix + class _CALLER_PACKAGE(object): - def __repr__(self): # pragma: no cover (for docs) + def __repr__(self): # pragma: no cover (for docs) return 'pyramid.path.CALLER_PACKAGE' + CALLER_PACKAGE = _CALLER_PACKAGE() + class Resolver(object): def __init__(self, package=CALLER_PACKAGE): if package in (None, CALLER_PACKAGE): @@ -95,7 +107,7 @@ class Resolver(object): except ImportError: raise ValueError( 'The dotted name %r cannot be imported' % (package,) - ) + ) package = sys.modules[package] self.package = package_of(package) @@ -164,6 +176,7 @@ class AssetResolver(Resolver): to the :meth:`~pyramid.path.AssetResolver.resolve` method, the resulting absolute asset spec would be ``xml.minidom:template.pt``. """ + def resolve(self, spec): """ Resolve the asset spec named as ``spec`` to an object that has the @@ -208,6 +221,7 @@ class AssetResolver(Resolver): ) return PkgResourcesAssetDescriptor(package_name, path) + class DottedNameResolver(Resolver): """ A class used to resolve a :term:`dotted Python name` to a package or module object. @@ -258,6 +272,7 @@ class DottedNameResolver(Resolver): :meth:`~pyramid.path.DottedNameResolver.resolve` method, the resulting import would be for ``xml.minidom``. """ + def resolve(self, dotted): """ This method resolves a dotted name reference to a global Python @@ -332,7 +347,7 @@ class DottedNameResolver(Resolver): if not package: raise ValueError( 'relative name %r irresolveable without package' % (value,) - ) + ) if value in ['.', ':']: value = package.__name__ else: @@ -348,7 +363,7 @@ class DottedNameResolver(Resolver): def _zope_dottedname_style(self, value, package): """ package.module.attr style """ - module = getattr(package, '__name__', None) # package may be None + module = getattr(package, '__name__', None) # package may be None if not module: module = None if value == '.': @@ -364,7 +379,7 @@ class DottedNameResolver(Resolver): raise ValueError( 'relative name %r irresolveable without ' 'package' % (value,) - ) + ) module = module.split('.') name.pop(0) while not name[0]: @@ -380,10 +395,11 @@ class DottedNameResolver(Resolver): found = getattr(found, n) except AttributeError: __import__(used) - found = getattr(found, n) # pragma: no cover + found = getattr(found, n) # pragma: no cover return found + @implementer(IAssetDescriptor) class PkgResourcesAssetDescriptor(object): pkg_resources = pkg_resources @@ -397,7 +413,8 @@ class PkgResourcesAssetDescriptor(object): def abspath(self): return os.path.abspath( - self.pkg_resources.resource_filename(self.pkg_name, self.path)) + self.pkg_resources.resource_filename(self.pkg_name, self.path) + ) def stream(self): return self.pkg_resources.resource_stream(self.pkg_name, self.path) @@ -411,9 +428,9 @@ class PkgResourcesAssetDescriptor(object): def exists(self): return self.pkg_resources.resource_exists(self.pkg_name, self.path) + @implementer(IAssetDescriptor) class FSAssetDescriptor(object): - def __init__(self, path): self.path = os.path.abspath(path) diff --git a/src/pyramid/predicates.py b/src/pyramid/predicates.py index 97edae8a0..b95dfd9be 100644 --- a/src/pyramid/predicates.py +++ b/src/pyramid/predicates.py @@ -8,17 +8,15 @@ from pyramid.csrf import check_csrf_token from pyramid.traversal import ( find_interface, traversal_path, - resource_path_tuple - ) + resource_path_tuple, +) from pyramid.urldispatch import _compile_route -from pyramid.util import ( - as_sorted_tuple, - object_description, -) +from pyramid.util import as_sorted_tuple, object_description _marker = object() + class XHRPredicate(object): def __init__(self, val, config): self.val = bool(val) @@ -31,6 +29,7 @@ class XHRPredicate(object): def __call__(self, context, request): return bool(request.is_xhr) is self.val + class RequestMethodPredicate(object): def __init__(self, val, config): request_method = as_sorted_tuple(val) @@ -47,6 +46,7 @@ class RequestMethodPredicate(object): def __call__(self, context, request): return request.method in self.val + class PathInfoPredicate(object): def __init__(self, val, config): self.orig = val @@ -63,7 +63,8 @@ class PathInfoPredicate(object): def __call__(self, context, request): return self.val.match(request.upath_info) is not None - + + class RequestParamPredicate(object): def __init__(self, val, config): val = as_sorted_tuple(val) @@ -85,7 +86,7 @@ class RequestParamPredicate(object): def text(self): return 'request_param %s' % ','.join( - ['%s=%s' % (x,y) if y else x for x, y in self.reqs] + ['%s=%s' % (x, y) if y else x for x, y in self.reqs] ) phash = text @@ -99,6 +100,7 @@ class RequestParamPredicate(object): return False return True + class HeaderPredicate(object): def __init__(self, val, config): name = val @@ -129,6 +131,7 @@ class HeaderPredicate(object): return False return self.val.match(val) is not None + class AcceptPredicate(object): _is_using_deprecated_ranges = False @@ -151,6 +154,7 @@ class AcceptPredicate(object): return self.values[0] in request.accept return bool(request.accept.acceptable_offers(self.values)) + class ContainmentPredicate(object): def __init__(self, val, config): self.val = config.maybe_dotted(val) @@ -163,7 +167,8 @@ class ContainmentPredicate(object): def __call__(self, context, request): ctx = getattr(request, 'context', context) return find_interface(ctx, self.val) is not None - + + class RequestTypePredicate(object): def __init__(self, val, config): self.val = val @@ -175,18 +180,19 @@ class RequestTypePredicate(object): def __call__(self, context, request): return self.val.providedBy(request) - + + class MatchParamPredicate(object): def __init__(self, val, config): val = as_sorted_tuple(val) self.val = val - reqs = [ p.split('=', 1) for p in val ] - self.reqs = [ (x.strip(), y.strip()) for x, y in reqs ] + reqs = [p.split('=', 1) for p in val] + self.reqs = [(x.strip(), y.strip()) for x, y in reqs] def text(self): return 'match_param %s' % ','.join( - ['%s=%s' % (x,y) for x, y in self.reqs] - ) + ['%s=%s' % (x, y) for x, y in self.reqs] + ) phash = text @@ -198,7 +204,8 @@ class MatchParamPredicate(object): if request.matchdict.get(k) != v: return False return True - + + class CustomPredicate(object): def __init__(self, func, config): self.func = func @@ -207,8 +214,8 @@ class CustomPredicate(object): return getattr( self.func, '__text__', - 'custom predicate: %s' % object_description(self.func) - ) + 'custom predicate: %s' % object_description(self.func), + ) def phash(self): # using hash() here rather than id() is intentional: we @@ -221,8 +228,8 @@ class CustomPredicate(object): def __call__(self, context, request): return self.func(context, request) - - + + class TraversePredicate(object): # Can only be used as a *route* "predicate"; it adds 'traverse' to the # matchdict if it's specified in the routing args. This causes the @@ -231,7 +238,7 @@ class TraversePredicate(object): def __init__(self, val, config): _, self.tgenerate = _compile_route(val) self.val = val - + def text(self): return 'traverse matchdict pseudo-predicate' @@ -252,10 +259,11 @@ class TraversePredicate(object): # return True. return True + class CheckCSRFTokenPredicate(object): - check_csrf_token = staticmethod(check_csrf_token) # testing - + check_csrf_token = staticmethod(check_csrf_token) # testing + def __init__(self, val, config): self.val = val @@ -272,6 +280,7 @@ class CheckCSRFTokenPredicate(object): return self.check_csrf_token(request, val, raises=False) return True + class PhysicalPathPredicate(object): def __init__(self, val, config): if is_nonstr_iter(val): @@ -290,6 +299,7 @@ class PhysicalPathPredicate(object): return resource_path_tuple(context) == self.val return False + class EffectivePrincipalsPredicate(object): def __init__(self, val, config): if is_nonstr_iter(val): @@ -310,6 +320,7 @@ class EffectivePrincipalsPredicate(object): return True return False + class Notted(object): def __init__(self, predicate): self.predicate = predicate diff --git a/src/pyramid/registry.py b/src/pyramid/registry.py index a741c495e..c24125830 100644 --- a/src/pyramid/registry.py +++ b/src/pyramid/registry.py @@ -7,19 +7,13 @@ from zope.interface.registry import Components from pyramid.compat import text_ from pyramid.decorator import reify -from pyramid.interfaces import ( - IIntrospector, - IIntrospectable, - ISettings, -) +from pyramid.interfaces import IIntrospector, IIntrospectable, ISettings -from pyramid.path import ( - CALLER_PACKAGE, - caller_package, -) +from pyramid.path import CALLER_PACKAGE, caller_package empty = text_('') + class Registry(Components, dict): """ A registry object is an :term:`application registry`. @@ -82,13 +76,19 @@ class Registry(Components, dict): self.has_listeners = True return result - def registerSelfAdapter(self, required=None, provided=None, name=empty, - info=empty, event=True): + def registerSelfAdapter( + self, required=None, provided=None, name=empty, info=empty, event=True + ): # registerAdapter analogue which always returns the object itself # when required is matched - return self.registerAdapter(lambda x: x, required=required, - provided=provided, name=name, - info=info, event=event) + return self.registerAdapter( + lambda x: x, + required=required, + provided=provided, + name=name, + info=info, + event=event, + ) def queryAdapterOrSelf(self, object, interface, default=None): # queryAdapter analogue which returns the object if it implements @@ -106,7 +106,7 @@ class Registry(Components, dict): def notify(self, *events): if self.has_listeners: # iterating over subscribers assures they get executed - [ _ for _ in self.subscribers(events, None) ] + [_ for _ in self.subscribers(events, None)] # backwards compatibility for code that wants to look up a settings # object via ``registry.getUtility(ISettings)`` @@ -119,6 +119,7 @@ class Registry(Components, dict): settings = property(_get_settings, _set_settings) + @implementer(IIntrospector) class Introspector(object): def __init__(self): @@ -147,16 +148,19 @@ class Introspector(object): values = category.values() values = sorted(set(values), key=sort_key) return [ - {'introspectable': intr, - 'related': self.related(intr)} + {'introspectable': intr, 'related': self.related(intr)} for intr in values ] def categorized(self, sort_key=None): L = [] for category_name in self.categories(): - L.append((category_name, self.get_category(category_name, - sort_key=sort_key))) + L.append( + ( + category_name, + self.get_category(category_name, sort_key=sort_key), + ) + ) return L def categories(self): @@ -186,7 +190,7 @@ class Introspector(object): def relate(self, *pairs): introspectables = self._get_intrs_by_pairs(pairs) - relatable = ((x,y) for x in introspectables for y in introspectables) + relatable = ((x, y) for x in introspectables for y in introspectables) for x, y in relatable: L = self._refs.setdefault(x, []) if x is not y and y not in L: @@ -194,7 +198,7 @@ class Introspector(object): def unrelate(self, *pairs): introspectables = self._get_intrs_by_pairs(pairs) - relatable = ((x,y) for x in introspectables for y in introspectables) + relatable = ((x, y) for x in introspectables for y in introspectables) for x, y in relatable: L = self._refs.get(x, []) if y in L: @@ -207,11 +211,12 @@ class Introspector(object): raise KeyError((category_name, discriminator)) return self._refs.get(intr, []) + @implementer(IIntrospectable) class Introspectable(dict): - order = 0 # mutated by introspector.add - action_info = None # mutated by self.register + order = 0 # mutated by introspector.add + action_info = None # mutated by self.register def __init__(self, category_name, discriminator, title, type_name): self.category_name = category_name @@ -240,14 +245,16 @@ class Introspectable(dict): def __repr__(self): self._assert_resolved() - return '<%s category %r, discriminator %r>' % (self.__class__.__name__, - self.category_name, - self.discriminator) + return '<%s category %r, discriminator %r>' % ( + self.__class__.__name__, + self.category_name, + self.discriminator, + ) def __nonzero__(self): return True - __bool__ = __nonzero__ # py3 + __bool__ = __nonzero__ # py3 def register(self, introspector, action_info): self.discriminator = undefer(self.discriminator) @@ -261,8 +268,9 @@ class Introspectable(dict): method = introspector.unrelate method( (self.category_name, self.discriminator), - (category_name, discriminator) - ) + (category_name, discriminator), + ) + class Deferred(object): """ Can be used by a third-party configuration extender to wrap a @@ -270,6 +278,7 @@ class Deferred(object): discriminator cannot be computed because it relies on unresolved values. The function should accept no arguments and should return a hashable discriminator.""" + def __init__(self, func): self.func = func @@ -282,6 +291,7 @@ class Deferred(object): def resolve(self): return self.value + def undefer(v): """ Function which accepts an object and returns it unless it is a :class:`pyramid.registry.Deferred` instance. If it is an instance of @@ -291,7 +301,9 @@ def undefer(v): v = v.resolve() return v + class predvalseq(tuple): """ A subtype of tuple used to represent a sequence of predicate values """ + global_registry = Registry('global') diff --git a/src/pyramid/renderers.py b/src/pyramid/renderers.py index d1c85b371..a8e3ec16f 100644 --- a/src/pyramid/renderers.py +++ b/src/pyramid/renderers.py @@ -3,22 +3,12 @@ import json import os import re -from zope.interface import ( - implementer, - providedBy, - ) +from zope.interface import implementer, providedBy from zope.interface.registry import Components -from pyramid.interfaces import ( - IJSONAdapter, - IRendererFactory, - IRendererInfo, - ) +from pyramid.interfaces import IJSONAdapter, IRendererFactory, IRendererInfo -from pyramid.compat import ( - string_types, - text_type, - ) +from pyramid.compat import string_types, text_type from pyramid.csrf import get_csrf_token from pyramid.decorator import reify @@ -35,6 +25,7 @@ from pyramid.util import hide_attrs # API + def render(renderer_name, value, request=None, package=None): """ Using the renderer ``renderer_name`` (a template or a static renderer), render the value (or set of values) present @@ -76,19 +67,19 @@ def render(renderer_name, value, request=None, package=None): registry = None if package is None: package = caller_package() - helper = RendererHelper(name=renderer_name, package=package, - registry=registry) + helper = RendererHelper( + name=renderer_name, package=package, registry=registry + ) with hide_attrs(request, 'response'): result = helper.render(value, None, request=request) return result -def render_to_response(renderer_name, - value, - request=None, - package=None, - response=None): + +def render_to_response( + renderer_name, value, request=None, package=None, response=None +): """ Using the renderer ``renderer_name`` (a template or a static renderer), render the value (or set of values) using the result of the renderer's ``__call__`` method (usually a string @@ -137,8 +128,9 @@ def render_to_response(renderer_name, registry = None if package is None: package = caller_package() - helper = RendererHelper(name=renderer_name, package=package, - registry=registry) + helper = RendererHelper( + name=renderer_name, package=package, registry=registry + ) with hide_attrs(request, 'response'): if response is not None: @@ -147,6 +139,7 @@ def render_to_response(renderer_name, return result + def get_renderer(renderer_name, package=None, registry=None): """ Return the renderer object for the renderer ``renderer_name``. @@ -165,12 +158,15 @@ def get_renderer(renderer_name, package=None, registry=None): """ if package is None: package = caller_package() - helper = RendererHelper(name=renderer_name, package=package, - registry=registry) + helper = RendererHelper( + name=renderer_name, package=package, registry=registry + ) return helper.renderer + # concrete renderer factory implementations (also API) + def string_renderer_factory(info): def _render(value, system): if not isinstance(value, string_types): @@ -182,10 +178,13 @@ def string_renderer_factory(info): if ct == response.default_content_type: response.content_type = 'text/plain' return value + return _render + _marker = object() + class JSON(object): """ Renderer that returns a JSON-encoded string. @@ -265,13 +264,15 @@ class JSON(object): instances of the ``Foo`` class when they're encountered in your view results.""" - self.components.registerAdapter(adapter, (type_or_iface,), - IJSONAdapter) + self.components.registerAdapter( + adapter, (type_or_iface,), IJSONAdapter + ) def __call__(self, info): """ Returns a plain JSON-encoded string with content-type ``application/json``. The content-type may be overridden by setting ``request.response.content_type``.""" + def _render(value, system): request = system.get('request') if request is not None: @@ -290,17 +291,21 @@ class JSON(object): return obj.__json__(request) obj_iface = providedBy(obj) adapters = self.components.adapters - result = adapters.lookup((obj_iface,), IJSONAdapter, - default=_marker) + result = adapters.lookup( + (obj_iface,), IJSONAdapter, default=_marker + ) if result is _marker: raise TypeError('%r is not JSON serializable' % (obj,)) return result(obj, request) + return default -json_renderer_factory = JSON() # bw compat + +json_renderer_factory = JSON() # bw compat JSONP_VALID_CALLBACK = re.compile(r"^[$a-z_][$0-9a-z_\.\[\]]+[^.]$", re.I) + class JSONP(JSON): """ `JSONP <https://en.wikipedia.org/wiki/JSONP>`_ renderer factory helper which implements a hybrid json/jsonp renderer. JSONP is useful for @@ -373,6 +378,7 @@ class JSONP(JSON): ``application/javascript`` if query parameter matching ``self.param_name`` is present in request.GET; otherwise returns plain-JSON encoded string with content-type ``application/json``""" + def _render(value, system): request = system.get('request') default = self._make_default(request) @@ -384,7 +390,9 @@ class JSONP(JSON): if callback is not None: if not JSONP_VALID_CALLBACK.match(callback): - raise HTTPBadRequest('Invalid JSONP callback function name.') + raise HTTPBadRequest( + 'Invalid JSONP callback function name.' + ) ct = 'application/javascript' body = '/**/{0}({1});'.format(callback, val) @@ -392,8 +400,10 @@ class JSONP(JSON): if response.content_type == response.default_content_type: response.content_type = ct return body + return _render + @implementer(IRendererInfo) class RendererHelper(object): def __init__(self, name=None, package=None, registry=None): @@ -422,36 +432,36 @@ class RendererHelper(object): def renderer(self): factory = self.registry.queryUtility(IRendererFactory, name=self.type) if factory is None: - raise ValueError( - 'No such renderer factory %s' % str(self.type)) + raise ValueError('No such renderer factory %s' % str(self.type)) return factory(self) def get_renderer(self): return self.renderer def render_view(self, request, response, view, context): - system = {'view':view, - 'renderer_name':self.name, # b/c - 'renderer_info':self, - 'context':context, - 'request':request, - 'req':request, - 'get_csrf_token':partial(get_csrf_token, request), - } + system = { + 'view': view, + 'renderer_name': self.name, # b/c + 'renderer_info': self, + 'context': context, + 'request': request, + 'req': request, + 'get_csrf_token': partial(get_csrf_token, request), + } return self.render_to_response(response, system, request=request) def render(self, value, system_values, request=None): renderer = self.renderer if system_values is None: system_values = { - 'view':None, - 'renderer_name':self.name, # b/c - 'renderer_info':self, - 'context':getattr(request, 'context', None), - 'request':request, - 'req':request, - 'get_csrf_token':partial(get_csrf_token, request), - } + 'view': None, + 'renderer_name': self.name, # b/c + 'renderer_info': self, + 'context': getattr(request, 'context', None), + 'request': request, + 'req': request, + 'get_csrf_token': partial(get_csrf_token, request), + } system_values = BeforeRender(system_values, value) @@ -495,12 +505,14 @@ class RendererHelper(object): registry = self.registry return self.__class__(name=name, package=package, registry=registry) + class NullRendererHelper(RendererHelper): """ Special renderer helper that has render_* methods which simply return the value they are fed rather than converting them to response objects; useful for testing purposes and special case view configuration registrations that want to use the view configuration machinery but do not want actual rendering to happen .""" + def __init__(self, name=None, package=None, registry=None): # we override the initializer to avoid calling get_current_registry # (it will return a reference to the global registry when this @@ -526,4 +538,5 @@ class NullRendererHelper(RendererHelper): def clone(self, name=None, package=None, registry=None): return self + null_renderer = NullRendererHelper() diff --git a/src/pyramid/request.py b/src/pyramid/request.py index 201f1d648..907b4477f 100644 --- a/src/pyramid/request.py +++ b/src/pyramid/request.py @@ -11,32 +11,23 @@ from pyramid.interfaces import ( IRequestExtensions, IResponse, ISessionFactory, - ) +) -from pyramid.compat import ( - text_, - bytes_, - native_, - iteritems_, - ) +from pyramid.compat import text_, bytes_, native_, iteritems_ from pyramid.decorator import reify from pyramid.i18n import LocalizerRequestMixin from pyramid.response import Response, _get_response_factory -from pyramid.security import ( - AuthenticationAPIMixin, - AuthorizationAPIMixin, - ) +from pyramid.security import AuthenticationAPIMixin, AuthorizationAPIMixin from pyramid.url import URLMethodsMixin -from pyramid.util import ( - InstancePropertyHelper, - InstancePropertyMixin, -) +from pyramid.util import InstancePropertyHelper, InstancePropertyMixin from pyramid.view import ViewMethodsMixin + class TemplateContext(object): pass + class CallbackMethodsMixin(object): @reify def finished_callbacks(self): @@ -146,6 +137,7 @@ class CallbackMethodsMixin(object): callback = callbacks.popleft() callback(self) + @implementer(IRequest) class Request( BaseRequest, @@ -156,7 +148,7 @@ class Request( AuthenticationAPIMixin, AuthorizationAPIMixin, ViewMethodsMixin, - ): +): """ A subclass of the :term:`WebOb` Request class. An instance of this class is created by the :term:`router` and is provided to a @@ -177,6 +169,7 @@ class Request( release of this :app:`Pyramid` version. See https://webob.org/ for further information. """ + exception = None exc_info = None matchdict = None @@ -201,7 +194,8 @@ class Request( if factory is None: raise AttributeError( 'No session factory registered ' - '(see the Sessions chapter of the Pyramid documentation)') + '(see the Sessions chapter of the Pyramid documentation)' + ) return factory(self) @reify @@ -244,13 +238,17 @@ def route_request_iface(name, bases=()): # zope.interface.interface.Element.__init__ and # https://github.com/Pylons/pyramid/issues/232; as a result, always pass # __doc__ to the InterfaceClass constructor. - iface = InterfaceClass('%s_IRequest' % name, bases=bases, - __doc__="route_request_iface-generated interface") + iface = InterfaceClass( + '%s_IRequest' % name, + bases=bases, + __doc__="route_request_iface-generated interface", + ) # for exception view lookups iface.combined = InterfaceClass( '%s_combined_IRequest' % name, bases=(iface, IRequest), - __doc__='route_request_iface-generated combined interface') + __doc__='route_request_iface-generated combined interface', + ) return iface @@ -258,8 +256,10 @@ def add_global_response_headers(request, headerlist): def add_headers(request, response): for k, v in headerlist: response.headerlist.append((k, v)) + request.add_response_callback(add_headers) + def call_app_with_subpath_as_path_info(request, app): # Copy the request. Use the source request's subpath (if it exists) as # the new request's PATH_INFO. Set the request copy's SCRIPT_NAME to the @@ -280,11 +280,12 @@ def call_app_with_subpath_as_path_info(request, app): new_script_name = '' # compute new_path_info - new_path_info = '/' + '/'.join([native_(x.encode('utf-8'), 'latin-1') - for x in subpath]) + new_path_info = '/' + '/'.join( + [native_(x.encode('utf-8'), 'latin-1') for x in subpath] + ) - if new_path_info != '/': # don't want a sole double-slash - if path_info != '/': # if orig path_info is '/', we're already done + if new_path_info != '/': # don't want a sole double-slash + if path_info != '/': # if orig path_info is '/', we're already done if path_info.endswith('/'): # readd trailing slash stripped by subpath (traversal) # conversion @@ -314,6 +315,7 @@ def call_app_with_subpath_as_path_info(request, app): return new_request.get_response(app) + def apply_request_extensions(request, extensions=None): """Apply request extensions (methods and properties) to an instance of :class:`pyramid.interfaces.IRequest`. This method is dependent on the @@ -331,4 +333,5 @@ def apply_request_extensions(request, extensions=None): setattr(request, name, method) InstancePropertyHelper.apply_properties( - request, extensions.descriptors) + request, extensions.descriptors + ) diff --git a/src/pyramid/resource.py b/src/pyramid/resource.py index 986c75e37..1454114fd 100644 --- a/src/pyramid/resource.py +++ b/src/pyramid/resource.py @@ -1,5 +1,6 @@ """ Backwards compatibility shim module (forever). """ -from pyramid.asset import * # b/w compat +from pyramid.asset import * # b/w compat + resolve_resource_spec = resolve_asset_spec resource_spec_from_abspath = asset_spec_from_abspath abspath_from_resource_spec = abspath_from_asset_spec diff --git a/src/pyramid/response.py b/src/pyramid/response.py index 1e2546ed0..38f9fa1ce 100644 --- a/src/pyramid/response.py +++ b/src/pyramid/response.py @@ -1,8 +1,5 @@ import mimetypes -from os.path import ( - getmtime, - getsize, - ) +from os.path import getmtime, getsize import venusian @@ -18,17 +15,20 @@ def init_mimetypes(mimetypes): return True return False + # See http://bugs.python.org/issue5853 which is a recursion bug # that seems to effect Python 2.6, Python 2.6.1, and 2.6.2 (a fix # has been applied on the Python 2 trunk). init_mimetypes(mimetypes) -_BLOCK_SIZE = 4096 * 64 # 256K +_BLOCK_SIZE = 4096 * 64 # 256K + @implementer(IResponse) class Response(_Response): pass + class FileResponse(Response): """ A Response object that can be used to serve a static file from disk @@ -51,14 +51,21 @@ class FileResponse(Response): binary file. This argument will be ignored if you also leave ``content-type`` as ``None``. """ - def __init__(self, path, request=None, cache_max_age=None, - content_type=None, content_encoding=None): + + def __init__( + self, + path, + request=None, + cache_max_age=None, + content_type=None, + content_encoding=None, + ): if content_type is None: content_type, content_encoding = _guess_type(path) super(FileResponse, self).__init__( conditional_response=True, content_type=content_type, - content_encoding=content_encoding + content_encoding=content_encoding, ) self.last_modified = getmtime(path) content_length = getsize(path) @@ -76,6 +83,7 @@ class FileResponse(Response): if cache_max_age is not None: self.cache_expires = cache_max_age + class FileIter(object): """ A fixed-block-size iterator for use as a WSGI app_iter. @@ -84,6 +92,7 @@ class FileIter(object): ``block_size`` is an optional block size for iteration. """ + def __init__(self, file, block_size=_BLOCK_SIZE): self.file = file self.block_size = block_size @@ -97,7 +106,7 @@ class FileIter(object): raise StopIteration return val - __next__ = next # py3 + __next__ = next # py3 def close(self): self.file.close() @@ -166,7 +175,8 @@ class response_adapter(object): Added the ``_depth`` and ``_category`` arguments. """ - venusian = venusian # for unit testing + + venusian = venusian # for unit testing def __init__(self, *types_or_ifaces, **kwargs): self.types_or_ifaces = types_or_ifaces @@ -180,8 +190,12 @@ class response_adapter(object): config.add_response_adapter(wrapped, type_or_iface, **self.kwargs) def __call__(self, wrapped): - self.venusian.attach(wrapped, self.register, category=self.category, - depth=self.depth + 1) + self.venusian.attach( + wrapped, + self.register, + category=self.category, + depth=self.depth + 1, + ) return wrapped @@ -190,18 +204,14 @@ def _get_response_factory(registry): `pyramid.interfaces.IResponseFactory`. """ response_factory = registry.queryUtility( - IResponseFactory, - default=lambda r: Response() + IResponseFactory, default=lambda r: Response() ) return response_factory def _guess_type(path): - content_type, content_encoding = mimetypes.guess_type( - path, - strict=False - ) + content_type, content_encoding = mimetypes.guess_type(path, strict=False) if content_type is None: content_type = 'application/octet-stream' # str-ifying content_type is a workaround for a bug in Python 2.7.7 diff --git a/src/pyramid/router.py b/src/pyramid/router.py index 49b7b601b..19641aecd 100644 --- a/src/pyramid/router.py +++ b/src/pyramid/router.py @@ -1,7 +1,4 @@ -from zope.interface import ( - implementer, - providedBy, - ) +from zope.interface import implementer, providedBy from pyramid.interfaces import ( IDebugLogger, @@ -15,14 +12,14 @@ from pyramid.interfaces import ( IRoutesMapper, ITraverser, ITweens, - ) +) from pyramid.events import ( ContextFound, NewRequest, NewResponse, BeforeTraversal, - ) +) from pyramid.httpexceptions import HTTPNotFound from pyramid.request import Request @@ -30,10 +27,8 @@ from pyramid.view import _call_view from pyramid.request import apply_request_extensions from pyramid.threadlocal import RequestContext -from pyramid.traversal import ( - DefaultRootFactory, - ResourceTreeTraverser, - ) +from pyramid.traversal import DefaultRootFactory, ResourceTreeTraverser + @implementer(IRouter) class Router(object): @@ -49,12 +44,13 @@ class Router(object): self.request_factory = q(IRequestFactory, default=Request) self.request_extensions = q(IRequestExtensions) self.execution_policy = q( - IExecutionPolicy, default=default_execution_policy) + IExecutionPolicy, default=default_execution_policy + ) self.orig_handle_request = self.handle_request tweens = q(ITweens) if tweens is not None: self.handle_request = tweens(self.handle_request, registry) - self.root_policy = self.root_factory # b/w compat + self.root_policy = self.root_factory # b/w compat self.registry = registry settings = registry.settings if settings is not None: @@ -82,8 +78,7 @@ class Router(object): match, route = info['match'], info['route'] if route is None: if debug_routematch: - msg = ('no route matched for url %s' % - request.url) + msg = 'no route matched for url %s' % request.url logger and logger.debug(msg) else: attrs['matchdict'] = match @@ -96,20 +91,21 @@ class Router(object): 'path_info: %r, ' 'pattern: %r, ' 'matchdict: %r, ' - 'predicates: %r' % ( + 'predicates: %r' + % ( request.url, route.name, request.path_info, route.pattern, match, - ', '.join([p.text() for p in route.predicates])) + ', '.join([p.text() for p in route.predicates]), ) + ) logger and logger.debug(msg) request.request_iface = registry.queryUtility( - IRouteRequest, - name=route.name, - default=IRequest) + IRouteRequest, name=route.name, default=IRequest + ) root_factory = route.factory or self.root_factory @@ -137,8 +133,8 @@ class Router(object): tdict['subpath'], tdict['traversed'], tdict['virtual_root'], - tdict['virtual_root_path'] - ) + tdict['virtual_root_path'], + ) attrs.update(tdict) @@ -149,12 +145,8 @@ class Router(object): # find a view callable context_iface = providedBy(context) response = _call_view( - registry, - request, - context, - context_iface, - view_name - ) + registry, request, context, context_iface, view_name + ) if response is None: if self.debug_notfound: @@ -162,11 +154,19 @@ class Router(object): 'debug_notfound of url %s; path_info: %r, ' 'context: %r, view_name: %r, subpath: %r, ' 'traversed: %r, root: %r, vroot: %r, ' - 'vroot_path: %r' % ( - request.url, request.path_info, context, - view_name, subpath, traversed, root, vroot, - vroot_path) + 'vroot_path: %r' + % ( + request.url, + request.path_info, + context, + view_name, + subpath, + traversed, + root, + vroot, + vroot_path, ) + ) logger and logger.debug(msg) else: msg = request.path_info @@ -270,6 +270,7 @@ class Router(object): response = self.execution_policy(environ, self) return response(environ, start_response) + def default_execution_policy(environ, router): with router.request_context(environ) as request: try: diff --git a/src/pyramid/scaffolds/__init__.py b/src/pyramid/scaffolds/__init__.py index 71a220e22..95c51a2d8 100644 --- a/src/pyramid/scaffolds/__init__.py +++ b/src/pyramid/scaffolds/__init__.py @@ -6,11 +6,13 @@ from pyramid.compat import native_ from pyramid.scaffolds.template import Template # API + class PyramidTemplate(Template): """ A class that can be used as a base class for Pyramid scaffolding templates. """ + def pre(self, command, output_dir, vars): """ Overrides :meth:`pyramid.scaffolds.template.Template.pre`, adding several variables to the default variables list (including @@ -26,7 +28,7 @@ class PyramidTemplate(Template): vars['package_logger'] = package_logger return Template.pre(self, command, output_dir, vars) - def post(self, command, output_dir, vars): # pragma: no cover + def post(self, command, output_dir, vars): # pragma: no cover """ Overrides :meth:`pyramid.scaffolds.template.Template.post`, to print "Welcome to Pyramid. Sorry for the convenience." after a successful scaffolding rendering.""" @@ -42,24 +44,29 @@ class PyramidTemplate(Template): Welcome to Pyramid. Sorry for the convenience. %(separator)s - """ % {'separator': separator}) + """ + % {'separator': separator} + ) self.out(msg) return Template.post(self, command, output_dir, vars) - def out(self, msg): # pragma: no cover (replaceable testing hook) + def out(self, msg): # pragma: no cover (replaceable testing hook) print(msg) + class StarterProjectTemplate(PyramidTemplate): _template_dir = 'starter' summary = 'Pyramid starter project using URL dispatch and Jinja2' + class ZODBProjectTemplate(PyramidTemplate): _template_dir = 'zodb' summary = 'Pyramid project using ZODB, traversal, and Chameleon' + class AlchemyProjectTemplate(PyramidTemplate): _template_dir = 'alchemy' summary = ( - 'Pyramid project using SQLAlchemy, SQLite, URL dispatch, and ' - 'Jinja2') + 'Pyramid project using SQLAlchemy, SQLite, URL dispatch, and ' 'Jinja2' + ) diff --git a/src/pyramid/scaffolds/copydir.py b/src/pyramid/scaffolds/copydir.py index 0914bb0d4..31e8dfb9e 100644 --- a/src/pyramid/scaffolds/copydir.py +++ b/src/pyramid/scaffolds/copydir.py @@ -11,7 +11,7 @@ from pyramid.compat import ( native_, url_quote as compat_url_quote, escape, - ) +) fsenc = sys.getfilesystemencoding() @@ -22,9 +22,20 @@ class SkipTemplate(Exception): Raise this exception during the substitution of your template """ -def copy_dir(source, dest, vars, verbosity, simulate, indent=0, - sub_vars=True, interactive=False, overwrite=True, - template_renderer=None, out_=sys.stdout): + +def copy_dir( + source, + dest, + vars, + verbosity, + simulate, + indent=0, + sub_vars=True, + interactive=False, + overwrite=True, + template_renderer=None, + out_=sys.stdout, +): """ Copies the ``source`` directory to the ``dest`` directory. @@ -49,10 +60,12 @@ def copy_dir(source, dest, vars, verbosity, simulate, indent=0, ``template_renderer(content_as_string, vars_as_dict, filename=filename)``. """ + def out(msg): out_.write(msg) out_.write('\n') out_.flush() + # This allows you to use a leading +dot+ in filenames which would # otherwise be skipped because leading dots make the file hidden: vars.setdefault('dot', '.') @@ -80,7 +93,7 @@ def copy_dir(source, dest, vars, verbosity, simulate, indent=0, if verbosity >= 2: reason = pad + reason % {'filename': full} out(reason) - continue # pragma: no cover + continue # pragma: no cover if sub_vars: dest_full = os.path.join(dest, substitute_filename(name, vars)) sub_file = False @@ -90,18 +103,36 @@ def copy_dir(source, dest, vars, verbosity, simulate, indent=0, if use_pkg_resources and pkg_resources.resource_isdir(source[0], full): if verbosity: out('%sRecursing into %s' % (pad, os.path.basename(full))) - copy_dir((source[0], full), dest_full, vars, verbosity, simulate, - indent=indent + 1, sub_vars=sub_vars, - interactive=interactive, overwrite=overwrite, - template_renderer=template_renderer, out_=out_) + copy_dir( + (source[0], full), + dest_full, + vars, + verbosity, + simulate, + indent=indent + 1, + sub_vars=sub_vars, + interactive=interactive, + overwrite=overwrite, + template_renderer=template_renderer, + out_=out_, + ) continue elif not use_pkg_resources and os.path.isdir(full): if verbosity: out('%sRecursing into %s' % (pad, os.path.basename(full))) - copy_dir(full, dest_full, vars, verbosity, simulate, - indent=indent + 1, sub_vars=sub_vars, - interactive=interactive, overwrite=overwrite, - template_renderer=template_renderer, out_=out_) + copy_dir( + full, + dest_full, + vars, + verbosity, + simulate, + indent=indent + 1, + sub_vars=sub_vars, + interactive=interactive, + overwrite=overwrite, + template_renderer=template_renderer, + out_=out_, + ) continue elif use_pkg_resources: content = pkg_resources.resource_string(source[0], full) @@ -111,12 +142,14 @@ def copy_dir(source, dest, vars, verbosity, simulate, indent=0, if sub_file: try: content = substitute_content( - content, vars, filename=full, - template_renderer=template_renderer - ) - except SkipTemplate: - continue # pragma: no cover - if content is None: + content, + vars, + filename=full, + template_renderer=template_renderer, + ) + except SkipTemplate: + continue # pragma: no cover + if content is None: continue # pragma: no cover already_exists = os.path.exists(dest_full) if already_exists: @@ -124,27 +157,33 @@ def copy_dir(source, dest, vars, verbosity, simulate, indent=0, old_content = f.read() if old_content == content: if verbosity: - out('%s%s already exists (same content)' % - (pad, dest_full)) - continue # pragma: no cover + out( + '%s%s already exists (same content)' % (pad, dest_full) + ) + continue # pragma: no cover if interactive: if not query_interactive( - native_(full, fsenc), native_(dest_full, fsenc), - native_(content, fsenc), native_(old_content, fsenc), - simulate=simulate, out_=out_): + native_(full, fsenc), + native_(dest_full, fsenc), + native_(content, fsenc), + native_(old_content, fsenc), + simulate=simulate, + out_=out_, + ): continue elif not overwrite: - continue # pragma: no cover + continue # pragma: no cover if verbosity and use_pkg_resources: out('%sCopying %s to %s' % (pad, full, dest_full)) elif verbosity: out( - '%sCopying %s to %s' % (pad, os.path.basename(full), - dest_full)) + '%sCopying %s to %s' % (pad, os.path.basename(full), dest_full) + ) if not simulate: with open(dest_full, 'wb') as f: f.write(content) + def should_skip_file(name): """ Checks if a file should be skipped based on its name. @@ -164,38 +203,60 @@ def should_skip_file(name): return 'Skipping version control directory %(filename)s' return None + # Overridden on user's request: all_answer = None -def query_interactive(src_fn, dest_fn, src_content, dest_content, - simulate, out_=sys.stdout): + +def query_interactive( + src_fn, dest_fn, src_content, dest_content, simulate, out_=sys.stdout +): def out(msg): out_.write(msg) out_.write('\n') out_.flush() + global all_answer from difflib import unified_diff, context_diff - u_diff = list(unified_diff( - dest_content.splitlines(), - src_content.splitlines(), - dest_fn, src_fn)) - c_diff = list(context_diff( - dest_content.splitlines(), - src_content.splitlines(), - dest_fn, src_fn)) - added = len([l for l in u_diff if l.startswith('+') and - not l.startswith('+++')]) - removed = len([l for l in u_diff if l.startswith('-') and - not l.startswith('---')]) + + u_diff = list( + unified_diff( + dest_content.splitlines(), + src_content.splitlines(), + dest_fn, + src_fn, + ) + ) + c_diff = list( + context_diff( + dest_content.splitlines(), + src_content.splitlines(), + dest_fn, + src_fn, + ) + ) + added = len( + [l for l in u_diff if l.startswith('+') and not l.startswith('+++')] + ) + removed = len( + [l for l in u_diff if l.startswith('-') and not l.startswith('---')] + ) if added > removed: msg = '; %i lines added' % (added - removed) elif removed > added: msg = '; %i lines removed' % (removed - added) else: msg = '' - out('Replace %i bytes with %i bytes (%i/%i lines changed%s)' % ( - len(dest_content), len(src_content), - removed, len(dest_content.splitlines()), msg)) + out( + 'Replace %i bytes with %i bytes (%i/%i lines changed%s)' + % ( + len(dest_content), + len(src_content), + removed, + len(dest_content.splitlines()), + msg, + ) + ) prompt = 'Overwrite %s [y/n/d/B/?] ' % dest_fn while 1: if all_answer is None: @@ -204,6 +265,7 @@ def query_interactive(src_fn, dest_fn, src_content, dest_content, response = all_answer if not response or response[0] == 'b': import shutil + new_dest_fn = dest_fn + '.bak' n = 0 while os.path.exists(new_dest_fn): @@ -230,6 +292,7 @@ def query_interactive(src_fn, dest_fn, src_content, dest_content, else: out(query_usage) + query_usage = """\ Responses: Y(es): Overwrite the file with the new content. @@ -240,39 +303,47 @@ Responses: Type "all Y/N/B" to use Y/N/B for answer to all future questions """ + def makedirs(dir, verbosity, pad): parent = os.path.dirname(os.path.abspath(dir)) if not os.path.exists(parent): makedirs(parent, verbosity, pad) # pragma: no cover os.mkdir(dir) + def substitute_filename(fn, vars): for var, value in vars.items(): fn = fn.replace('+%s+' % var, str(value)) return fn -def substitute_content(content, vars, filename='<string>', - template_renderer=None): + +def substitute_content( + content, vars, filename='<string>', template_renderer=None +): v = standard_vars.copy() v.update(vars) return template_renderer(content, v, filename=filename) + def html_quote(s): if s is None: return '' return escape(str(s), 1) + def url_quote(s): if s is None: return '' return compat_url_quote(str(s)) + def test(conf, true_cond, false_cond=None): if conf: return true_cond else: return false_cond + def skip_template(condition=True, *args): """ Raise SkipTemplate, which causes copydir to skip the template @@ -286,6 +357,7 @@ def skip_template(condition=True, *args): if condition: raise SkipTemplate(*args) + standard_vars = { 'nothing': None, 'html_quote': html_quote, @@ -297,5 +369,4 @@ standard_vars = { 'bool': bool, 'SkipTemplate': SkipTemplate, 'skip_template': skip_template, - } - +} diff --git a/src/pyramid/scaffolds/template.py b/src/pyramid/scaffolds/template.py index e5098e815..60b543842 100644 --- a/src/pyramid/scaffolds/template.py +++ b/src/pyramid/scaffolds/template.py @@ -6,19 +6,18 @@ import re import sys import os -from pyramid.compat import ( - native_, - bytes_, - ) +from pyramid.compat import native_, bytes_ from pyramid.scaffolds import copydir fsenc = sys.getfilesystemencoding() + class Template(object): """ Inherit from this base class and override methods to use the Pyramid scaffolding system.""" - copydir = copydir # for testing + + copydir = copydir # for testing _template_dir = None def __init__(self, name): @@ -36,7 +35,10 @@ class Template(object): try: return bytes_( substitute_escaped_double_braces( - substitute_double_braces(content, TypeMapper(vars))), fsenc) + substitute_double_braces(content, TypeMapper(vars)) + ), + fsenc, + ) except Exception as e: _add_except(e, ' in file %s' % filename) raise @@ -54,7 +56,8 @@ class Template(object): construct a path. If _template_dir is a tuple, it should be a 2-element tuple: ``(package_name, package_relative_path)``.""" assert self._template_dir is not None, ( - "Template %r didn't set _template_dir" % self) + "Template %r didn't set _template_dir" % self + ) if isinstance(self._template_dir, tuple): return self._template_dir else: @@ -65,13 +68,13 @@ class Template(object): self.write_files(command, output_dir, vars) self.post(command, output_dir, vars) - def pre(self, command, output_dir, vars): # pragma: no cover + def pre(self, command, output_dir, vars): # pragma: no cover """ Called before template is applied. """ pass - def post(self, command, output_dir, vars): # pragma: no cover + def post(self, command, output_dir, vars): # pragma: no cover """ Called after template is applied. """ @@ -95,15 +98,15 @@ class Template(object): overwrite=command.args.overwrite, indent=1, template_renderer=self.render_template, - ) + ) - def makedirs(self, dir): # pragma: no cover + def makedirs(self, dir): # pragma: no cover return os.makedirs(dir) - def exists(self, path): # pragma: no cover + def exists(self, path): # pragma: no cover return os.path.exists(path) - def out(self, msg): # pragma: no cover + def out(self, msg): # pragma: no cover print(msg) # hair for exit with usage when paster create is used under 1.3 instead @@ -113,13 +116,15 @@ class Template(object): # required_templates tuple is required to allow it to get as far as # calling check_vars. required_templates = () + def check_vars(self, vars, other): raise RuntimeError( 'Under Pyramid 1.3, you should use the "pcreate" command rather ' - 'than "paster create"') + 'than "paster create"' + ) -class TypeMapper(dict): +class TypeMapper(dict): def __getitem__(self, item): options = item.split('|') for op in options[:-1]: @@ -135,6 +140,7 @@ class TypeMapper(dict): else: return str(value) + def eval_with_catch(expr, vars): try: return eval(expr, vars) @@ -142,23 +148,32 @@ def eval_with_catch(expr, vars): _add_except(e, 'in expression %r' % expr) raise + double_brace_pattern = re.compile(r'{{(?P<braced>.*?)}}') + def substitute_double_braces(content, values): def double_bracerepl(match): value = match.group('braced').strip() return values[value] + return double_brace_pattern.sub(double_bracerepl, content) -escaped_double_brace_pattern = re.compile(r'\\{\\{(?P<escape_braced>[^\\]*?)\\}\\}') + +escaped_double_brace_pattern = re.compile( + r'\\{\\{(?P<escape_braced>[^\\]*?)\\}\\}' +) + def substitute_escaped_double_braces(content): def escaped_double_bracerepl(match): value = match.group('escape_braced').strip() return "{{%(value)s}}" % locals() + return escaped_double_brace_pattern.sub(escaped_double_bracerepl, content) -def _add_except(exc, info): # pragma: no cover + +def _add_except(exc, info): # pragma: no cover if not hasattr(exc, 'args') or exc.args is None: return args = list(exc.args) @@ -168,5 +183,3 @@ def _add_except(exc, info): # pragma: no cover args = [info] exc.args = tuple(args) return - - diff --git a/src/pyramid/scaffolds/tests.py b/src/pyramid/scaffolds/tests.py index 44680a464..cb8842dbe 100644 --- a/src/pyramid/scaffolds/tests.py +++ b/src/pyramid/scaffolds/tests.py @@ -15,12 +15,12 @@ class TemplateTest(object): def make_venv(self, directory): # pragma: no cover import virtualenv from virtualenv import Logger + logger = Logger([(Logger.level_for_integer(2), sys.stdout)]) virtualenv.logger = logger - virtualenv.create_environment(directory, - site_packages=False, - clear=False, - unzip_setuptools=True) + virtualenv.create_environment( + directory, site_packages=False, clear=False, unzip_setuptools=True + ) def install(self, tmpl_name): # pragma: no cover try: @@ -36,14 +36,18 @@ class TemplateTest(object): os.chdir('Dingle') subprocess.check_call([pip, 'install', '.[testing]']) if tmpl_name == 'alchemy': - populate = os.path.join(self.directory, 'bin', - 'initialize_Dingle_db') + populate = os.path.join( + self.directory, 'bin', 'initialize_Dingle_db' + ) subprocess.check_call([populate, 'development.ini']) - subprocess.check_call([ - os.path.join(self.directory, 'bin', 'py.test')]) + subprocess.check_call( + [os.path.join(self.directory, 'bin', 'py.test')] + ) pserve = os.path.join(self.directory, 'bin', 'pserve') - for ininame, hastoolbar in (('development.ini', True), - ('production.ini', False)): + for ininame, hastoolbar in ( + ('development.ini', True), + ('production.ini', False), + ): proc = subprocess.Popen([pserve, ininame]) try: time.sleep(5) @@ -66,10 +70,10 @@ class TemplateTest(object): shutil.rmtree(self.directory) os.chdir(self.old_cwd) -if __name__ == '__main__': # pragma: no cover + +if __name__ == '__main__': # pragma: no cover templates = ['starter', 'alchemy', 'zodb'] for name in templates: test = TemplateTest() test.install(name) - diff --git a/src/pyramid/scripting.py b/src/pyramid/scripting.py index 087b55ccb..cbf9d5e32 100644 --- a/src/pyramid/scripting.py +++ b/src/pyramid/scripting.py @@ -1,16 +1,14 @@ from pyramid.config import global_registries from pyramid.exceptions import ConfigurationError -from pyramid.interfaces import ( - IRequestFactory, - IRootFactory, - ) +from pyramid.interfaces import IRequestFactory, IRootFactory from pyramid.request import Request from pyramid.request import apply_request_extensions from pyramid.threadlocal import RequestContext from pyramid.traversal import DefaultRootFactory + def get_root(app, request=None): """ Return a tuple composed of ``(root, closer)`` when provided a :term:`router` instance as the ``app`` argument. The ``root`` @@ -29,11 +27,14 @@ def get_root(app, request=None): request.registry = registry ctx = RequestContext(request) ctx.begin() + def closer(): ctx.end() + root = app.root_factory(request) return root, closer + def prepare(request=None, registry=None): """ This function pushes data onto the Pyramid threadlocal stack (request and registry), making those objects 'current'. It @@ -80,9 +81,11 @@ def prepare(request=None, registry=None): if registry is None: registry = getattr(request, 'registry', global_registries.last) if registry is None: - raise ConfigurationError('No valid Pyramid applications could be ' - 'found, make sure one has been created ' - 'before trying to activate it.') + raise ConfigurationError( + 'No valid Pyramid applications could be ' + 'found, make sure one has been created ' + 'before trying to activate it.' + ) if request is None: request = _make_request('/', registry) # NB: even though _make_request might have already set registry on @@ -92,10 +95,13 @@ def prepare(request=None, registry=None): ctx = RequestContext(request) ctx.begin() apply_request_extensions(request) + def closer(): ctx.end() - root_factory = registry.queryUtility(IRootFactory, - default=DefaultRootFactory) + + root_factory = registry.queryUtility( + IRootFactory, default=DefaultRootFactory + ) root = root_factory(request) if getattr(request, 'context', None) is None: request.context = root @@ -107,6 +113,7 @@ def prepare(request=None, registry=None): root_factory=root_factory, ) + class AppEnvironment(dict): def __enter__(self): return self @@ -114,6 +121,7 @@ class AppEnvironment(dict): def __exit__(self, type, value, traceback): self['closer']() + def _make_request(path, registry=None): """ Return a :meth:`pyramid.request.Request` object anchored at a given path. The object returned will be generated from the supplied diff --git a/src/pyramid/scripts/common.py b/src/pyramid/scripts/common.py index f4b8027db..9181eea8e 100644 --- a/src/pyramid/scripts/common.py +++ b/src/pyramid/scripts/common.py @@ -1,5 +1,6 @@ import plaster + def parse_vars(args): """ Given variables like ``['a=b', 'c=d']`` turns it into ``{'a': @@ -8,13 +9,12 @@ def parse_vars(args): result = {} for arg in args: if '=' not in arg: - raise ValueError( - 'Variable assignment %r invalid (no "=")' - % arg) + raise ValueError('Variable assignment %r invalid (no "=")' % arg) name, value = arg.split('=', 1) result[name] = value return result + def get_config_loader(config_uri): """ Find a ``plaster.ILoader`` object supporting the "wsgi" protocol. diff --git a/src/pyramid/scripts/pcreate.py b/src/pyramid/scripts/pcreate.py index a6db520ce..f3dffefef 100644 --- a/src/pyramid/scripts/pcreate.py +++ b/src/pyramid/scripts/pcreate.py @@ -33,60 +33,88 @@ https://github.com/Pylons/?q=cookiecutter """, formatter_class=argparse.RawDescriptionHelpFormatter, ) - parser.add_argument('-s', '--scaffold', - dest='scaffold_name', - action='append', - help=("Add a scaffold to the create process " - "(multiple -s args accepted)")) - parser.add_argument('-t', '--template', - dest='scaffold_name', - action='append', - help=('A backwards compatibility alias for ' - '-s/--scaffold. Add a scaffold to the ' - 'create process (multiple -t args accepted)')) - parser.add_argument('-l', '--list', - dest='list', - action='store_true', - help="List all available scaffold names") - parser.add_argument('--list-templates', - dest='list', - action='store_true', - help=("A backwards compatibility alias for -l/--list. " - "List all available scaffold names.")) - parser.add_argument('--package-name', - dest='package_name', - action='store', - help='Package name to use. The name provided is ' - 'assumed to be a valid Python package name, and ' - 'will not be validated. By default the package ' - 'name is derived from the value of ' - 'output_directory.') - parser.add_argument('--simulate', - dest='simulate', - action='store_true', - help='Simulate but do no work') - parser.add_argument('--overwrite', - dest='overwrite', - action='store_true', - help='Always overwrite') - parser.add_argument('--interactive', - dest='interactive', - action='store_true', - help='When a file would be overwritten, interrogate ' - '(this is the default, but you may specify it to ' - 'override --overwrite)') - parser.add_argument('--ignore-conflicting-name', - dest='force_bad_name', - action='store_true', - default=False, - help='Do create a project even if the chosen name ' - 'is the name of an already existing / importable ' - 'package.') - parser.add_argument('output_directory', - nargs='?', - default=None, - help='The directory where the project will be ' - 'created.') + parser.add_argument( + '-s', + '--scaffold', + dest='scaffold_name', + action='append', + help=( + "Add a scaffold to the create process " + "(multiple -s args accepted)" + ), + ) + parser.add_argument( + '-t', + '--template', + dest='scaffold_name', + action='append', + help=( + 'A backwards compatibility alias for ' + '-s/--scaffold. Add a scaffold to the ' + 'create process (multiple -t args accepted)' + ), + ) + parser.add_argument( + '-l', + '--list', + dest='list', + action='store_true', + help="List all available scaffold names", + ) + parser.add_argument( + '--list-templates', + dest='list', + action='store_true', + help=( + "A backwards compatibility alias for -l/--list. " + "List all available scaffold names." + ), + ) + parser.add_argument( + '--package-name', + dest='package_name', + action='store', + help='Package name to use. The name provided is ' + 'assumed to be a valid Python package name, and ' + 'will not be validated. By default the package ' + 'name is derived from the value of ' + 'output_directory.', + ) + parser.add_argument( + '--simulate', + dest='simulate', + action='store_true', + help='Simulate but do no work', + ) + parser.add_argument( + '--overwrite', + dest='overwrite', + action='store_true', + help='Always overwrite', + ) + parser.add_argument( + '--interactive', + dest='interactive', + action='store_true', + help='When a file would be overwritten, interrogate ' + '(this is the default, but you may specify it to ' + 'override --overwrite)', + ) + parser.add_argument( + '--ignore-conflicting-name', + dest='force_bad_name', + action='store_true', + default=False, + help='Do create a project even if the chosen name ' + 'is the name of an already existing / importable ' + 'package.', + ) + parser.add_argument( + 'output_directory', + nargs='?', + default=None, + help='The directory where the project will be ' 'created.', + ) pyramid_dist = pkg_resources.get_distribution("pyramid") @@ -123,7 +151,8 @@ https://github.com/Pylons/?q=cookiecutter project_name = os.path.basename(os.path.split(output_dir)[1]) if self.args.package_name is None: pkg_name = _bad_chars_re.sub( - '', project_name.lower().replace('-', '_')) + '', project_name.lower().replace('-', '_') + ) safe_name = pkg_resources.safe_name(project_name) else: pkg_name = self.args.package_name @@ -170,9 +199,14 @@ https://github.com/Pylons/?q=cookiecutter max_name = max([len(t.name) for t in scaffolds]) self.out('Available scaffolds:') for scaffold in scaffolds: - self.out(' %s:%s %s' % ( - scaffold.name, - ' ' * (max_name - len(scaffold.name)), scaffold.summary)) + self.out( + ' %s:%s %s' + % ( + scaffold.name, + ' ' * (max_name - len(scaffold.name)), + scaffold.summary, + ) + ) else: self.out('No scaffolds available') return 0 @@ -186,8 +220,10 @@ https://github.com/Pylons/?q=cookiecutter scaffold = scaffold_class(entry.name) scaffolds.append(scaffold) except Exception as e: # pragma: no cover - self.out('Warning: could not load entry point %s (%s: %s)' % ( - entry.name, e.__class__.__name__, e)) + self.out( + 'Warning: could not load entry point %s (%s: %s)' + % (entry.name, e.__class__.__name__, e) + ) return scaffolds def out(self, msg): # pragma: no cover @@ -196,8 +232,10 @@ https://github.com/Pylons/?q=cookiecutter def validate_input(self): if not self.args.scaffold_name: - self.out('You must provide at least one scaffold name: ' - '-s <scaffold name>') + self.out( + 'You must provide at least one scaffold name: ' + '-s <scaffold name>' + ) self.out('') self.show_scaffolds() return False @@ -213,11 +251,14 @@ https://github.com/Pylons/?q=cookiecutter pkg_name = self.project_vars['package'] if pkg_name == 'site' and not self.args.force_bad_name: - self.out('The package name "site" has a special meaning in ' - 'Python. Are you sure you want to use it as your ' - 'project\'s name?') - return self.confirm_bad_name('Really use "{0}"?: '.format( - pkg_name)) + self.out( + 'The package name "site" has a special meaning in ' + 'Python. Are you sure you want to use it as your ' + 'project\'s name?' + ) + return self.confirm_bad_name( + 'Really use "{0}"?: '.format(pkg_name) + ) # check if pkg_name can be imported (i.e. already exists in current # $PYTHON_PATH, if so - let the user confirm @@ -232,8 +273,10 @@ https://github.com/Pylons/?q=cookiecutter if self.args.force_bad_name: return True - self.out('A package named "{0}" already exists, are you sure you want ' - 'to use it as your project\'s name?'.format(pkg_name)) + self.out( + 'A package named "{0}" already exists, are you sure you want ' + 'to use it as your project\'s name?'.format(pkg_name) + ) return self.confirm_bad_name('Really use "{0}"?: '.format(pkg_name)) def confirm_bad_name(self, prompt): # pragma: no cover @@ -241,11 +284,14 @@ https://github.com/Pylons/?q=cookiecutter return answer.strip().lower() == 'y' def _warn_pcreate_deprecated(self): - self.out('''\ + self.out( + '''\ Note: As of Pyramid 1.8, this command is deprecated. Use a specific cookiecutter instead: https://github.com/pylons/?query=cookiecutter -''') +''' + ) + if __name__ == '__main__': # pragma: no cover sys.exit(main() or 0) diff --git a/src/pyramid/scripts/pdistreport.py b/src/pyramid/scripts/pdistreport.py index 1952e5d39..3ace9451e 100644 --- a/src/pyramid/scripts/pdistreport.py +++ b/src/pyramid/scripts/pdistreport.py @@ -4,19 +4,27 @@ import pkg_resources import argparse from operator import itemgetter -def out(*args): # pragma: no cover + +def out(*args): # pragma: no cover for arg in args: sys.stdout.write(arg) sys.stdout.write(' ') sys.stdout.write('\n') + def get_parser(): parser = argparse.ArgumentParser( - description="Show Python distribution versions and locations in use") + description="Show Python distribution versions and locations in use" + ) return parser -def main(argv=sys.argv, pkg_resources=pkg_resources, platform=platform.platform, - out=out): + +def main( + argv=sys.argv, + pkg_resources=pkg_resources, + platform=platform.platform, + out=out, +): # all args except argv are for unit testing purposes only parser = get_parser() parser.parse_args(argv[1:]) @@ -24,11 +32,13 @@ def main(argv=sys.argv, pkg_resources=pkg_resources, platform=platform.platform, for distribution in pkg_resources.working_set: name = distribution.project_name packages.append( - {'version': distribution.version, - 'lowername': name.lower(), - 'name': name, - 'location':distribution.location} - ) + { + 'version': distribution.version, + 'lowername': name.lower(), + 'name': name, + 'location': distribution.location, + } + ) packages = sorted(packages, key=itemgetter('lowername')) pyramid_version = pkg_resources.get_distribution('pyramid').version plat = platform() @@ -39,5 +49,6 @@ def main(argv=sys.argv, pkg_resources=pkg_resources, platform=platform.platform, out(' ', package['name'], package['version']) out(' ', package['location']) -if __name__ == '__main__': # pragma: no cover + +if __name__ == '__main__': # pragma: no cover sys.exit(main() or 0) diff --git a/src/pyramid/scripts/prequest.py b/src/pyramid/scripts/prequest.py index f0681afd7..e8f5ff8b3 100644 --- a/src/pyramid/scripts/prequest.py +++ b/src/pyramid/scripts/prequest.py @@ -8,10 +8,12 @@ from pyramid.request import Request from pyramid.scripts.common import get_config_loader from pyramid.scripts.common import parse_vars + def main(argv=sys.argv, quiet=False): command = PRequestCommand(argv, quiet) return command.run() + class PRequestCommand(object): description = """\ Submit a HTTP request to a web application. @@ -48,15 +50,16 @@ class PRequestCommand(object): parser = argparse.ArgumentParser( description=textwrap.dedent(description), formatter_class=argparse.RawDescriptionHelpFormatter, - ) + ) parser.add_argument( - '-n', '--app-name', + '-n', + '--app-name', dest='app_name', metavar='NAME', help=( "Load the named application from the config file (default 'main')" ), - ) + ) parser.add_argument( '--header', dest='headers', @@ -67,47 +70,55 @@ class PRequestCommand(object): ), ) parser.add_argument( - '-d', '--display-headers', + '-d', + '--display-headers', dest='display_headers', action='store_true', - help='Display status and headers before the response body' - ) + help='Display status and headers before the response body', + ) parser.add_argument( - '-m', '--method', + '-m', + '--method', dest='method', - choices=['GET', 'HEAD', 'POST', 'PUT', 'PATCH','DELETE', - 'PROPFIND', 'OPTIONS'], + choices=[ + 'GET', + 'HEAD', + 'POST', + 'PUT', + 'PATCH', + 'DELETE', + 'PROPFIND', + 'OPTIONS', + ], help='Request method type (GET, POST, PUT, PATCH, DELETE, ' - 'PROPFIND, OPTIONS)', - ) + 'PROPFIND, OPTIONS)', + ) parser.add_argument( - '-l', '--login', + '-l', + '--login', dest='login', help='HTTP basic auth username:password pair', - ) + ) parser.add_argument( 'config_uri', nargs='?', default=None, help='The URI to the configuration file.', - ) + ) parser.add_argument( - 'path_info', - nargs='?', - default=None, - help='The path of the request.', - ) + 'path_info', nargs='?', default=None, help='The path of the request.' + ) parser.add_argument( 'config_vars', nargs='*', default=(), help="Variables required by the config file. For example, " - "`http_port=%%(http_port)s` would expect `http_port=8080` to be " - "passed here.", - ) + "`http_port=%%(http_port)s` would expect `http_port=8080` to be " + "passed here.", + ) _get_config_loader = staticmethod(get_config_loader) stdin = sys.stdin @@ -116,7 +127,7 @@ class PRequestCommand(object): self.quiet = quiet self.args = self.parser.parse_args(argv[1:]) - def out(self, msg): # pragma: no cover + def out(self, msg): # pragma: no cover if not self.quiet: print(msg) @@ -153,7 +164,8 @@ class PRequestCommand(object): if ':' not in item: self.out( "Bad --header=%s option, value must be in the form " - "'name:value'" % item) + "'name:value'" % item + ) return 2 name, value = item.split(':', 1) headers[name] = value.strip() @@ -162,13 +174,13 @@ class PRequestCommand(object): environ = { 'REQUEST_METHOD': request_method, - 'SCRIPT_NAME': '', # may be empty if app is at the root + 'SCRIPT_NAME': '', # may be empty if app is at the root 'PATH_INFO': path, 'SERVER_NAME': 'localhost', # always mandatory - 'SERVER_PORT': '80', # always mandatory + 'SERVER_PORT': '80', # always mandatory 'SERVER_PROTOCOL': 'HTTP/1.0', 'CONTENT_TYPE': 'text/plain', - 'REMOTE_ADDR':'127.0.0.1', + 'REMOTE_ADDR': '127.0.0.1', 'wsgi.run_once': True, 'wsgi.multithread': False, 'wsgi.multiprocess': False, @@ -178,7 +190,7 @@ class PRequestCommand(object): 'QUERY_STRING': qs, 'HTTP_ACCEPT': 'text/plain;q=1.0, */*;q=0.1', 'paste.command_request': True, - } + } if request_method in ('POST', 'PUT', 'PATCH'): environ['wsgi.input'] = self.stdin @@ -203,5 +215,6 @@ class PRequestCommand(object): self.out(response.body) return 0 -if __name__ == '__main__': # pragma: no cover + +if __name__ == '__main__': # pragma: no cover sys.exit(main() or 0) diff --git a/src/pyramid/scripts/proutes.py b/src/pyramid/scripts/proutes.py index 69d61ae8f..2bce7d1de 100644 --- a/src/pyramid/scripts/proutes.py +++ b/src/pyramid/scripts/proutes.py @@ -110,7 +110,7 @@ def _get_view_module(view_callable): if original_view.package_name is not None: return '%s:%s' % ( original_view.package_name, - original_view.docroot + original_view.docroot, ) else: return original_view.docroot @@ -122,10 +122,7 @@ def _get_view_module(view_callable): # for them and remove this logic view_name = str(view_callable) - view_module = '%s.%s' % ( - view_callable.__module__, - view_name, - ) + view_module = '%s.%s' % (view_callable.__module__, view_name) # If pyramid wraps something in wsgiapp or wsgiapp2 decorators # that is currently returned as pyramid.router.decorator, lets @@ -139,24 +136,17 @@ def _get_view_module(view_callable): def get_route_data(route, registry): pattern = _get_pattern(route) - request_iface = registry.queryUtility( - IRouteRequest, - name=route.name - ) + request_iface = registry.queryUtility(IRouteRequest, name=route.name) route_request_methods = None view_request_methods_order = [] view_request_methods = {} view_callable = None - route_intr = registry.introspector.get( - 'routes', route.name - ) + route_intr = registry.introspector.get('routes', route.name) if request_iface is None: - return [ - (route.name, _get_pattern(route), UNKNOWN_KEY, ANY_KEY) - ] + return [(route.name, _get_pattern(route), UNKNOWN_KEY, ANY_KEY)] view_callables = _find_views(registry, request_iface, Interface, '') if view_callables: @@ -188,7 +178,7 @@ def get_route_data(route, registry): view_callable = getattr(view['callable'], view['attr']) view_module = '%s.%s' % ( _get_view_module(view['callable']), - view['attr'] + view['attr'], ) else: view_callable = view['callable'] @@ -217,17 +207,11 @@ def get_route_data(route, registry): for view_module in view_request_methods_order: methods = view_request_methods[view_module] - request_methods = _get_request_methods( - route_request_methods, - methods - ) + request_methods = _get_request_methods(route_request_methods, methods) - final_routes.append(( - route.name, - pattern, - view_module, - request_methods, - )) + final_routes.append( + (route.name, pattern, view_module, request_methods) + ) return final_routes @@ -251,43 +235,49 @@ class PRoutesCommand(object): parser = argparse.ArgumentParser( description=textwrap.dedent(description), formatter_class=argparse.RawDescriptionHelpFormatter, - ) - parser.add_argument('-g', '--glob', - action='store', - dest='glob', - default='', - help='Display routes matching glob pattern') - - parser.add_argument('-f', '--format', - action='store', - dest='format', - default='', - help=('Choose which columns to display, this will ' - 'override the format key in the [proutes] ini ' - 'section')) + ) + parser.add_argument( + '-g', + '--glob', + action='store', + dest='glob', + default='', + help='Display routes matching glob pattern', + ) + + parser.add_argument( + '-f', + '--format', + action='store', + dest='format', + default='', + help=( + 'Choose which columns to display, this will ' + 'override the format key in the [proutes] ini ' + 'section' + ), + ) parser.add_argument( 'config_uri', nargs='?', default=None, help='The URI to the configuration file.', - ) + ) parser.add_argument( 'config_vars', nargs='*', default=(), help="Variables required by the config file. For example, " - "`http_port=%%(http_port)s` would expect `http_port=8080` to be " - "passed here.", - ) + "`http_port=%%(http_port)s` would expect `http_port=8080` to be " + "passed here.", + ) def __init__(self, argv, quiet=False): self.args = self.parser.parse_args(argv[1:]) self.quiet = quiet - self.available_formats = [ - 'name', 'pattern', 'view', 'method' - ] + self.available_formats = ['name', 'pattern', 'view', 'method'] self.column_format = self.available_formats def validate_formats(self, formats): @@ -296,10 +286,7 @@ class PRoutesCommand(object): if fmt not in self.available_formats: invalid_formats.append(fmt) - msg = ( - 'You provided invalid formats %s, ' - 'Available formats are %s' - ) + msg = 'You provided invalid formats %s, ' 'Available formats are %s' if invalid_formats: msg = msg % (invalid_formats, self.available_formats) @@ -321,6 +308,7 @@ class PRoutesCommand(object): def _get_mapper(self, registry): from pyramid.config import Configurator + config = Configurator(registry=registry) return config.get_routes_mapper() @@ -361,25 +349,29 @@ class PRoutesCommand(object): if len(routes) == 0: return 0 - mapped_routes = [{ - 'name': 'Name', - 'pattern': 'Pattern', - 'view': 'View', - 'method': 'Method' - },{ - 'name': '----', - 'pattern': '-------', - 'view': '----', - 'method': '------' - }] + mapped_routes = [ + { + 'name': 'Name', + 'pattern': 'Pattern', + 'view': 'View', + 'method': 'Method', + }, + { + 'name': '----', + 'pattern': '-------', + 'view': '----', + 'method': '------', + }, + ] for route in routes: route_data = get_route_data(route, registry) for name, pattern, view, method in route_data: if self.args.glob: - match = (fnmatch.fnmatch(name, self.args.glob) or - fnmatch.fnmatch(pattern, self.args.glob)) + match = fnmatch.fnmatch( + name, self.args.glob + ) or fnmatch.fnmatch(pattern, self.args.glob) if not match: continue @@ -395,12 +387,14 @@ class PRoutesCommand(object): if len(method) > max_method: max_method = len(method) - mapped_routes.append({ - 'name': name, - 'pattern': pattern, - 'view': view, - 'method': method - }) + mapped_routes.append( + { + 'name': name, + 'pattern': pattern, + 'view': view, + 'method': method, + } + ) fmt = _get_print_format( self.column_format, max_name, max_pattern, max_view, max_method diff --git a/src/pyramid/scripts/pserve.py b/src/pyramid/scripts/pserve.py index 8ee6e1467..581479d65 100644 --- a/src/pyramid/scripts/pserve.py +++ b/src/pyramid/scripts/pserve.py @@ -46,67 +46,86 @@ class PServeCommand(object): parser = argparse.ArgumentParser( description=textwrap.dedent(description), formatter_class=argparse.RawDescriptionHelpFormatter, - ) + ) parser.add_argument( - '-n', '--app-name', + '-n', + '--app-name', dest='app_name', metavar='NAME', - help="Load the named application (default main)") + help="Load the named application (default main)", + ) parser.add_argument( - '-s', '--server', + '-s', + '--server', dest='server', metavar='SERVER_TYPE', - help="Use the named server.") + help="Use the named server.", + ) parser.add_argument( '--server-name', dest='server_name', metavar='SECTION_NAME', - help=("Use the named server as defined in the configuration file " - "(default: main)")) + help=( + "Use the named server as defined in the configuration file " + "(default: main)" + ), + ) parser.add_argument( '--reload', dest='reload', action='store_true', - help="Use auto-restart file monitor") + help="Use auto-restart file monitor", + ) parser.add_argument( '--reload-interval', dest='reload_interval', default=1, - help=("Seconds between checking files (low number can cause " - "significant CPU usage)")) + help=( + "Seconds between checking files (low number can cause " + "significant CPU usage)" + ), + ) parser.add_argument( - '-b', '--browser', + '-b', + '--browser', dest='browser', action='store_true', - help=("Open a web browser to the server url. The server url is " - "determined from the 'open_url' setting in the 'pserve' " - "section of the configuration file.")) + help=( + "Open a web browser to the server url. The server url is " + "determined from the 'open_url' setting in the 'pserve' " + "section of the configuration file." + ), + ) parser.add_argument( - '-v', '--verbose', + '-v', + '--verbose', default=default_verbosity, dest='verbose', action='count', - help="Set verbose level (default " + str(default_verbosity) + ")") + help="Set verbose level (default " + str(default_verbosity) + ")", + ) parser.add_argument( - '-q', '--quiet', + '-q', + '--quiet', action='store_const', const=0, dest='verbose', - help="Suppress verbose output") + help="Suppress verbose output", + ) parser.add_argument( 'config_uri', nargs='?', default=None, help='The URI to the configuration file.', - ) + ) parser.add_argument( 'config_vars', nargs='*', default=(), help="Variables required by the config file. For example, " - "`http_port=%%(http_port)s` would expect `http_port=8080` to be " - "passed here.", - ) + "`http_port=%%(http_port)s` would expect `http_port=8080` to be " + "passed here.", + ) _get_config_loader = staticmethod(get_config_loader) # for testing @@ -187,18 +206,23 @@ class PServeCommand(object): if not url: url = self.guess_server_url( - server_loader, server_name, config_vars) + server_loader, server_name, config_vars + ) if not url: - self.out('WARNING: could not determine the server\'s url to ' - 'open the browser. To fix this set the "open_url" ' - 'setting in the [pserve] section of the ' - 'configuration file.') + self.out( + 'WARNING: could not determine the server\'s url to ' + 'open the browser. To fix this set the "open_url" ' + 'setting in the [pserve] section of the ' + 'configuration file.' + ) else: + def open_browser(): time.sleep(1) webbrowser.open(url) + t = threading.Thread(target=open_browser) t.setDaemon(True) t.start() @@ -210,7 +234,7 @@ class PServeCommand(object): 'pyramid.scripts.pserve.main', reload_interval=int(self.args.reload_interval), verbose=self.args.verbose, - worker_kwargs=self.worker_kwargs + worker_kwargs=self.worker_kwargs, ) return 0 @@ -250,6 +274,7 @@ class PServeCommand(object): # For paste.deploy server instantiation (egg:pyramid#wsgiref) def wsgiref_server_runner(wsgi_app, global_conf, **kw): # pragma: no cover from wsgiref.simple_server import make_server + host = kw.get('host', '0.0.0.0') port = int(kw.get('port', 8080)) server = make_server(host, port, wsgi_app) @@ -259,11 +284,18 @@ def wsgiref_server_runner(wsgi_app, global_conf, **kw): # pragma: no cover # For paste.deploy server instantiation (egg:pyramid#cherrypy) def cherrypy_server_runner( - app, global_conf=None, host='127.0.0.1', port=None, - ssl_pem=None, protocol_version=None, numthreads=None, - server_name=None, max=None, request_queue_size=None, - timeout=None - ): # pragma: no cover + app, + global_conf=None, + host='127.0.0.1', + port=None, + ssl_pem=None, + protocol_version=None, + numthreads=None, + server_name=None, + max=None, + request_queue_size=None, + timeout=None, +): # pragma: no cover """ Entry point for CherryPy's WSGI server @@ -346,8 +378,7 @@ def cherrypy_server_runner( except ImportError: from cherrypy.wsgiserver import CherryPyWSGIServer as WSGIServer - server = WSGIServer(bind_addr, app, - server_name=server_name, **kwargs) + server = WSGIServer(bind_addr, app, server_name=server_name, **kwargs) if ssl_pem is not None: if PY2: server.ssl_certificate = server.ssl_private_key = ssl_pem @@ -368,8 +399,10 @@ def cherrypy_server_runner( try: protocol = is_ssl and 'https' or 'http' if host == '0.0.0.0': - print('serving on 0.0.0.0:%s view at %s://127.0.0.1:%s' % - (port, protocol, port)) + print( + 'serving on 0.0.0.0:%s view at %s://127.0.0.1:%s' + % (port, protocol, port) + ) else: print('serving on %s://%s:%s' % (protocol, host, port)) server.start() diff --git a/src/pyramid/scripts/pshell.py b/src/pyramid/scripts/pshell.py index 4898eb39f..e63114d18 100644 --- a/src/pyramid/scripts/pshell.py +++ b/src/pyramid/scripts/pshell.py @@ -16,6 +16,7 @@ from pyramid.settings import aslist from pyramid.scripts.common import get_config_loader from pyramid.scripts.common import parse_vars + def main(argv=sys.argv, quiet=False): command = PShellCommand(argv, quiet) return command.run() @@ -49,38 +50,52 @@ class PShellCommand(object): parser = argparse.ArgumentParser( description=textwrap.dedent(description), formatter_class=argparse.RawDescriptionHelpFormatter, - ) - parser.add_argument('-p', '--python-shell', - action='store', - dest='python_shell', - default='', - help=('Select the shell to use. A list of possible ' - 'shells is available using the --list-shells ' - 'option.')) - parser.add_argument('-l', '--list-shells', - dest='list', - action='store_true', - help='List all available shells.') - parser.add_argument('--setup', - dest='setup', - help=("A callable that will be passed the environment " - "before it is made available to the shell. This " - "option will override the 'setup' key in the " - "[pshell] ini section.")) - parser.add_argument('config_uri', - nargs='?', - default=None, - help='The URI to the configuration file.') + ) + parser.add_argument( + '-p', + '--python-shell', + action='store', + dest='python_shell', + default='', + help=( + 'Select the shell to use. A list of possible ' + 'shells is available using the --list-shells ' + 'option.' + ), + ) + parser.add_argument( + '-l', + '--list-shells', + dest='list', + action='store_true', + help='List all available shells.', + ) + parser.add_argument( + '--setup', + dest='setup', + help=( + "A callable that will be passed the environment " + "before it is made available to the shell. This " + "option will override the 'setup' key in the " + "[pshell] ini section." + ), + ) + parser.add_argument( + 'config_uri', + nargs='?', + default=None, + help='The URI to the configuration file.', + ) parser.add_argument( 'config_vars', nargs='*', default=(), help="Variables required by the config file. For example, " - "`http_port=%%(http_port)s` would expect `http_port=8080` to be " - "passed here.", - ) + "`http_port=%%(http_port)s` would expect `http_port=8080` to be " + "passed here.", + ) - default_runner = python_shell_runner # testing + default_runner = python_shell_runner # testing loaded_objects = {} object_help = {} @@ -107,7 +122,7 @@ class PShellCommand(object): self.loaded_objects[k] = self.resolver.maybe_resolve(v) self.object_help[k] = v - def out(self, msg): # pragma: no cover + def out(self, msg): # pragma: no cover if not self.quiet: print(msg) @@ -152,8 +167,9 @@ class PShellCommand(object): env_help['root'] = 'Root of the default resource tree.' env_help['registry'] = 'Active Pyramid registry.' env_help['request'] = 'Active request object.' - env_help['root_factory'] = ( - 'Default root factory used to create `root`.') + env_help[ + 'root_factory' + ] = 'Default root factory used to create `root`.' # load the pshell section of the ini file env.update(self.loaded_objects) @@ -236,6 +252,7 @@ class PShellCommand(object): # by default prioritize all shells above python preferred_shells = [k for k in shells.keys() if k != 'python'] max_weight = len(preferred_shells) + def order(x): # invert weight to reverse sort the list # (closer to the front is higher priority) @@ -243,6 +260,7 @@ class PShellCommand(object): return preferred_shells.index(x[0].lower()) - max_weight except ValueError: return 1 + sorted_shells = sorted(shells.items(), key=order) if len(sorted_shells) > 0: @@ -266,5 +284,5 @@ class PShellCommand(object): return shell -if __name__ == '__main__': # pragma: no cover +if __name__ == '__main__': # pragma: no cover sys.exit(main() or 0) diff --git a/src/pyramid/scripts/ptweens.py b/src/pyramid/scripts/ptweens.py index d5cbebe12..e6a5c5ac7 100644 --- a/src/pyramid/scripts/ptweens.py +++ b/src/pyramid/scripts/ptweens.py @@ -10,10 +10,12 @@ from pyramid.paster import bootstrap from pyramid.paster import setup_logging from pyramid.scripts.common import parse_vars + def main(argv=sys.argv, quiet=False): command = PTweensCommand(argv, quiet) return command.run() + class PTweensCommand(object): description = """\ Print all implicit and explicit tween objects used by a Pyramid @@ -31,25 +33,27 @@ class PTweensCommand(object): parser = argparse.ArgumentParser( description=textwrap.dedent(description), formatter_class=argparse.RawDescriptionHelpFormatter, - ) + ) - parser.add_argument('config_uri', - nargs='?', - default=None, - help='The URI to the configuration file.') + parser.add_argument( + 'config_uri', + nargs='?', + default=None, + help='The URI to the configuration file.', + ) parser.add_argument( 'config_vars', nargs='*', default=(), help="Variables required by the config file. For example, " - "`http_port=%%(http_port)s` would expect `http_port=8080` to be " - "passed here.", - ) + "`http_port=%%(http_port)s` would expect `http_port=8080` to be " + "passed here.", + ) stdout = sys.stdout - bootstrap = staticmethod(bootstrap) # testing - setup_logging = staticmethod(setup_logging) # testing + bootstrap = staticmethod(bootstrap) # testing + setup_logging = staticmethod(setup_logging) # testing def __init__(self, argv, quiet=False): self.quiet = quiet @@ -57,10 +61,11 @@ class PTweensCommand(object): def _get_tweens(self, registry): from pyramid.config import Configurator + config = Configurator(registry=registry) return config.registry.queryUtility(ITweens) - def out(self, msg): # pragma: no cover + def out(self, msg): # pragma: no cover if not self.quiet: print(msg) @@ -86,8 +91,10 @@ class PTweensCommand(object): if tweens is not None: explicit = tweens.explicit if explicit: - self.out('"pyramid.tweens" config value set ' - '(explicitly ordered tweens used)') + self.out( + '"pyramid.tweens" config value set ' + '(explicitly ordered tweens used)' + ) self.out('') self.out('Explicit Tween Chain (used)') self.out('') @@ -97,13 +104,16 @@ class PTweensCommand(object): self.out('') self.show_chain(tweens.implicit()) else: - self.out('"pyramid.tweens" config value NOT set ' - '(implicitly ordered tweens used)') + self.out( + '"pyramid.tweens" config value NOT set ' + '(implicitly ordered tweens used)' + ) self.out('') self.out('Implicit Tween Chain') self.out('') self.show_chain(tweens.implicit()) return 0 -if __name__ == '__main__': # pragma: no cover + +if __name__ == '__main__': # pragma: no cover sys.exit(main() or 0) diff --git a/src/pyramid/scripts/pviews.py b/src/pyramid/scripts/pviews.py index c0df2f078..891dc4709 100644 --- a/src/pyramid/scripts/pviews.py +++ b/src/pyramid/scripts/pviews.py @@ -9,10 +9,12 @@ from pyramid.request import Request from pyramid.scripts.common import parse_vars from pyramid.view import _find_views + def main(argv=sys.argv, quiet=False): command = PViewsCommand(argv, quiet) return command.run() + class PViewsCommand(object): description = """\ Print, for a given URL, the views that might match. Underneath each @@ -31,38 +33,41 @@ class PViewsCommand(object): parser = argparse.ArgumentParser( description=textwrap.dedent(description), formatter_class=argparse.RawDescriptionHelpFormatter, - ) + ) - parser.add_argument('config_uri', - nargs='?', - default=None, - help='The URI to the configuration file.') + parser.add_argument( + 'config_uri', + nargs='?', + default=None, + help='The URI to the configuration file.', + ) - parser.add_argument('url', - nargs='?', - default=None, - help='The path info portion of the URL.') + parser.add_argument( + 'url', + nargs='?', + default=None, + help='The path info portion of the URL.', + ) parser.add_argument( 'config_vars', nargs='*', default=(), help="Variables required by the config file. For example, " - "`http_port=%%(http_port)s` would expect `http_port=8080` to be " - "passed here.", - ) + "`http_port=%%(http_port)s` would expect `http_port=8080` to be " + "passed here.", + ) - - bootstrap = staticmethod(bootstrap) # testing - setup_logging = staticmethod(setup_logging) # testing + bootstrap = staticmethod(bootstrap) # testing + setup_logging = staticmethod(setup_logging) # testing def __init__(self, argv, quiet=False): self.quiet = quiet self.args = self.parser.parse_args(argv[1:]) - def out(self, msg): # pragma: no cover + def out(self, msg): # pragma: no cover if not self.quiet: print(msg) - + def _find_multi_routes(self, mapper, request): infos = [] path = request.environ['PATH_INFO'] @@ -70,7 +75,7 @@ class PViewsCommand(object): for route in mapper.get_routes(): match = route.match(path) if match is not None: - info = {'match':match, 'route':route} + info = {'match': match, 'route': route} infos.append(info) return infos @@ -99,22 +104,17 @@ class PViewsCommand(object): @implementer(IMultiView) class RoutesMultiView(object): - def __init__(self, infos, context_iface, root_factory, request): self.views = [] for info in infos: match, route = info['match'], info['route'] if route is not None: request_iface = registry.queryUtility( - IRouteRequest, - name=route.name, - default=IRequest) + IRouteRequest, name=route.name, default=IRequest + ) views = _find_views( - request.registry, - request_iface, - context_iface, - '' - ) + request.registry, request_iface, context_iface, '' + ) if not views: continue view = views[0] @@ -148,9 +148,8 @@ class PViewsCommand(object): attrs['matched_route'] = route request.environ['bfg.routes.matchdict'] = match request_iface = registry.queryUtility( - IRouteRequest, - name=route.name, - default=IRequest) + IRouteRequest, name=route.name, default=IRequest + ) root_factory = route.factory or root_factory if len(infos) > 1: routes_multiview = infos @@ -171,11 +170,8 @@ class PViewsCommand(object): context_iface = providedBy(context) if routes_multiview is None: views = _find_views( - request.registry, - request_iface, - context_iface, - view_name, - ) + request.registry, request_iface, context_iface, view_name + ) if views: view = views[0] else: @@ -186,11 +182,8 @@ class PViewsCommand(object): # routes are not registered with a view name if view is None: views = _find_views( - request.registry, - request_iface, - context_iface, - '', - ) + request.registry, request_iface, context_iface, '' + ) if views: view = views[0] else: @@ -285,5 +278,6 @@ class PViewsCommand(object): env['closer']() return 0 -if __name__ == '__main__': # pragma: no cover + +if __name__ == '__main__': # pragma: no cover sys.exit(main() or 0) diff --git a/src/pyramid/security.py b/src/pyramid/security.py index 0bdca090b..9e256f73c 100644 --- a/src/pyramid/security.py +++ b/src/pyramid/security.py @@ -7,7 +7,7 @@ from pyramid.interfaces import ( ISecuredView, IView, IViewClassifier, - ) +) from pyramid.compat import map_ from pyramid.threadlocal import get_current_registry @@ -17,6 +17,7 @@ Authenticated = 'system.Authenticated' Allow = 'Allow' Deny = 'Deny' + class AllPermissionsList(object): """ Stand in 'permission list' to represent all permissions """ @@ -29,22 +30,26 @@ class AllPermissionsList(object): def __eq__(self, other): return isinstance(other, self.__class__) + ALL_PERMISSIONS = AllPermissionsList() DENY_ALL = (Deny, Everyone, ALL_PERMISSIONS) NO_PERMISSION_REQUIRED = '__no_permission_required__' + def _get_registry(request): try: reg = request.registry except AttributeError: - reg = get_current_registry() # b/c + reg = get_current_registry() # b/c return reg + def _get_authentication_policy(request): registry = _get_registry(request) return registry.queryUtility(IAuthenticationPolicy) + def has_permission(permission, context, request): """ A function that calls :meth:`pyramid.request.Request.has_permission` @@ -56,15 +61,16 @@ def has_permission(permission, context, request): .. versionchanged:: 1.5a3 If context is None, then attempt to use the context attribute of self; if not set, then the AttributeError is propagated. - """ + """ return request.has_permission(permission, context) + deprecated( 'has_permission', 'As of Pyramid 1.5 the "pyramid.security.has_permission" API is now ' 'deprecated. It will be removed in Pyramid 1.8. Use the ' - '"has_permission" method of the Pyramid request instead.' - ) + '"has_permission" method of the Pyramid request instead.', +) def authenticated_userid(request): @@ -74,15 +80,17 @@ def authenticated_userid(request): .. deprecated:: 1.5 Use :attr:`pyramid.request.Request.authenticated_userid` instead. - """ + """ return request.authenticated_userid + deprecated( 'authenticated_userid', 'As of Pyramid 1.5 the "pyramid.security.authenticated_userid" API is now ' 'deprecated. It will be removed in Pyramid 1.8. Use the ' - '"authenticated_userid" attribute of the Pyramid request instead.' - ) + '"authenticated_userid" attribute of the Pyramid request instead.', +) + def unauthenticated_userid(request): """ @@ -91,15 +99,17 @@ def unauthenticated_userid(request): .. deprecated:: 1.5 Use :attr:`pyramid.request.Request.unauthenticated_userid` instead. - """ + """ return request.unauthenticated_userid + deprecated( 'unauthenticated_userid', 'As of Pyramid 1.5 the "pyramid.security.unauthenticated_userid" API is ' 'now deprecated. It will be removed in Pyramid 1.8. Use the ' - '"unauthenticated_userid" attribute of the Pyramid request instead.' - ) + '"unauthenticated_userid" attribute of the Pyramid request instead.', +) + def effective_principals(request): """ @@ -108,15 +118,17 @@ def effective_principals(request): .. deprecated:: 1.5 Use :attr:`pyramid.request.Request.effective_principals` instead. - """ + """ return request.effective_principals + deprecated( 'effective_principals', 'As of Pyramid 1.5 the "pyramid.security.effective_principals" API is ' 'now deprecated. It will be removed in Pyramid 1.8. Use the ' - '"effective_principals" attribute of the Pyramid request instead.' - ) + '"effective_principals" attribute of the Pyramid request instead.', +) + def remember(request, userid, **kw): """ @@ -154,6 +166,7 @@ def remember(request, userid, **kw): return [] return policy.remember(request, userid, **kw) + def forget(request): """ Return a sequence of header tuples (e.g. ``[('Set-Cookie', @@ -172,12 +185,13 @@ def forget(request): If no :term:`authentication policy` is in use, this function will always return an empty sequence. - """ + """ policy = _get_authentication_policy(request) if policy is None: return [] return policy.forget(request) + def principals_allowed_by_permission(context, permission): """ Provided a ``context`` (a resource object), and a ``permission`` (a string or unicode object), if an :term:`authorization policy` is @@ -201,6 +215,7 @@ def principals_allowed_by_permission(context, permission): return [Everyone] return policy.principals_allowed_by_permission(context, permission) + def view_execution_permitted(context, request, name=''): """ If the view specified by ``context`` and ``name`` is protected by a :term:`permission`, check the permission associated with the @@ -222,12 +237,15 @@ def view_execution_permitted(context, request, name=''): if view is None: view = reg.adapters.lookup(provides, IView, name=name) if view is None: - raise TypeError('No registered view satisfies the constraints. ' - 'It would not make sense to claim that this view ' - '"is" or "is not" permitted.') + raise TypeError( + 'No registered view satisfies the constraints. ' + 'It would not make sense to claim that this view ' + '"is" or "is not" permitted.' + ) return Allowed( - 'Allowed: view name %r in context %r (no permission defined)' % - (name, context)) + 'Allowed: view name %r in context %r (no permission defined)' + % (name, context) + ) return view.__permitted__(context, request) @@ -255,9 +273,12 @@ class PermitsResult(int): return self.msg def __repr__(self): - return '<%s instance at %s with msg %r>' % (self.__class__.__name__, - id(self), - self.msg) + return '<%s instance at %s with msg %r>' % ( + self.__class__.__name__, + id(self), + self.msg, + ) + class Denied(PermitsResult): """ @@ -268,8 +289,10 @@ class Denied(PermitsResult): the deny. """ + boolval = 0 + class Allowed(PermitsResult): """ An instance of ``Allowed`` is returned when a security-related @@ -279,8 +302,10 @@ class Allowed(PermitsResult): the allow. """ + boolval = 1 + class ACLPermitsResult(PermitsResult): def __new__(cls, ace, acl, permission, principals, context): """ @@ -294,17 +319,12 @@ class ACLPermitsResult(PermitsResult): searched. """ - fmt = ('%s permission %r via ACE %r in ACL %r on context %r for ' - 'principals %r') + fmt = ( + '%s permission %r via ACE %r in ACL %r on context %r for ' + 'principals %r' + ) inst = PermitsResult.__new__( - cls, - fmt, - cls.__name__, - permission, - ace, - acl, - context, - principals, + cls, fmt, cls.__name__, permission, ace, acl, context, principals ) inst.permission = permission inst.ace = ace @@ -313,6 +333,7 @@ class ACLPermitsResult(PermitsResult): inst.context = context return inst + class ACLDenied(ACLPermitsResult, Denied): """ An instance of ``ACLDenied`` is a specialization of @@ -326,6 +347,7 @@ class ACLDenied(ACLPermitsResult, Denied): """ + class ACLAllowed(ACLPermitsResult, Allowed): """ An instance of ``ACLAllowed`` is a specialization of @@ -339,8 +361,8 @@ class ACLAllowed(ACLPermitsResult, Allowed): """ -class AuthenticationAPIMixin(object): +class AuthenticationAPIMixin(object): def _get_authentication_policy(self): reg = _get_registry(self) return reg.queryUtility(IAuthenticationPolicy) @@ -389,8 +411,8 @@ class AuthenticationAPIMixin(object): return [Everyone] return policy.effective_principals(self) -class AuthorizationAPIMixin(object): +class AuthorizationAPIMixin(object): def has_permission(self, permission, context=None): """ Given a permission and an optional context, returns an instance of :data:`pyramid.security.Allowed` if the permission is granted to this @@ -421,7 +443,9 @@ class AuthorizationAPIMixin(object): return Allowed('No authentication policy in use.') authz_policy = reg.queryUtility(IAuthorizationPolicy) if authz_policy is None: - raise ValueError('Authentication policy registered without ' - 'authorization policy') # should never happen + raise ValueError( + 'Authentication policy registered without ' + 'authorization policy' + ) # should never happen principals = authn_policy.effective_principals(self) return authz_policy.permits(context, principals, permission) diff --git a/src/pyramid/session.py b/src/pyramid/session.py index b953fa184..9d4ef6dbb 100644 --- a/src/pyramid/session.py +++ b/src/pyramid/session.py @@ -9,22 +9,10 @@ import warnings from zope.deprecation import deprecated from zope.interface import implementer -from webob.cookies import ( - JSONSerializer, - SignedSerializer, -) +from webob.cookies import JSONSerializer, SignedSerializer -from pyramid.compat import ( - pickle, - PY2, - text_, - bytes_, - native_, - ) -from pyramid.csrf import ( - check_csrf_origin, - check_csrf_token, -) +from pyramid.compat import pickle, PY2, text_, bytes_, native_ +from pyramid.csrf import check_csrf_origin, check_csrf_token from pyramid.interfaces import ISession from pyramid.util import strings_differ @@ -33,25 +21,31 @@ from pyramid.util import strings_differ def manage_accessed(wrapped): """ Decorator which causes a cookie to be renewed when an accessor method is called.""" + def accessed(session, *arg, **kw): session.accessed = now = int(time.time()) if session._reissue_time is not None: if now - session.renewed > session._reissue_time: session.changed() return wrapped(session, *arg, **kw) + accessed.__doc__ = wrapped.__doc__ return accessed + def manage_changed(wrapped): """ Decorator which causes a cookie to be set when a setter method is called.""" + def changed(session, *arg, **kw): session.accessed = int(time.time()) session.changed() return wrapped(session, *arg, **kw) + changed.__doc__ = wrapped.__doc__ return changed + def signed_serialize(data, secret): """ Serialize any pickleable structure (``data``) and sign it using the ``secret`` (must be a string). Return the @@ -82,6 +76,7 @@ def signed_serialize(data, secret): sig = hmac.new(secret, pickled, hashlib.sha1).hexdigest() return sig + native_(base64.b64encode(pickled)) + deprecated( 'signed_serialize', 'This function will be removed in Pyramid 2.0. It is using pickle-based ' @@ -89,6 +84,7 @@ deprecated( 'attacks.', ) + def signed_deserialize(serialized, secret, hmac=hmac): """ Deserialize the value returned from ``signed_serialize``. If the value cannot be deserialized for any reason, a @@ -111,8 +107,10 @@ def signed_deserialize(serialized, secret, hmac=hmac): """ # hmac parameterized only for unit tests try: - input_sig, pickled = (bytes_(serialized[:40]), - base64.b64decode(bytes_(serialized[40:]))) + input_sig, pickled = ( + bytes_(serialized[:40]), + base64.b64decode(bytes_(serialized[40:])), + ) except (binascii.Error, TypeError) as e: # Badly formed data can make base64 die raise ValueError('Badly formed base64 data: %s' % e) @@ -131,6 +129,7 @@ def signed_deserialize(serialized, secret, hmac=hmac): return pickle.loads(pickled) + deprecated( 'signed_deserialize', 'This function will be removed in Pyramid 2.0. It is using pickle-based ' @@ -149,6 +148,7 @@ class PickleSerializer(object): Defaults to :attr:`pickle.HIGHEST_PROTOCOL`. """ + def __init__(self, protocol=pickle.HIGHEST_PROTOCOL): self.protocol = protocol @@ -180,7 +180,7 @@ def BaseCookieSessionFactory( timeout=1200, reissue_time=0, set_on_exception=True, - ): +): """ Configure a :term:`session factory` which will provide cookie-based sessions. The return value of this function is a :term:`session factory`, @@ -280,7 +280,9 @@ def BaseCookieSessionFactory( _cookie_samesite = samesite _cookie_on_exception = set_on_exception _timeout = timeout if timeout is None else int(timeout) - _reissue_time = reissue_time if reissue_time is None else int(reissue_time) + _reissue_time = ( + reissue_time if reissue_time is None else int(reissue_time) + ) # dirty flag _dirty = False @@ -330,13 +332,15 @@ def BaseCookieSessionFactory( def changed(self): if not self._dirty: self._dirty = True + def set_cookie_callback(request, response): self._set_cookie(response) - self.request = None # explicitly break cycle for gc + self.request = None # explicitly break cycle for gc + self.request.add_response_callback(set_cookie_callback) def invalidate(self): - self.clear() # XXX probably needs to unset cookie + self.clear() # XXX probably needs to unset cookie # non-modifying dictionary methods get = manage_accessed(dict.get) @@ -398,16 +402,18 @@ def BaseCookieSessionFactory( def _set_cookie(self, response): if not self._cookie_on_exception: exception = getattr(self.request, 'exception', None) - if exception is not None: # dont set a cookie during exceptions + if ( + exception is not None + ): # dont set a cookie during exceptions return False - cookieval = native_(serializer.dumps( - (self.accessed, self.created, dict(self)) - )) + cookieval = native_( + serializer.dumps((self.accessed, self.created, dict(self))) + ) if len(cookieval) > 4064: raise ValueError( - 'Cookie value is too long to store (%s bytes)' % - len(cookieval) - ) + 'Cookie value is too long to store (%s bytes)' + % len(cookieval) + ) response.set_cookie( self._cookie_name, value=cookieval, @@ -417,7 +423,7 @@ def BaseCookieSessionFactory( secure=self._cookie_secure, httponly=self._cookie_httponly, samesite=self._cookie_samesite, - ) + ) return True return CookieSession @@ -436,7 +442,7 @@ def UnencryptedCookieSessionFactoryConfig( cookie_on_exception=True, signed_serialize=signed_serialize, signed_deserialize=signed_deserialize, - ): +): """ .. deprecated:: 1.5 Use :func:`pyramid.session.SignedCookieSessionFactory` instead. @@ -530,18 +536,19 @@ def UnencryptedCookieSessionFactoryConfig( httponly=cookie_httponly, samesite=cookie_samesite, timeout=timeout, - reissue_time=0, # to keep session.accessed == session.renewed + reissue_time=0, # to keep session.accessed == session.renewed set_on_exception=cookie_on_exception, ) + deprecated( 'UnencryptedCookieSessionFactoryConfig', 'The UnencryptedCookieSessionFactoryConfig callable is deprecated as of ' 'Pyramid 1.5. Use ``pyramid.session.SignedCookieSessionFactory`` instead.' ' Caveat: Cookies generated using SignedCookieSessionFactory are not ' 'compatible with cookies generated using UnencryptedCookieSessionFactory, ' - 'so existing user session data will be destroyed if you switch to it.' - ) + 'so existing user session data will be destroyed if you switch to it.', +) def SignedCookieSessionFactory( @@ -559,7 +566,7 @@ def SignedCookieSessionFactory( hashalg='sha512', salt='pyramid.session.', serializer=None, - ): +): """ .. versionadded:: 1.5 @@ -681,11 +688,8 @@ def SignedCookieSessionFactory( ) signed_serializer = SignedSerializer( - secret, - salt, - hashalg, - serializer=serializer, - ) + secret, salt, hashalg, serializer=serializer + ) return BaseCookieSessionFactory( signed_serializer, @@ -701,12 +705,17 @@ def SignedCookieSessionFactory( set_on_exception=set_on_exception, ) + check_csrf_origin = check_csrf_origin # api -deprecated('check_csrf_origin', - 'pyramid.session.check_csrf_origin is deprecated as of Pyramid ' - '1.9. Use pyramid.csrf.check_csrf_origin instead.') +deprecated( + 'check_csrf_origin', + 'pyramid.session.check_csrf_origin is deprecated as of Pyramid ' + '1.9. Use pyramid.csrf.check_csrf_origin instead.', +) check_csrf_token = check_csrf_token # api -deprecated('check_csrf_token', - 'pyramid.session.check_csrf_token is deprecated as of Pyramid ' - '1.9. Use pyramid.csrf.check_csrf_token instead.') +deprecated( + 'check_csrf_token', + 'pyramid.session.check_csrf_token is deprecated as of Pyramid ' + '1.9. Use pyramid.csrf.check_csrf_token instead.', +) diff --git a/src/pyramid/settings.py b/src/pyramid/settings.py index 8a498d572..af9433840 100644 --- a/src/pyramid/settings.py +++ b/src/pyramid/settings.py @@ -3,6 +3,7 @@ from pyramid.compat import string_types truthy = frozenset(('t', 'true', 'y', 'yes', 'on', '1')) falsey = frozenset(('f', 'false', 'n', 'no', 'off', '0')) + def asbool(s): """ Return the boolean value ``True`` if the case-lowered value of string input ``s`` is a :term:`truthy string`. If ``s`` is already one of the @@ -14,11 +15,13 @@ def asbool(s): s = str(s).strip() return s.lower() in truthy + def aslist_cronly(value): if isinstance(value, string_types): value = filter(None, [x.strip() for x in value.splitlines()]) return list(value) + def aslist(value, flatten=True): """ Return a list of strings, separating the input based on newlines and, if flatten=True (the default), also split on spaces within diff --git a/src/pyramid/static.py b/src/pyramid/static.py index 70fdf877b..58ad97a46 100644 --- a/src/pyramid/static.py +++ b/src/pyramid/static.py @@ -2,47 +2,25 @@ import json import os -from os.path import ( - getmtime, - normcase, - normpath, - join, - isdir, - exists, - ) - -from pkg_resources import ( - resource_exists, - resource_filename, - resource_isdir, - ) - -from pyramid.asset import ( - abspath_from_asset_spec, - resolve_asset_spec, -) - -from pyramid.compat import ( - lru_cache, - text_, -) - -from pyramid.httpexceptions import ( - HTTPNotFound, - HTTPMovedPermanently, - ) +from os.path import getmtime, normcase, normpath, join, isdir, exists + +from pkg_resources import resource_exists, resource_filename, resource_isdir + +from pyramid.asset import abspath_from_asset_spec, resolve_asset_spec + +from pyramid.compat import lru_cache, text_ + +from pyramid.httpexceptions import HTTPNotFound, HTTPMovedPermanently from pyramid.path import caller_package -from pyramid.response import ( - _guess_type, - FileResponse, -) +from pyramid.response import _guess_type, FileResponse from pyramid.traversal import traversal_path_info slash = text_('/') + class static_view(object): """ An instance of this class is a callable which can act as a :app:`Pyramid` :term:`view callable`; this view will serve @@ -88,8 +66,14 @@ class static_view(object): to override the assets it contains. """ - def __init__(self, root_dir, cache_max_age=3600, package_name=None, - use_subpath=False, index='index.html'): + def __init__( + self, + root_dir, + cache_max_age=3600, + package_name=None, + use_subpath=False, + index='index.html', + ): # package_name is for bw compat; it is preferred to pass in a # package-relative path as root_dir # (e.g. ``anotherpackage:foo/static``). @@ -113,20 +97,21 @@ class static_view(object): if path is None: raise HTTPNotFound('Out of bounds: %s' % request.url) - if self.package_name: # package resource + if self.package_name: # package resource resource_path = '%s/%s' % (self.docroot.rstrip('/'), path) if resource_isdir(self.package_name, resource_path): if not request.path_url.endswith('/'): self.add_slash_redirect(request) resource_path = '%s/%s' % ( - resource_path.rstrip('/'), self.index + resource_path.rstrip('/'), + self.index, ) if not resource_exists(self.package_name, resource_path): raise HTTPNotFound(request.url) filepath = resource_filename(self.package_name, resource_path) - else: # filesystem file + else: # filesystem file # os.path.normpath converts / to \ on windows filepath = normcase(normpath(join(self.norm_docroot, path))) @@ -139,8 +124,12 @@ class static_view(object): content_type, content_encoding = _guess_type(filepath) return FileResponse( - filepath, request, self.cache_max_age, - content_type, content_encoding=None) + filepath, + request, + self.cache_max_age, + content_type, + content_encoding=None, + ) def add_slash_redirect(self, request): url = request.path_url + '/' @@ -149,14 +138,19 @@ class static_view(object): url = url + '?' + qs raise HTTPMovedPermanently(url) + _seps = set(['/', os.sep]) + + def _contains_slash(item): for sep in _seps: if sep in item: return True + _has_insecure_pathelement = set(['..', '.', '']).intersection + @lru_cache(1000) def _secure_path(path_tuple): if _has_insecure_pathelement(path_tuple): @@ -166,9 +160,10 @@ def _secure_path(path_tuple): return None if any([_contains_slash(item) for item in path_tuple]): return None - encoded = slash.join(path_tuple) # will be unicode + encoded = slash.join(path_tuple) # will be unicode return encoded + class QueryStringCacheBuster(object): """ An implementation of :class:`~pyramid.interfaces.ICacheBuster` which adds @@ -182,6 +177,7 @@ class QueryStringCacheBuster(object): .. versionadded:: 1.6 """ + def __init__(self, param='x'): self.param = param @@ -194,6 +190,7 @@ class QueryStringCacheBuster(object): kw['_query'] = tuple(query) + ((self.param, token),) return subpath, kw + class QueryStringConstantCacheBuster(QueryStringCacheBuster): """ An implementation of :class:`~pyramid.interfaces.ICacheBuster` which adds @@ -207,6 +204,7 @@ class QueryStringConstantCacheBuster(QueryStringCacheBuster): .. versionadded:: 1.6 """ + def __init__(self, token, param='x'): super(QueryStringConstantCacheBuster, self).__init__(param=param) self._token = token @@ -214,6 +212,7 @@ class QueryStringConstantCacheBuster(QueryStringCacheBuster): def tokenize(self, request, subpath, kw): return self._token + class ManifestCacheBuster(object): """ An implementation of :class:`~pyramid.interfaces.ICacheBuster` which @@ -255,13 +254,15 @@ class ManifestCacheBuster(object): .. versionadded:: 1.6 """ - exists = staticmethod(exists) # testing - getmtime = staticmethod(getmtime) # testing + + exists = staticmethod(exists) # testing + getmtime = staticmethod(getmtime) # testing def __init__(self, manifest_spec, reload=False): package_name = caller_package().__name__ self.manifest_path = abspath_from_asset_spec( - manifest_spec, package_name) + manifest_spec, package_name + ) self.reload = reload self._mtime = None diff --git a/src/pyramid/testing.py b/src/pyramid/testing.py index 4986c0e27..e2549f0b9 100644 --- a/src/pyramid/testing.py +++ b/src/pyramid/testing.py @@ -2,22 +2,11 @@ import copy import os from contextlib import contextmanager -from zope.interface import ( - implementer, - alsoProvides, - ) +from zope.interface import implementer, alsoProvides -from pyramid.interfaces import ( - IRequest, - ISession, - ) +from pyramid.interfaces import IRequest, ISession -from pyramid.compat import ( - PY3, - PYPY, - class_types, - text_, - ) +from pyramid.compat import PY3, PYPY, class_types, text_ from pyramid.config import Configurator from pyramid.decorator import reify @@ -30,12 +19,9 @@ from pyramid.security import ( Everyone, AuthenticationAPIMixin, AuthorizationAPIMixin, - ) +) -from pyramid.threadlocal import ( - get_current_registry, - manager, - ) +from pyramid.threadlocal import get_current_registry, manager from pyramid.i18n import LocalizerRequestMixin from pyramid.request import CallbackMethodsMixin @@ -46,17 +32,27 @@ from pyramid.view import ViewMethodsMixin _marker = object() + class DummyRootFactory(object): __parent__ = None __name__ = None + def __init__(self, request): if 'bfg.routes.matchdict' in request: self.__dict__.update(request['bfg.routes.matchdict']) + class DummySecurityPolicy(object): """ A standin for both an IAuthentication and IAuthorization policy """ - def __init__(self, userid=None, groupids=(), permissive=True, - remember_result=None, forget_result=None): + + def __init__( + self, + userid=None, + groupids=(), + permissive=True, + remember_result=None, + forget_result=None, + ): self.userid = userid self.groupids = groupids self.permissive = permissive @@ -95,6 +91,7 @@ class DummySecurityPolicy(object): def principals_allowed_by_permission(self, context, permission): return self.effective_principals(None) + class DummyTemplateRenderer(object): """ An instance of this class is returned from @@ -103,6 +100,7 @@ class DummyTemplateRenderer(object): assertion which compares data passed to the renderer by the view function against expected key/value pairs. """ + def __init__(self, string_response=''): self._received = {} self._string_response = string_response @@ -113,9 +111,11 @@ class DummyTemplateRenderer(object): # source code, *everything* is an API! def _get_string_response(self): return self._string_response + def _set_string_response(self, response): self._string_response = response self._implementation.response = response + string_response = property(_get_string_response, _set_string_response) def implementation(self): @@ -151,19 +151,23 @@ class DummyTemplateRenderer(object): if myval is _marker: raise AssertionError( 'A value for key "%s" was not passed to the renderer' - % k) + % k + ) if myval != v: raise AssertionError( - '\nasserted value for %s: %r\nactual value: %r' % ( - k, v, myval)) + '\nasserted value for %s: %r\nactual value: %r' + % (k, v, myval) + ) return True class DummyResource: """ A dummy :app:`Pyramid` :term:`resource` object.""" - def __init__(self, __name__=None, __parent__=None, __provides__=None, - **kw): + + def __init__( + self, __name__=None, __parent__=None, __provides__=None, **kw + ): """ The resource's ``__name__`` attribute will be set to the value of the ``__name__`` argument, and the resource's ``__parent__`` attribute will be set to the value of the @@ -250,12 +254,15 @@ class DummyResource: inst.__parent__ = __parent__ return inst -DummyModel = DummyResource # b/w compat (forever) + +DummyModel = DummyResource # b/w compat (forever) + @implementer(ISession) class DummySession(dict): created = None new = True + def changed(self): pass @@ -286,6 +293,7 @@ class DummySession(dict): token = self.new_csrf_token() return token + @implementer(IRequest) class DummyRequest( URLMethodsMixin, @@ -295,7 +303,7 @@ class DummyRequest( AuthenticationAPIMixin, AuthorizationAPIMixin, ViewMethodsMixin, - ): +): """ A DummyRequest object (incompletely) imitates a :term:`request` object. The ``params``, ``environ``, ``headers``, ``path``, and @@ -322,6 +330,7 @@ class DummyRequest( a Request, use the :class:`pyramid.request.Request` class itself rather than this class while writing tests. """ + method = 'GET' application_url = 'http://example.com' host = 'example.com:80' @@ -333,8 +342,16 @@ class DummyRequest( _registry = None request_iface = IRequest - def __init__(self, params=None, environ=None, headers=None, path='/', - cookies=None, post=None, **kw): + def __init__( + self, + params=None, + environ=None, + headers=None, + path='/', + cookies=None, + post=None, + **kw + ): if environ is None: environ = {} if params is None: @@ -369,7 +386,7 @@ class DummyRequest( self.context = None self.root = None self.virtual_root = None - self.marshalled = params # repoze.monty + self.marshalled = params # repoze.monty self.session = DummySession() self.__dict__.update(kw) @@ -391,11 +408,18 @@ class DummyRequest( f = _get_response_factory(self.registry) return f(self) + have_zca = True -def setUp(registry=None, request=None, hook_zca=True, autocommit=True, - settings=None, package=None): +def setUp( + registry=None, + request=None, + hook_zca=True, + autocommit=True, + settings=None, + package=None, +): """ Set :app:`Pyramid` registry and request thread locals for the duration of a single unit test. @@ -462,8 +486,9 @@ def setUp(registry=None, request=None, hook_zca=True, autocommit=True, registry = Registry('testing') if package is None: package = caller_package() - config = Configurator(registry=registry, autocommit=autocommit, - package=package) + config = Configurator( + registry=registry, autocommit=autocommit, package=package + ) if settings is None: settings = {} if getattr(registry, 'settings', None) is None: @@ -486,12 +511,13 @@ def setUp(registry=None, request=None, hook_zca=True, autocommit=True, global have_zca try: have_zca and hook_zca and config.hook_zca() - except ImportError: # pragma: no cover + except ImportError: # pragma: no cover # (dont choke on not being able to import z.component) have_zca = False config.begin(request=request) return config + def tearDown(unhook_zca=True): """Undo the effects of :func:`pyramid.testing.setUp`. Use this function in the ``tearDown`` method of a unit test that uses @@ -507,8 +533,9 @@ def tearDown(unhook_zca=True): if unhook_zca and have_zca: try: from zope.component import getSiteManager + getSiteManager.reset() - except ImportError: # pragma: no cover + except ImportError: # pragma: no cover have_zca = False info = manager.pop() manager.clear() @@ -524,6 +551,7 @@ def tearDown(unhook_zca=True): # understand, let's not blow up pass + def cleanUp(*arg, **kw): """ An alias for :func:`pyramid.testing.setUp`. """ package = kw.get('package', None) @@ -532,6 +560,7 @@ def cleanUp(*arg, **kw): kw['package'] = package return setUp(*arg, **kw) + class DummyRendererFactory(object): """ Registered by :meth:`pyramid.config.Configurator.testing_add_renderer` as @@ -540,9 +569,10 @@ class DummyRendererFactory(object): wild believing they can register either. The ``factory`` argument passed to this constructor is usually the *real* template renderer factory, found when ``testing_add_renderer`` is called.""" + def __init__(self, name, factory): self.name = name - self.factory = factory # the "real" renderer factory reg'd previously + self.factory = factory # the "real" renderer factory reg'd previously self.renderers = {} def add(self, spec, renderer): @@ -562,8 +592,9 @@ class DummyRendererFactory(object): if self.factory: renderer = self.factory(info) else: - raise KeyError('No testing renderer registered for %r' % - spec) + raise KeyError( + 'No testing renderer registered for %r' % spec + ) return renderer @@ -571,15 +602,19 @@ class MockTemplate(object): def __init__(self, response): self._received = {} self.response = response + def __getattr__(self, attrname): return self + def __getitem__(self, attrname): return self + def __call__(self, *arg, **kw): self._received.update(kw) return self.response -def skip_on(*platforms): # pragma: no cover + +def skip_on(*platforms): # pragma: no cover skip = False for platform in platforms: if skip_on.os_name.startswith(platform): @@ -596,22 +631,26 @@ def skip_on(*platforms): # pragma: no cover else: return func else: + def wrapper(*args, **kw): if skip: return return func(*args, **kw) + wrapper.__name__ = func.__name__ wrapper.__doc__ = func.__doc__ return wrapper + return decorator -skip_on.os_name = os.name # for testing + + +skip_on.os_name = os.name # for testing + @contextmanager -def testConfig(registry=None, - request=None, - hook_zca=True, - autocommit=True, - settings=None): +def testConfig( + registry=None, request=None, hook_zca=True, autocommit=True, settings=None +): """Returns a context manager for test set up. This context manager calls :func:`pyramid.testing.setUp` when @@ -630,11 +669,13 @@ def testConfig(registry=None, req = DummyRequest() resp = myview(req) """ - config = setUp(registry=registry, - request=request, - hook_zca=hook_zca, - autocommit=autocommit, - settings=settings) + config = setUp( + registry=registry, + request=request, + hook_zca=hook_zca, + autocommit=autocommit, + settings=settings, + ) try: yield config finally: diff --git a/src/pyramid/threadlocal.py b/src/pyramid/threadlocal.py index e8f825715..7eca5b0f0 100644 --- a/src/pyramid/threadlocal.py +++ b/src/pyramid/threadlocal.py @@ -2,6 +2,7 @@ import threading from pyramid.registry import global_registry + class ThreadLocalManager(threading.local): def __init__(self, default=None): # http://code.google.com/p/google-app-engine-django/issues/detail?id=119 @@ -15,7 +16,7 @@ class ThreadLocalManager(threading.local): def push(self, info): self.stack.append(info) - set = push # b/c + set = push # b/c def pop(self): if self.stack: @@ -30,11 +31,14 @@ class ThreadLocalManager(threading.local): def clear(self): self.stack[:] = [] + def defaults(): return {'request': None, 'registry': global_registry} + manager = ThreadLocalManager(default=defaults) + def get_current_request(): """ Return the currently active request or ``None`` if no request @@ -49,7 +53,10 @@ def get_current_request(): """ return manager.get()['request'] -def get_current_registry(context=None): # context required by getSiteManager API + +def get_current_registry( + context=None +): # context required by getSiteManager API """ Return the currently active :term:`application registry` or the global application registry if no request is currently active. @@ -63,6 +70,7 @@ def get_current_registry(context=None): # context required by getSiteManager API """ return manager.get()['registry'] + class RequestContext(object): def __init__(self, request): self.request = request diff --git a/src/pyramid/traversal.py b/src/pyramid/traversal.py index d8f4690fd..322f57ed6 100644 --- a/src/pyramid/traversal.py +++ b/src/pyramid/traversal.py @@ -6,7 +6,7 @@ from pyramid.interfaces import ( IRequestFactory, ITraverser, VH_ROOT_KEY, - ) +) from pyramid.compat import ( PY2, @@ -19,18 +19,19 @@ from pyramid.compat import ( decode_path_info, unquote_bytes_to_wsgi, lru_cache, - ) +) from pyramid.encode import url_quote from pyramid.exceptions import URLDecodeError from pyramid.location import lineage from pyramid.threadlocal import get_current_registry -PATH_SEGMENT_SAFE = "~!$&'()*+,;=:@" # from webob +PATH_SEGMENT_SAFE = "~!$&'()*+,;=:@" # from webob PATH_SAFE = PATH_SEGMENT_SAFE + "/" empty = text_('') + def find_root(resource): """ Find the root node in the resource tree to which ``resource`` belongs. Note that ``resource`` should be :term:`location`-aware. @@ -43,6 +44,7 @@ def find_root(resource): break return resource + def find_resource(resource, path): """ Given a resource object and a string or tuple representing a path (such as the return value of :func:`pyramid.traversal.resource_path` or @@ -101,7 +103,9 @@ def find_resource(resource, path): raise KeyError('%r has no subelement %s' % (context, view_name)) return context -find_model = find_resource # b/w compat (forever) + +find_model = find_resource # b/w compat (forever) + def find_interface(resource, class_or_interface): """ @@ -121,6 +125,7 @@ def find_interface(resource, class_or_interface): if test(location): return location + def resource_path(resource, *elements): """ Return a string object representing the absolute physical path of the resource object based on its position in the resource tree, e.g @@ -166,7 +171,9 @@ def resource_path(resource, *elements): # which caches the joined result for us return _join_path_tuple(resource_path_tuple(resource, *elements)) -model_path = resource_path # b/w compat (forever) + +model_path = resource_path # b/w compat (forever) + def traverse(resource, path): """Given a resource object as ``resource`` and a string or tuple @@ -314,7 +321,8 @@ def traverse(resource, path): request_factory = reg.queryUtility(IRequestFactory) if request_factory is None: - from pyramid.request import Request # avoid circdep + from pyramid.request import Request # avoid circdep + request_factory = Request request = request_factory.blank(path) @@ -325,6 +333,7 @@ def traverse(resource, path): return traverser(request) + def resource_path_tuple(resource, *elements): """ Return a tuple representing the absolute physical path of the @@ -365,8 +374,10 @@ def resource_path_tuple(resource, *elements): """ return tuple(_resource_path_list(resource, *elements)) + model_path_tuple = resource_path_tuple # b/w compat (forever) + def _resource_path_list(resource, *elements): """ Implementation detail shared by resource_path and resource_path_tuple""" path = [loc.__name__ or '' for loc in lineage(resource)] @@ -374,7 +385,9 @@ def _resource_path_list(resource, *elements): path.extend(elements) return path -_model_path_list = _resource_path_list # b/w compat, not an API + +_model_path_list = _resource_path_list # b/w compat, not an API + def virtual_root(resource, request): """ @@ -412,7 +425,7 @@ def virtual_root(resource, request): vpath, rpath = url_adapter.virtual_path, url_adapter.physical_path if rpath != vpath and rpath.endswith(vpath): - vroot_path = rpath[:-len(vpath)] + vroot_path = rpath[: -len(vpath)] return find_resource(resource, vroot_path) try: @@ -420,6 +433,7 @@ def virtual_root(resource, request): except AttributeError: return find_root(resource) + def traversal_path(path): """ Variant of :func:`pyramid.traversal.traversal_path_info` suitable for decoding paths that are URL-encoded. @@ -435,8 +449,9 @@ def traversal_path(path): # must not possess characters outside ascii path = path.encode('ascii') # we unquote this path exactly like a PEP 3333 server would - path = unquote_bytes_to_wsgi(path) # result will be a native string - return traversal_path_info(path) # result will be a tuple of unicode + path = unquote_bytes_to_wsgi(path) # result will be a native string + return traversal_path_info(path) # result will be a tuple of unicode + @lru_cache(1000) def traversal_path_info(path): @@ -510,10 +525,11 @@ def traversal_path_info(path): applications in :app:`Pyramid`. """ try: - path = decode_path_info(path) # result will be Unicode + path = decode_path_info(path) # result will be Unicode except UnicodeDecodeError as e: raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason) - return split_path_info(path) # result will be tuple of Unicode + return split_path_info(path) # result will be tuple of Unicode + @lru_cache(1000) def split_path_info(path): @@ -531,6 +547,7 @@ def split_path_info(path): clean.append(segment) return tuple(clean) + _segment_cache = {} quote_path_segment_doc = """ \ @@ -574,7 +591,9 @@ if PY2: try: return _segment_cache[(segment, safe)] except KeyError: - if segment.__class__ is text_type: #isinstance slighly slower (~15%) + if ( + segment.__class__ is text_type + ): # isinstance slighly slower (~15%) result = url_quote(segment.encode('utf-8'), safe) else: result = url_quote(str(segment), safe) @@ -582,7 +601,10 @@ if PY2: # will generate exactly one Python bytecode (STORE_SUBSCR) _segment_cache[(segment, safe)] = result return result + + else: + def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE): """ %s """ % quote_path_segment_doc # The bit of this code that deals with ``_segment_cache`` is an @@ -601,8 +623,10 @@ else: _segment_cache[(segment, safe)] = result return result + slash = text_('/') + @implementer(ITraverser) class ResourceTreeTraverser(object): """ A resource tree traverser that should be used (for speed) when @@ -610,7 +634,6 @@ class ResourceTreeTraverser(object): ``__parent__`` attribute (ie. every resource in the tree is :term:`location` aware) .""" - VH_ROOT_KEY = VH_ROOT_KEY VIEW_SELECTOR = '@@' @@ -647,14 +670,17 @@ class ResourceTreeTraverser(object): # if environ['PATH_INFO'] is just not there path = slash except UnicodeDecodeError as e: - raise URLDecodeError(e.encoding, e.object, e.start, e.end, - e.reason) + raise URLDecodeError( + e.encoding, e.object, e.start, e.end, e.reason + ) if self.VH_ROOT_KEY in environ: # HTTP_X_VHM_ROOT vroot_path = decode_path_info(environ[self.VH_ROOT_KEY]) vroot_tuple = split_path_info(vroot_path) - vpath = vroot_path + path # both will (must) be unicode or asciistr + vpath = ( + vroot_path + path + ) # both will (must) be unicode or asciistr vroot_idx = len(vroot_tuple) - 1 else: vroot_tuple = () @@ -664,7 +690,7 @@ class ResourceTreeTraverser(object): root = self.root ob = vroot = root - if vpath == slash: # invariant: vpath must not be empty + if vpath == slash: # invariant: vpath must not be empty # prevent a call to traversal_path if we know it's going # to return the empty tuple vpath_tuple = () @@ -677,44 +703,60 @@ class ResourceTreeTraverser(object): vpath_tuple = split_path_info(vpath) for segment in vpath_tuple: if segment[:2] == view_selector: - return {'context': ob, - 'view_name': segment[2:], - 'subpath': vpath_tuple[i + 1:], - 'traversed': vpath_tuple[:vroot_idx + i + 1], - 'virtual_root': vroot, - 'virtual_root_path': vroot_tuple, - 'root': root} + return { + 'context': ob, + 'view_name': segment[2:], + 'subpath': vpath_tuple[i + 1 :], + 'traversed': vpath_tuple[: vroot_idx + i + 1], + 'virtual_root': vroot, + 'virtual_root_path': vroot_tuple, + 'root': root, + } try: getitem = ob.__getitem__ except AttributeError: - return {'context': ob, - 'view_name': segment, - 'subpath': vpath_tuple[i + 1:], - 'traversed': vpath_tuple[:vroot_idx + i + 1], - 'virtual_root': vroot, - 'virtual_root_path': vroot_tuple, - 'root': root} + return { + 'context': ob, + 'view_name': segment, + 'subpath': vpath_tuple[i + 1 :], + 'traversed': vpath_tuple[: vroot_idx + i + 1], + 'virtual_root': vroot, + 'virtual_root_path': vroot_tuple, + 'root': root, + } try: next = getitem(segment) except KeyError: - return {'context': ob, - 'view_name': segment, - 'subpath': vpath_tuple[i + 1:], - 'traversed': vpath_tuple[:vroot_idx + i + 1], - 'virtual_root': vroot, - 'virtual_root_path': vroot_tuple, - 'root': root} + return { + 'context': ob, + 'view_name': segment, + 'subpath': vpath_tuple[i + 1 :], + 'traversed': vpath_tuple[: vroot_idx + i + 1], + 'virtual_root': vroot, + 'virtual_root_path': vroot_tuple, + 'root': root, + } if i == vroot_idx: vroot = next ob = next i += 1 - return {'context':ob, 'view_name':empty, 'subpath':subpath, - 'traversed':vpath_tuple, 'virtual_root':vroot, - 'virtual_root_path':vroot_tuple, 'root':root} + return { + 'context': ob, + 'view_name': empty, + 'subpath': subpath, + 'traversed': vpath_tuple, + 'virtual_root': vroot, + 'virtual_root_path': vroot_tuple, + 'root': root, + } + + +ModelGraphTraverser = ( + ResourceTreeTraverser +) # b/w compat, not API, used in wild -ModelGraphTraverser = ResourceTreeTraverser # b/w compat, not API, used in wild @implementer(IResourceURL) class ResourceURL(object): @@ -742,19 +784,24 @@ class ResourceURL(object): vroot_path_tuple = tuple(vroot_path.split('/')) numels = len(vroot_path_tuple) virtual_path_tuple = ('',) + physical_path_tuple[numels:] - virtual_path = physical_path[len(vroot_path):] + virtual_path = physical_path[len(vroot_path) :] - self.virtual_path = virtual_path # IResourceURL attr + self.virtual_path = virtual_path # IResourceURL attr self.physical_path = physical_path # IResourceURL attr - self.virtual_path_tuple = virtual_path_tuple # IResourceURL attr (1.5) - self.physical_path_tuple = physical_path_tuple # IResourceURL attr (1.5) + self.virtual_path_tuple = virtual_path_tuple # IResourceURL attr (1.5) + self.physical_path_tuple = ( + physical_path_tuple + ) # IResourceURL attr (1.5) + @lru_cache(1000) def _join_path_tuple(tuple): return tuple and '/'.join([quote_path_segment(x) for x in tuple]) or '/' + class DefaultRootFactory: __parent__ = None __name__ = None + def __init__(self, request): pass diff --git a/src/pyramid/tweens.py b/src/pyramid/tweens.py index 740b6961c..839c53b8f 100644 --- a/src/pyramid/tweens.py +++ b/src/pyramid/tweens.py @@ -3,6 +3,7 @@ import sys from pyramid.compat import reraise from pyramid.httpexceptions import HTTPNotFound + def _error_handler(request, exc): # NOTE: we do not need to delete exc_info because this function # should never be in the call stack of the exception @@ -17,6 +18,7 @@ def _error_handler(request, exc): return response + def excview_tween_factory(handler, registry): """ A :term:`tween` factory which produces a tween that catches an exception raised by downstream tweens (or the main Pyramid request @@ -43,6 +45,7 @@ def excview_tween_factory(handler, registry): return excview_tween + MAIN = 'MAIN' INGRESS = 'INGRESS' EXCVIEW = 'pyramid.tweens.excview_tween_factory' diff --git a/src/pyramid/url.py b/src/pyramid/url.py index 852aa5e55..c6cf3da7f 100644 --- a/src/pyramid/url.py +++ b/src/pyramid/url.py @@ -2,21 +2,10 @@ import os -from pyramid.interfaces import ( - IResourceURL, - IRoutesMapper, - IStaticURLInfo, - ) +from pyramid.interfaces import IResourceURL, IRoutesMapper, IStaticURLInfo -from pyramid.compat import ( - bytes_, - lru_cache, - string_types, - ) -from pyramid.encode import ( - url_quote, - urlencode, -) +from pyramid.compat import bytes_, lru_cache, string_types +from pyramid.encode import url_quote, urlencode from pyramid.path import caller_package from pyramid.threadlocal import get_current_registry @@ -25,11 +14,12 @@ from pyramid.traversal import ( quote_path_segment, PATH_SAFE, PATH_SEGMENT_SAFE, - ) +) -QUERY_SAFE = "/?:@!$&'()*+,;=" # RFC 3986 +QUERY_SAFE = "/?:@!$&'()*+,;=" # RFC 3986 ANCHOR_SAFE = QUERY_SAFE + def parse_url_overrides(request, kw): """ Parse special arguments passed when generating urls. @@ -48,7 +38,7 @@ def parse_url_overrides(request, kw): anchor = kw.pop('_anchor', '') if app_url is None: - if (scheme is not None or host is not None or port is not None): + if scheme is not None or host is not None or port is not None: app_url = request._partial_application_url(scheme, host, port) else: app_url = request.application_url @@ -66,6 +56,7 @@ def parse_url_overrides(request, kw): return app_url, qs, frag + class URLMethodsMixin(object): """ Request methods mixin for BaseRequest having to do with URL generation """ @@ -115,7 +106,7 @@ class URLMethodsMixin(object): if port: url += ':%s' % port - url_encoding = getattr(self, 'url_encoding', 'utf-8') # webob 1.2b3+ + url_encoding = getattr(self, 'url_encoding', 'utf-8') # webob 1.2b3+ bscript_name = bytes_(self.script_name, url_encoding) return url + url_quote(bscript_name, PATH_SAFE) @@ -255,7 +246,7 @@ class URLMethodsMixin(object): try: reg = self.registry except AttributeError: - reg = get_current_registry() # b/c + reg = get_current_registry() # b/c mapper = reg.getUtility(IRoutesMapper) route = mapper.get_route(route_name) @@ -267,7 +258,7 @@ class URLMethodsMixin(object): app_url, qs, anchor = parse_url_overrides(self, kw) - path = route.generate(kw) # raises KeyError if generate fails + path = route.generate(kw) # raises KeyError if generate fails if elements: suffix = _join_elements(elements) @@ -522,7 +513,7 @@ class URLMethodsMixin(object): try: reg = self.registry except AttributeError: - reg = get_current_registry() # b/c + reg = get_current_registry() # b/c url_adapter = reg.queryMultiAdapter((resource, self), IResourceURL) if url_adapter is None: @@ -531,9 +522,7 @@ class URLMethodsMixin(object): virtual_path = getattr(url_adapter, 'virtual_path', None) urlkw = {} - for name in ( - 'app_url', 'scheme', 'host', 'port', 'query', 'anchor' - ): + for name in ('app_url', 'scheme', 'host', 'port', 'query', 'anchor'): val = kw.get(name, None) if val is not None: urlkw['_' + name] = val @@ -583,7 +572,7 @@ class URLMethodsMixin(object): return resource_url + suffix + qs + anchor - model_url = resource_url # b/w compat forever + model_url = resource_url # b/w compat forever def resource_path(self, resource, *elements, **kw): """ @@ -651,7 +640,7 @@ class URLMethodsMixin(object): try: reg = self.registry except AttributeError: - reg = get_current_registry() # b/c + reg = get_current_registry() # b/c info = reg.queryUtility(IStaticURLInfo) if info is None: @@ -803,6 +792,7 @@ def route_url(route_name, request, *elements, **kw): """ return request.route_url(route_name, *elements, **kw) + def route_path(route_name, request, *elements, **kw): """ This is a backwards compatibility function. Its result is the same as @@ -814,6 +804,7 @@ def route_path(route_name, request, *elements, **kw): """ return request.route_path(route_name, *elements, **kw) + def resource_url(resource, request, *elements, **kw): """ This is a backwards compatibility function. Its result is the same as @@ -825,7 +816,8 @@ def resource_url(resource, request, *elements, **kw): """ return request.resource_url(resource, *elements, **kw) -model_url = resource_url # b/w compat (forever) + +model_url = resource_url # b/w compat (forever) def static_url(path, request, **kw): @@ -865,6 +857,7 @@ def static_path(path, request, **kw): path = '%s:%s' % (package.__name__, path) return request.static_path(path, **kw) + def current_route_url(request, *elements, **kw): """ This is a backwards compatibility function. Its result is the same as @@ -877,6 +870,7 @@ def current_route_url(request, *elements, **kw): """ return request.current_route_url(*elements, **kw) + def current_route_path(request, *elements, **kw): """ This is a backwards compatibility function. Its result is the same as @@ -889,6 +883,9 @@ def current_route_path(request, *elements, **kw): """ return request.current_route_path(*elements, **kw) + @lru_cache(1000) def _join_elements(elements): - return '/'.join([quote_path_segment(s, safe=PATH_SEGMENT_SAFE) for s in elements]) + return '/'.join( + [quote_path_segment(s, safe=PATH_SEGMENT_SAFE) for s in elements] + ) diff --git a/src/pyramid/urldispatch.py b/src/pyramid/urldispatch.py index a61071845..de8a69d2a 100644 --- a/src/pyramid/urldispatch.py +++ b/src/pyramid/urldispatch.py @@ -1,10 +1,7 @@ import re from zope.interface import implementer -from pyramid.interfaces import ( - IRoutesMapper, - IRoute, - ) +from pyramid.interfaces import IRoutesMapper, IRoute from pyramid.compat import ( PY2, @@ -15,30 +12,29 @@ from pyramid.compat import ( binary_type, is_nonstr_iter, decode_path_info, - ) +) from pyramid.exceptions import URLDecodeError -from pyramid.traversal import ( - quote_path_segment, - split_path_info, - PATH_SAFE, - ) +from pyramid.traversal import quote_path_segment, split_path_info, PATH_SAFE _marker = object() + @implementer(IRoute) class Route(object): - def __init__(self, name, pattern, factory=None, predicates=(), - pregenerator=None): + def __init__( + self, name, pattern, factory=None, predicates=(), pregenerator=None + ): self.pattern = pattern - self.path = pattern # indefinite b/w compat, not in interface + self.path = pattern # indefinite b/w compat, not in interface self.match, self.generate = _compile_route(pattern) self.name = name self.factory = factory self.predicates = predicates self.pregenerator = pregenerator + @implementer(IRoutesMapper) class RoutesMapper(object): def __init__(self): @@ -59,8 +55,15 @@ class RoutesMapper(object): def get_route(self, name): return self.routes.get(name) - def connect(self, name, pattern, factory=None, predicates=(), - pregenerator=None, static=False): + def connect( + self, + name, + pattern, + factory=None, + predicates=(), + pregenerator=None, + static=False, + ): if name in self.routes: oldroute = self.routes[name] if oldroute in self.routelist: @@ -86,18 +89,21 @@ class RoutesMapper(object): except KeyError: path = '/' except UnicodeDecodeError as e: - raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason) + raise URLDecodeError( + e.encoding, e.object, e.start, e.end, e.reason + ) for route in self.routelist: match = route.match(path) if match is not None: preds = route.predicates - info = {'match':match, 'route':route} + info = {'match': match, 'route': route} if preds and not all((p(info, request) for p in preds)): continue return info - return {'route':None, 'match':None} + return {'route': None, 'match': None} + # stolen from bobo and modified old_route_re = re.compile(r'(\:[_a-zA-Z]\w*)') @@ -109,10 +115,12 @@ star_at_end = re.compile(r'\*(\w*)$') # (\{[a-zA-Z][^\}]*\}) but that choked when supplied with e.g. {foo:\d{4}}. route_re = re.compile(r'(\{[_a-zA-Z][^{}]*(?:\{[^{}]*\}[^{}]*)*\})') + def update_pattern(matchobj): name = matchobj.group(0) return '{%s}' % name[1:] + def _compile_route(route): # This function really wants to consume Unicode patterns natively, but if # someone passes us a bytestring, we allow it by converting it to Unicode @@ -126,7 +134,8 @@ def _compile_route(route): raise ValueError( 'The pattern value passed to add_route must be ' 'either a Unicode string or a plain string without ' - 'any non-ASCII characters (you provided %r).' % route) + 'any non-ASCII characters (you provided %r).' % route + ) if old_route_re.search(route) and not route_re.search(route): route = old_route_re.sub(update_pattern, route) @@ -145,17 +154,19 @@ def _compile_route(route): pat.reverse() rpat = [] gen = [] - prefix = pat.pop() # invar: always at least one element (route='/'+route) + prefix = pat.pop() # invar: always at least one element (route='/'+route) # We want to generate URL-encoded URLs, so we url-quote the prefix, being # careful not to quote any embedded slashes. We have to replace '%' with # '%%' afterwards, as the strings that go into "gen" are used as string # replacement targets. - gen.append(quote_path_segment(prefix, safe='/').replace('%', '%%')) # native - rpat.append(re.escape(prefix)) # unicode + gen.append( + quote_path_segment(prefix, safe='/').replace('%', '%%') + ) # native + rpat.append(re.escape(prefix)) # unicode while pat: - name = pat.pop() # unicode + name = pat.pop() # unicode name = name[1:-1] if ':' in name: # reg may contain colons as well, @@ -163,12 +174,12 @@ def _compile_route(route): name, reg = name.split(':', 1) else: reg = '[^/]+' - gen.append('%%(%s)s' % native_(name)) # native - name = '(?P<%s>%s)' % (name, reg) # unicode + gen.append('%%(%s)s' % native_(name)) # native + name = '(?P<%s>%s)' % (name, reg) # unicode rpat.append(name) - s = pat.pop() # unicode + s = pat.pop() # unicode if s: - rpat.append(re.escape(s)) # unicode + rpat.append(re.escape(s)) # unicode # We want to generate URL-encoded URLs, so we url-quote this # literal in the pattern, being careful not to quote the embedded # slashes. We have to replace '%' with '%%' afterwards, as the @@ -177,12 +188,13 @@ def _compile_route(route): gen.append(quote_path_segment(s, safe='/').replace('%', '%%')) if remainder: - rpat.append('(?P<%s>.*?)' % remainder) # unicode - gen.append('%%(%s)s' % native_(remainder)) # native + rpat.append('(?P<%s>.*?)' % remainder) # unicode + gen.append('%%(%s)s' % native_(remainder)) # native - pattern = ''.join(rpat) + '$' # unicode + pattern = ''.join(rpat) + '$' # unicode match = re.compile(pattern).match + def matcher(path): # This function really wants to consume Unicode patterns natively, # but if someone passes us a bytestring, we allow it by converting it @@ -227,9 +239,7 @@ def _compile_route(route): if k == remainder: # a stararg argument if is_nonstr_iter(v): - v = '/'.join( - [q(x) for x in v] - ) # native + v = '/'.join([q(x) for x in v]) # native else: if v.__class__ not in string_types: v = str(v) @@ -243,7 +253,7 @@ def _compile_route(route): # at this point, the value will be a native string newdict[k] = v - result = gen % newdict # native string result + result = gen % newdict # native string result return result return matcher, generator diff --git a/src/pyramid/util.py b/src/pyramid/util.py index 6655455bf..bebf9e7d3 100644 --- a/src/pyramid/util.py +++ b/src/pyramid/util.py @@ -1,5 +1,6 @@ from contextlib import contextmanager import functools + try: # py2.7.7+ and py3.3+ have native comparison support from hmac import compare_digest @@ -8,10 +9,7 @@ except ImportError: # pragma: no cover import inspect import weakref -from pyramid.exceptions import ( - ConfigurationError, - CyclicDependencyError, - ) +from pyramid.exceptions import ConfigurationError, CyclicDependencyError from pyramid.compat import ( getargspec, @@ -22,8 +20,8 @@ from pyramid.compat import ( bytes_, text_, PY2, - native_ - ) + native_, +) from pyramid.path import DottedNameResolver as _DottedNameResolver @@ -31,21 +29,26 @@ _marker = object() class DottedNameResolver(_DottedNameResolver): - def __init__(self, package=None): # default to package = None for bw compat + def __init__( + self, package=None + ): # default to package = None for bw compat _DottedNameResolver.__init__(self, package) + def is_string_or_iterable(v): if isinstance(v, string_types): return True if hasattr(v, '__iter__'): return True + def as_sorted_tuple(val): if not is_nonstr_iter(val): val = (val,) val = tuple(sorted(val)) return val + class InstancePropertyHelper(object): """A helper object for assigning properties and descriptors to instances. It is not normally possible to do this because descriptors must be @@ -56,6 +59,7 @@ class InstancePropertyHelper(object): per-property and then invoking :meth:`.apply` on target objects. """ + def __init__(self): self.properties = {} @@ -81,7 +85,8 @@ class InstancePropertyHelper(object): name = callable.__name__ fn = callable if reify: - import pyramid.decorator # avoid circular import + import pyramid.decorator # avoid circular import + fn = pyramid.decorator.reify(fn) elif not is_property: fn = property(fn) @@ -141,6 +146,7 @@ class InstancePropertyHelper(object): if self.properties: self.apply_properties(target, self.properties) + class InstancePropertyMixin(object): """ Mixin that will allow an instance to add properties at run-time as if they had been defined via @property or @reify @@ -200,7 +206,9 @@ class InstancePropertyMixin(object): 1 """ InstancePropertyHelper.set_property( - self, callable, name=name, reify=reify) + self, callable, name=name, reify=reify + ) + class WeakOrderedSet(object): """ Maintain a set of items. @@ -268,6 +276,7 @@ class WeakOrderedSet(object): oid = self._order[-1] return self._items[oid]() + def strings_differ(string1, string2, compare_digest=compare_digest): """Check whether two strings differ while avoiding timing attacks. @@ -299,6 +308,7 @@ def strings_differ(string1, string2, compare_digest=compare_digest): invalid_bits += a != b return invalid_bits != 0 + def object_description(object): """ Produce a human-consumable text description of ``object``, usually involving a Python dotted name. For example: @@ -345,11 +355,12 @@ def object_description(object): return text_('module %s' % modulename) if inspect.ismethod(object): oself = getattr(object, '__self__', None) - if oself is None: # pragma: no cover + if oself is None: # pragma: no cover oself = getattr(object, 'im_self', None) - return text_('method %s of class %s.%s' % - (object.__name__, modulename, - oself.__class__.__name__)) + return text_( + 'method %s of class %s.%s' + % (object.__name__, modulename, oself.__class__.__name__) + ) if inspect.isclass(object): dottedname = '%s.%s' % (modulename, object.__name__) @@ -359,12 +370,14 @@ def object_description(object): return text_('function %s' % dottedname) return text_('object %s' % str(object)) + def shortrepr(object, closer): r = str(object) if len(r) > 100: r = r[:100] + ' ... %s' % closer return r + class Sentinel(object): def __init__(self, repr): self.repr = repr @@ -372,19 +385,18 @@ class Sentinel(object): def __repr__(self): return self.repr + FIRST = Sentinel('FIRST') LAST = Sentinel('LAST') + class TopologicalSorter(object): """ A utility class which can be used to perform topological sorts against tuple-like data.""" + def __init__( - self, - default_before=LAST, - default_after=None, - first=FIRST, - last=LAST, - ): + self, default_before=LAST, default_after=None, first=FIRST, last=LAST + ): self.names = [] self.req_before = set() self.req_after = set() @@ -414,7 +426,7 @@ class TopologicalSorter(object): self.req_before.remove(name) for u in before: self.order.remove((name, u)) - + def add(self, name, val, after=None, before=None): """ Add a node to the sort input. The ``name`` should be a string or any other hashable object, the ``val`` should be the sortable (doesn't @@ -454,7 +466,6 @@ class TopologicalSorter(object): self.order += [(name, o) for o in before] self.req_before.add(name) - def sorted(self): """ Returns the sort input values in topologically sorted order""" order = [(self.first, self.last)] @@ -469,7 +480,7 @@ class TopologicalSorter(object): def add_node(node): if node not in graph: roots.append(node) - graph[node] = [0] # 0 = number of arcs coming into this node + graph[node] = [0] # 0 = number of arcs coming into this node def add_arc(fromnode, tonode): graph[fromnode].append(tonode) @@ -482,7 +493,7 @@ class TopologicalSorter(object): has_before, has_after = set(), set() for a, b in order: - if a in names and b in names: # deal with missing dependencies + if a in names and b in names: # deal with missing dependencies add_arc(a, b) has_before.add(a) has_after.add(b) @@ -507,7 +518,7 @@ class TopologicalSorter(object): for child in children: arcs = graph[child][0] arcs -= 1 - graph[child][0] = arcs + graph[child][0] = arcs if arcs == 0: roots.insert(0, child) del graph[root] @@ -542,6 +553,7 @@ def get_callable_name(name): ) raise ConfigurationError(msg % name) + @contextmanager def hide_attrs(obj, *attrs): """ @@ -574,9 +586,11 @@ def is_same_domain(host, pattern): return False pattern = pattern.lower() - return (pattern[0] == "." and - (host.endswith(pattern) or host == pattern[1:]) or - pattern == host) + return ( + pattern[0] == "." + and (host.endswith(pattern) or host == pattern[1:]) + or pattern == host + ) def make_contextmanager(fn): @@ -590,6 +604,7 @@ def make_contextmanager(fn): @functools.wraps(fn) def wrapper(*a, **kw): yield fn(*a, **kw) + return wrapper diff --git a/src/pyramid/view.py b/src/pyramid/view.py index 769328344..c45b976bb 100644 --- a/src/pyramid/view.py +++ b/src/pyramid/view.py @@ -13,31 +13,26 @@ from pyramid.interfaces import ( IViewClassifier, IRequest, IExceptionViewClassifier, - ) +) from pyramid.compat import decode_path_info from pyramid.compat import reraise as reraise_ -from pyramid.exceptions import ( - ConfigurationError, - PredicateMismatch, -) +from pyramid.exceptions import ConfigurationError, PredicateMismatch from pyramid.httpexceptions import ( HTTPNotFound, HTTPTemporaryRedirect, default_exceptionresponse_view, - ) +) -from pyramid.threadlocal import ( - get_current_registry, - manager, - ) +from pyramid.threadlocal import get_current_registry, manager from pyramid.util import hide_attrs _marker = object() + def render_view_to_response(context, request, name='', secure=True): """ Call the :term:`view callable` configured with a :term:`view configuration` that matches the :term:`view name` ``name`` @@ -84,9 +79,9 @@ def render_view_to_response(context, request, name='', secure=True): name, secure=secure, request_iface=request_iface, - ) + ) - return response # NB: might be None + return response # NB: might be None def render_view_to_iterable(context, request, name='', secure=True): @@ -119,6 +114,7 @@ def render_view_to_iterable(context, request, name='', secure=True): return None return response.app_iter + def render_view(context, request, name='', secure=True): """ Call the :term:`view callable` configured with a :term:`view configuration` that matches the :term:`view name` ``name`` @@ -146,6 +142,7 @@ def render_view(context, request, name='', secure=True): return None return b''.join(iterable) + class view_config(object): """ A function, class or method :term:`decorator` which allows a developer to create view registrations nearer to a :term:`view @@ -213,7 +210,9 @@ class view_config(object): because of the limitation of ``venusian.Scanner.scan``. """ - venusian = venusian # for testing injection + + venusian = venusian # for testing injection + def __init__(self, **settings): if 'for_' in settings: if settings.get('context') is None: @@ -229,8 +228,9 @@ class view_config(object): config = context.config.with_package(info.module) config.add_view(view=ob, **settings) - info = self.venusian.attach(wrapped, callback, category=category, - depth=depth + 1) + info = self.venusian.attach( + wrapped, callback, category=category, depth=depth + 1 + ) if info.scope == 'class': # if the decorator was attached to a method in a class, or @@ -239,10 +239,12 @@ class view_config(object): if settings.get('attr') is None: settings['attr'] = wrapped.__name__ - settings['_info'] = info.codeinfo # fbo "action_method" + settings['_info'] = info.codeinfo # fbo "action_method" return wrapped -bfg_view = view_config # bw compat (forever) + +bfg_view = view_config # bw compat (forever) + class view_defaults(view_config): """ A class :term:`decorator` which, when applied to a class, will @@ -257,6 +259,7 @@ class view_defaults(view_config): wrapped.__view_defaults__ = self.__dict__.copy() return wrapped + class AppendSlashNotFoundViewFactory(object): """ There can only be one :term:`Not Found view` in any :app:`Pyramid` application. Even if you use @@ -292,7 +295,10 @@ class AppendSlashNotFoundViewFactory(object): .. deprecated:: 1.3 """ - def __init__(self, notfound_view=None, redirect_class=HTTPTemporaryRedirect): + + def __init__( + self, notfound_view=None, redirect_class=HTTPTemporaryRedirect + ): if notfound_view is None: notfound_view = default_exceptionresponse_view self.notfound_view = notfound_view @@ -309,9 +315,12 @@ class AppendSlashNotFoundViewFactory(object): qs = request.query_string if qs: qs = '?' + qs - return self.redirect_class(location=request.path + '/' + qs) + return self.redirect_class( + location=request.path + '/' + qs + ) return self.notfound_view(context, request) + append_slash_notfound_view = AppendSlashNotFoundViewFactory() append_slash_notfound_view.__doc__ = """\ For behavior like Django's ``APPEND_SLASH=True``, use this view as the @@ -337,6 +346,7 @@ view as the Not Found view:: """ + class notfound_view_config(object): """ .. versionadded:: 1.3 @@ -417,8 +427,9 @@ class notfound_view_config(object): config = context.config.with_package(info.module) config.add_notfound_view(view=ob, **settings) - info = self.venusian.attach(wrapped, callback, category=category, - depth=depth + 1) + info = self.venusian.attach( + wrapped, callback, category=category, depth=depth + 1 + ) if info.scope == 'class': # if the decorator was attached to a method in a class, or @@ -427,9 +438,10 @@ class notfound_view_config(object): if settings.get('attr') is None: settings['attr'] = wrapped.__name__ - settings['_info'] = info.codeinfo # fbo "action_method" + settings['_info'] = info.codeinfo # fbo "action_method" return wrapped + class forbidden_view_config(object): """ .. versionadded:: 1.3 @@ -479,8 +491,9 @@ class forbidden_view_config(object): config = context.config.with_package(info.module) config.add_forbidden_view(view=ob, **settings) - info = self.venusian.attach(wrapped, callback, category=category, - depth=depth + 1) + info = self.venusian.attach( + wrapped, callback, category=category, depth=depth + 1 + ) if info.scope == 'class': # if the decorator was attached to a method in a class, or @@ -489,9 +502,10 @@ class forbidden_view_config(object): if settings.get('attr') is None: settings['attr'] = wrapped.__name__ - settings['_info'] = info.codeinfo # fbo "action_method" + settings['_info'] = info.codeinfo # fbo "action_method" return wrapped + class exception_view_config(object): """ .. versionadded:: 1.8 @@ -526,6 +540,7 @@ class exception_view_config(object): Added the ``_depth`` and ``_category`` arguments. """ + venusian = venusian def __init__(self, *args, **settings): @@ -545,8 +560,9 @@ class exception_view_config(object): config = context.config.with_package(info.module) config.add_exception_view(view=ob, **settings) - info = self.venusian.attach(wrapped, callback, category=category, - depth=depth + 1) + info = self.venusian.attach( + wrapped, callback, category=category, depth=depth + 1 + ) if info.scope == 'class': # if the decorator was attached to a method in a class, or @@ -555,9 +571,10 @@ class exception_view_config(object): if settings.get('attr') is None: settings['attr'] = wrapped.__name__ - settings['_info'] = info.codeinfo # fbo "action_method" + settings['_info'] = info.codeinfo # fbo "action_method" return wrapped + def _find_views( registry, request_iface, @@ -565,7 +582,7 @@ def _find_views( view_name, view_types=None, view_classifier=None, - ): +): if view_types is None: view_types = (IView, ISecuredView, IMultiView) if view_classifier is None: @@ -581,9 +598,7 @@ def _find_views( source_ifaces = (view_classifier, req_type, ctx_type) for view_type in view_types: view_callable = registered( - source_ifaces, - view_type, - name=view_name, + source_ifaces, view_type, name=view_name ) if view_callable is not None: views.append(view_callable) @@ -599,6 +614,7 @@ def _find_views( return views + def _call_view( registry, request, @@ -609,7 +625,7 @@ def _call_view( view_classifier=None, secure=True, request_iface=None, - ): +): if request_iface is None: request_iface = getattr(request, 'request_iface', IRequest) view_callables = _find_views( @@ -619,7 +635,7 @@ def _call_view( view_name, view_types=view_types, view_classifier=view_classifier, - ) + ) pme = None response = None @@ -631,10 +647,8 @@ def _call_view( # the view will have a __call_permissive__ attribute if it's # secured; otherwise it won't. view_callable = getattr( - view_callable, - '__call_permissive__', - view_callable - ) + view_callable, '__call_permissive__', view_callable + ) # if this view is secured, it will raise a Forbidden # appropriately if the executing user does not have the proper @@ -649,15 +663,13 @@ def _call_view( return response + class ViewMethodsMixin(object): """ Request methods mixin for BaseRequest having to do with executing views """ + def invoke_exception_view( - self, - exc_info=None, - request=None, - secure=True, - reraise=False, + self, exc_info=None, request=None, secure=True, reraise=False ): """ Executes an exception view related to the request it's called upon. The arguments it takes are these: @@ -742,7 +754,7 @@ class ViewMethodsMixin(object): view_classifier=IExceptionViewClassifier, secure=secure, request_iface=request_iface.combined, - ) + ) except Exception: if reraise: reraise_(*exc_info) diff --git a/src/pyramid/viewderivers.py b/src/pyramid/viewderivers.py index d914a4752..0b4758554 100644 --- a/src/pyramid/viewderivers.py +++ b/src/pyramid/viewderivers.py @@ -1,15 +1,9 @@ import inspect -from zope.interface import ( - implementer, - provider, - ) +from zope.interface import implementer, provider from pyramid.security import NO_PERMISSION_REQUIRED -from pyramid.csrf import ( - check_csrf_origin, - check_csrf_token, -) +from pyramid.csrf import check_csrf_origin, check_csrf_token from pyramid.response import Response from pyramid.interfaces import ( @@ -21,21 +15,13 @@ from pyramid.interfaces import ( IResponse, IViewMapper, IViewMapperFactory, - ) +) -from pyramid.compat import ( - is_bound_method, - is_unbound_method, - ) +from pyramid.compat import is_bound_method, is_unbound_method -from pyramid.exceptions import ( - ConfigurationError, - ) +from pyramid.exceptions import ConfigurationError from pyramid.httpexceptions import HTTPForbidden -from pyramid.util import ( - object_description, - takes_one_arg, -) +from pyramid.util import object_description, takes_one_arg from pyramid.view import render_view_to_response from pyramid import renderers @@ -47,9 +33,11 @@ def view_description(view): # custom view mappers might not add __text__ return object_description(view) + def requestonly(view, attr=None): return takes_one_arg(view, attr=attr, argname='request') + @implementer(IViewMapper) @provider(IViewMapperFactory) class DefaultViewMapper(object): @@ -58,10 +46,12 @@ class DefaultViewMapper(object): def __call__(self, view): if is_unbound_method(view) and self.attr is None: - raise ConfigurationError(( - 'Unbound method calls are not supported, please set the class ' - 'as your `view` and the method as your `attr`' - )) + raise ConfigurationError( + ( + 'Unbound method calls are not supported, please set the class ' + 'as your `view` and the method as your `attr`' + ) + ) if inspect.isclass(view): view = self.map_class(view) @@ -76,7 +66,9 @@ class DefaultViewMapper(object): else: mapped_view = self.map_class_native(view) mapped_view.__text__ = 'method %s of %s' % ( - self.attr or '__call__', object_description(view)) + self.attr or '__call__', + object_description(view), + ) return mapped_view def map_nonclass(self, view): @@ -98,11 +90,15 @@ class DefaultViewMapper(object): # bound method. if is_bound_method(view): _mapped_view = mapped_view + def mapped_view(context, request): return _mapped_view(context, request) + if self.attr is not None: mapped_view.__text__ = 'attr %s of %s' % ( - self.attr, object_description(view)) + self.attr, + object_description(view), + ) else: mapped_view.__text__ = object_description(view) return mapped_view @@ -110,6 +106,7 @@ class DefaultViewMapper(object): def map_class_requestonly(self, view): # its a class that has an __init__ which only accepts request attr = self.attr + def _class_requestonly_view(context, request): inst = view(request) request.__view__ = inst @@ -118,12 +115,14 @@ class DefaultViewMapper(object): else: response = getattr(inst, attr)() return response + return _class_requestonly_view def map_class_native(self, view): # its a class that has an __init__ which accepts both context and # request attr = self.attr + def _class_view(context, request): inst = view(context, request) request.__view__ = inst @@ -132,18 +131,21 @@ class DefaultViewMapper(object): else: response = getattr(inst, attr)() return response + return _class_view def map_nonclass_requestonly(self, view): # its a function that has a __call__ which accepts only a single # request argument attr = self.attr + def _requestonly_view(context, request): if attr is None: response = view(request) else: response = getattr(view, attr)(request) return response + return _requestonly_view def map_nonclass_attr(self, view): @@ -152,6 +154,7 @@ class DefaultViewMapper(object): def _attr_view(context, request): response = getattr(view, self.attr)(context, request) return response + return _attr_view @@ -159,8 +162,10 @@ def wraps_view(wrapper): def inner(view, info): wrapper_view = wrapper(view, info) return preserve_view_attrs(view, wrapper_view) + return inner + def preserve_view_attrs(view, wrapper): if view is None: return wrapper @@ -185,9 +190,16 @@ def preserve_view_attrs(view, wrapper): # attrs that may not exist on "view", but, if so, must be attached to # "wrapped view" - for attr in ('__permitted__', '__call_permissive__', '__permission__', - '__predicated__', '__predicates__', '__accept__', - '__order__', '__text__'): + for attr in ( + '__permitted__', + '__call_permissive__', + '__permission__', + '__predicated__', + '__predicates__', + '__accept__', + '__order__', + '__text__', + ): try: setattr(wrapper, attr, getattr(view, attr)) except AttributeError: @@ -195,6 +207,7 @@ def preserve_view_attrs(view, wrapper): return wrapper + def mapped_view(view, info): mapper = info.options.get('mapper') if mapper is None: @@ -207,29 +220,37 @@ def mapped_view(view, info): mapped_view = mapper(**info.options)(view) return mapped_view + mapped_view.options = ('mapper', 'attr') + def owrapped_view(view, info): wrapper_viewname = info.options.get('wrapper') viewname = info.options.get('name') if not wrapper_viewname: return view + def _owrapped_view(context, request): response = view(context, request) request.wrapped_response = response request.wrapped_body = response.body request.wrapped_view = view - wrapped_response = render_view_to_response(context, request, - wrapper_viewname) + wrapped_response = render_view_to_response( + context, request, wrapper_viewname + ) if wrapped_response is None: raise ValueError( 'No wrapper view named %r found when executing view ' - 'named %r' % (wrapper_viewname, viewname)) + 'named %r' % (wrapper_viewname, viewname) + ) return wrapped_response + return _owrapped_view + owrapped_view.options = ('name', 'wrapper') + def http_cached_view(view, info): if info.settings.get('prevent_http_cache', False): return view @@ -247,27 +268,33 @@ def http_cached_view(view, info): except ValueError: raise ConfigurationError( 'If http_cache parameter is a tuple or list, it must be ' - 'in the form (seconds, options); not %s' % (seconds,)) + 'in the form (seconds, options); not %s' % (seconds,) + ) def wrapper(context, request): response = view(context, request) - prevent_caching = getattr(response.cache_control, 'prevent_auto', - False) + prevent_caching = getattr( + response.cache_control, 'prevent_auto', False + ) if not prevent_caching: response.cache_expires(seconds, **options) return response return wrapper + http_cached_view.options = ('http_cache',) + def secured_view(view, info): for wrapper in (_secured_view, _authdebug_view): view = wraps_view(wrapper)(view, info) return view + secured_view.options = ('permission',) + def _secured_view(view, info): permission = explicit_val = info.options.get('permission') if permission is None: @@ -287,18 +314,23 @@ def _secured_view(view, info): return view if authn_policy and authz_policy and (permission is not None): + def permitted(context, request): principals = authn_policy.effective_principals(request) return authz_policy.permits(context, principals, permission) + def secured_view(context, request): result = permitted(context, request) if result: return view(context, request) view_name = getattr(view, '__name__', view) msg = getattr( - request, 'authdebug_message', - 'Unauthorized: %s failed permission check' % view_name) + request, + 'authdebug_message', + 'Unauthorized: %s failed permission check' % view_name, + ) raise HTTPForbidden(msg, result=result) + wrapped_view = secured_view wrapped_view.__call_permissive__ = view wrapped_view.__permitted__ = permitted @@ -306,6 +338,7 @@ def _secured_view(view, info): return wrapped_view + def _authdebug_view(view, info): wrapped_view = view settings = info.settings @@ -321,6 +354,7 @@ def _authdebug_view(view, info): return view if settings and settings.get('debug_authorization', False): + def authdebug_view(context, request): view_name = getattr(request, 'view_name', None) @@ -331,24 +365,29 @@ def _authdebug_view(view, info): msg = 'Allowed (no permission registered)' else: principals = authn_policy.effective_principals(request) - msg = str(authz_policy.permits( - context, principals, permission)) + msg = str( + authz_policy.permits(context, principals, permission) + ) else: msg = 'Allowed (no authorization policy in use)' view_name = getattr(request, 'view_name', None) url = getattr(request, 'url', None) - msg = ('debug_authorization of url %s (view name %r against ' - 'context %r): %s' % (url, view_name, context, msg)) + msg = ( + 'debug_authorization of url %s (view name %r against ' + 'context %r): %s' % (url, view_name, context, msg) + ) if logger: logger.debug(msg) if request is not None: request.authdebug_message = msg return view(context, request) + wrapped_view = authdebug_view return wrapped_view + def rendered_view(view, info): # one way or another this wrapper must produce a Response (unless # the renderer is a NullRendererHelper) @@ -360,23 +399,29 @@ def rendered_view(view, info): # a view registration. def viewresult_to_response(context, request): result = view(context, request) - if result.__class__ is Response: # common case + if result.__class__ is Response: # common case response = result else: response = info.registry.queryAdapterOrSelf(result, IResponse) if response is None: if result is None: - append = (' You may have forgotten to return a value ' - 'from the view callable.') + append = ( + ' You may have forgotten to return a value ' + 'from the view callable.' + ) elif isinstance(result, dict): - append = (' You may have forgotten to define a ' - 'renderer in the view configuration.') + append = ( + ' You may have forgotten to define a ' + 'renderer in the view configuration.' + ) else: append = '' - msg = ('Could not convert return value of the view ' - 'callable %s into a response object. ' - 'The value returned was %r.' + append) + msg = ( + 'Could not convert return value of the view ' + 'callable %s into a response object. ' + 'The value returned was %r.' + append + ) raise ValueError(msg % (view_description(view), result)) @@ -389,7 +434,7 @@ def rendered_view(view, info): def rendered_view(context, request): result = view(context, request) - if result.__class__ is Response: # potential common case + if result.__class__ is Response: # potential common case response = result else: # this must adapt, it can't do a simple interface check @@ -403,7 +448,8 @@ def rendered_view(view, info): view_renderer = renderers.RendererHelper( name=renderer_name, package=info.package, - registry=info.registry) + registry=info.registry, + ) else: view_renderer = renderer.clone() if '__view__' in attrs: @@ -411,21 +457,26 @@ def rendered_view(view, info): else: view_inst = getattr(view, '__original_view__', view) response = view_renderer.render_view( - request, result, view_inst, context) + request, result, view_inst, context + ) return response return rendered_view + rendered_view.options = ('renderer',) + def decorated_view(view, info): decorator = info.options.get('decorator') if decorator is None: return view return decorator(view) + decorated_view.options = ('decorator',) + def csrf_view(view, info): explicit_val = info.options.get('require_csrf') defaults = info.registry.queryUtility(IDefaultCSRFOptions) @@ -443,29 +494,29 @@ def csrf_view(view, info): callback = defaults.callback enabled = ( - explicit_val is True or + explicit_val is True + or # fallback to the default val if not explicitly enabled # but only if the view is not an exception view - ( - explicit_val is not False and default_val and - not info.exception_only - ) + (explicit_val is not False and default_val and not info.exception_only) ) # disable if both header and token are disabled enabled = enabled and (token or header) wrapped_view = view if enabled: + def csrf_view(context, request): - if ( - request.method not in safe_methods and - (callback is None or callback(request)) + if request.method not in safe_methods and ( + callback is None or callback(request) ): check_csrf_origin(request, raises=True) check_csrf_token(request, token, header, raises=True) return view(context, request) + wrapped_view = csrf_view return wrapped_view + csrf_view.options = ('require_csrf',) VIEW = 'VIEW' diff --git a/src/pyramid/wsgi.py b/src/pyramid/wsgi.py index 1c1bded32..b3f3803e4 100644 --- a/src/pyramid/wsgi.py +++ b/src/pyramid/wsgi.py @@ -1,6 +1,7 @@ from functools import wraps from pyramid.request import call_app_with_subpath_as_path_info + def wsgiapp(wrapped): """ Decorator to turn a WSGI application into a :app:`Pyramid` :term:`view callable`. This decorator differs from the @@ -41,6 +42,7 @@ def wsgiapp(wrapped): return wraps(wrapped)(decorator) return wraps(wrapped, ('__module__', '__doc__'))(decorator) + def wsgiapp2(wrapped): """ Decorator to turn a WSGI application into a :app:`Pyramid` view callable. This decorator differs from the |
