From 0c29cf2df41600d3906d521c72991c7686018b71 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 15 Oct 2018 01:55:54 -0500 Subject: format source using black --- src/pyramid/asset.py | 19 +- src/pyramid/authentication.py | 346 +++++++++++++---------- src/pyramid/authorization.py | 42 ++- src/pyramid/compat.py | 63 ++++- src/pyramid/config/__init__.py | 305 +++++++++++--------- src/pyramid/config/adapters.py | 77 ++--- src/pyramid/config/assets.py | 67 +++-- src/pyramid/config/factories.py | 95 ++++--- src/pyramid/config/i18n.py | 31 +- src/pyramid/config/predicates.py | 1 + src/pyramid/config/rendering.py | 31 +- src/pyramid/config/routes.py | 144 +++++----- src/pyramid/config/security.py | 110 +++++--- src/pyramid/config/settings.py | 4 + src/pyramid/config/testing.py | 58 ++-- src/pyramid/config/tweens.py | 45 ++- src/pyramid/config/util.py | 35 +-- src/pyramid/config/views.py | 563 ++++++++++++++++++++----------------- src/pyramid/config/zca.py | 4 +- src/pyramid/csrf.py | 64 +++-- src/pyramid/decorator.py | 2 +- src/pyramid/encode.py | 7 +- src/pyramid/events.py | 37 ++- src/pyramid/exceptions.py | 22 +- src/pyramid/httpexceptions.py | 312 +++++++++++++++----- src/pyramid/i18n.py | 65 +++-- src/pyramid/interfaces.py | 369 +++++++++++++++++------- src/pyramid/location.py | 3 +- src/pyramid/paster.py | 5 +- src/pyramid/path.py | 41 ++- src/pyramid/predicates.py | 55 ++-- src/pyramid/registry.py | 70 +++-- src/pyramid/renderers.py | 109 +++---- src/pyramid/request.py | 53 ++-- src/pyramid/resource.py | 3 +- src/pyramid/response.py | 46 +-- src/pyramid/router.py | 63 +++-- src/pyramid/scaffolds/__init__.py | 17 +- src/pyramid/scaffolds/copydir.py | 169 +++++++---- src/pyramid/scaffolds/template.py | 51 ++-- src/pyramid/scaffolds/tests.py | 28 +- src/pyramid/scripting.py | 26 +- src/pyramid/scripts/common.py | 6 +- src/pyramid/scripts/pcreate.py | 188 ++++++++----- src/pyramid/scripts/pdistreport.py | 31 +- src/pyramid/scripts/prequest.py | 71 +++-- src/pyramid/scripts/proutes.py | 134 +++++---- src/pyramid/scripts/pserve.py | 107 ++++--- src/pyramid/scripts/pshell.py | 78 +++-- src/pyramid/scripts/ptweens.py | 42 +-- src/pyramid/scripts/pviews.py | 76 +++-- src/pyramid/security.py | 98 ++++--- src/pyramid/session.py | 99 ++++--- src/pyramid/settings.py | 3 + src/pyramid/static.py | 89 +++--- src/pyramid/testing.py | 145 ++++++---- src/pyramid/threadlocal.py | 12 +- src/pyramid/traversal.py | 141 ++++++---- src/pyramid/tweens.py | 3 + src/pyramid/url.py | 53 ++-- src/pyramid/urldispatch.py | 78 ++--- src/pyramid/util.py | 69 +++-- src/pyramid/view.py | 100 ++++--- src/pyramid/viewderivers.py | 171 +++++++---- src/pyramid/wsgi.py | 2 + 65 files changed, 3328 insertions(+), 2125 deletions(-) (limited to 'src') 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( - '', - acl, - permission, - principals, - context) + '', 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( + '''\ ${status} @@ -215,13 +217,21 @@ ${body}''')

${status}

${body} -''') +''' + ) ## 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 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
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 `_ 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='', - template_renderer=None): + +def substitute_content( + content, vars, filename='', 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.*?)}}') + 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[^\\]*?)\\}\\}') + +escaped_double_brace_pattern = re.compile( + r'\\{\\{(?P[^\\]*?)\\}\\}' +) + 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 ') + self.out( + 'You must provide at least one scaffold name: ' + '-s ' + ) 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 -- cgit v1.2.3 From 4d838f06d0e6d27e34b766fc5c34a8283ff31b38 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 15 Oct 2018 02:09:05 -0500 Subject: remove bare excepts --- src/pyramid/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/pyramid/compat.py b/src/pyramid/compat.py index 4df279e94..31832c874 100644 --- a/src/pyramid/compat.py +++ b/src/pyramid/compat.py @@ -9,7 +9,7 @@ try: # pragma: no cover import __pypy__ PYPY = True -except: # pragma: no cover +except BaseException: # pragma: no cover __pypy__ = None PYPY = False -- cgit v1.2.3 From a54bc1ccac17625991e26eb5d4577f893803c683 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Mon, 15 Oct 2018 03:02:45 -0500 Subject: fix lint on src --- src/pyramid/authentication.py | 22 ++++++++++----------- src/pyramid/config/__init__.py | 3 ++- src/pyramid/config/routes.py | 16 ++++++++-------- src/pyramid/config/tweens.py | 2 +- src/pyramid/config/util.py | 6 +++--- src/pyramid/config/views.py | 39 ++++++++++++++++++++------------------ src/pyramid/csrf.py | 4 ++-- src/pyramid/httpexceptions.py | 24 +++++++++++------------ src/pyramid/i18n.py | 12 ++++++------ src/pyramid/interfaces.py | 5 +++-- src/pyramid/location.py | 2 +- src/pyramid/paster.py | 2 +- src/pyramid/resource.py | 8 ++++---- src/pyramid/scaffolds/__init__.py | 2 +- src/pyramid/security.py | 10 +++++----- src/pyramid/traversal.py | 17 +++++++++-------- src/pyramid/url.py | 40 +++++++++++++++++++-------------------- src/pyramid/view.py | 9 +++++---- src/pyramid/viewderivers.py | 4 ++-- 19 files changed, 117 insertions(+), 110 deletions(-) (limited to 'src') diff --git a/src/pyramid/authentication.py b/src/pyramid/authentication.py index f4c2b51ef..7cb6b6811 100644 --- a/src/pyramid/authentication.py +++ b/src/pyramid/authentication.py @@ -143,8 +143,8 @@ class CallbackAuthenticationPolicy(object): 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, @@ -315,8 +315,8 @@ class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy): 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, @@ -339,8 +339,8 @@ class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy): 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, @@ -596,7 +596,7 @@ class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy): .. versionchanged:: 1.10 Added the ``samesite`` option and made the default ``'Lax'``. - + Objects of this class implement the interface described by :class:`pyramid.interfaces.IAuthenticationPolicy`. @@ -1044,8 +1044,8 @@ class AuthTktCookieHelper(object): "userid is of type {}, and is not supported by the " "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)), + "string as the userid, it will not be decoded back to the " + "type provided.".format(type(userid)), RuntimeWarning, ) encoding, encoder = self.userid_type_encoders.get(text_type) @@ -1154,8 +1154,8 @@ class BasicAuthAuthenticationPolicy(CallbackAuthenticationPolicy): ``realm`` - Default: ``"Realm"``. The Basic Auth Realm string. Usually displayed to - the user by the browser in the login dialog. + Default: ``"Realm"``. The Basic Auth Realm string. Usually displayed + to the user by the browser in the login dialog. ``debug`` diff --git a/src/pyramid/config/__init__.py b/src/pyramid/config/__init__.py index ab5edfefe..f5790352e 100644 --- a/src/pyramid/config/__init__.py +++ b/src/pyramid/config/__init__.py @@ -395,7 +395,8 @@ class Configurator( # commit below because: # # - the default exceptionresponse_view requires the superdefault view - # mapper, so we need to configure it before adding default_view_mapper + # mapper, so we need to configure it before adding + # default_view_mapper # # - superdefault renderers should be overrideable without requiring # the user to commit before calling config.add_renderer diff --git a/src/pyramid/config/routes.py b/src/pyramid/config/routes.py index 46b8921cd..4e1cb0762 100644 --- a/src/pyramid/config/routes.py +++ b/src/pyramid/config/routes.py @@ -158,8 +158,8 @@ class RoutesConfiguratorMixin(object): For backwards compatibility purposes (as of :app:`Pyramid` 1.0), a ``path`` keyword argument passed to this function will be used to represent the pattern value if the ``pattern`` argument is - ``None``. If both ``path`` and ``pattern`` are passed, ``pattern`` - wins. + ``None``. If both ``path`` and ``pattern`` are passed, + ``pattern`` wins. xhr @@ -298,9 +298,9 @@ 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 ' + '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 ' @@ -315,9 +315,9 @@ class RoutesConfiguratorMixin(object): 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, diff --git a/src/pyramid/config/tweens.py b/src/pyramid/config/tweens.py index a90008784..b74a57adf 100644 --- a/src/pyramid/config/tweens.py +++ b/src/pyramid/config/tweens.py @@ -56,7 +56,7 @@ class TweensConfiguratorMixin(object): - An iterable of any combination of the above. This allows the user to specify fallbacks if the desired tween is not included, as well as compatibility with multiple other tweens. - + ``under`` means 'closer to the main Pyramid application than', ``over`` means 'closer to the request ingress than'. diff --git a/src/pyramid/config/util.py b/src/pyramid/config/util.py index 627b78b6f..8723b7721 100644 --- a/src/pyramid/config/util.py +++ b/src/pyramid/config/util.py @@ -126,9 +126,9 @@ class PredicateList(object): def add(self, name, factory, weighs_more_than=None, weighs_less_than=None): # Predicates should be added to a predicate list in (presumed) # computation expense order. - ## if weighs_more_than is None and weighs_less_than is None: - ## weighs_more_than = self.last_added or FIRST - ## weighs_less_than = LAST + # if weighs_more_than is None and weighs_less_than is None: + # weighs_more_than = self.last_added or FIRST + # weighs_less_than = LAST self.last_added = name self.sorter.add( name, factory, after=weighs_more_than, before=weighs_less_than diff --git a/src/pyramid/config/views.py b/src/pyramid/config/views.py index 277b207fd..cc5b48ecb 100644 --- a/src/pyramid/config/views.py +++ b/src/pyramid/config/views.py @@ -728,8 +728,8 @@ class ViewsConfiguratorMixin(object): If CSRF checking is performed, the checked value will be the value of ``request.params[check_name]``. This value will be compared against the value of ``policy.get_csrf_token()`` (where ``policy`` is an - implementation of :meth:`pyramid.interfaces.ICSRFStoragePolicy`), and the - check will pass if these two values are the same. If the check + implementation of :meth:`pyramid.interfaces.ICSRFStoragePolicy`), and + the check will pass if these two values are the same. If the check passes, the associated view will be permitted to execute. If the check fails, the associated view will not be permitted to execute. @@ -808,13 +808,13 @@ 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, @@ -824,10 +824,10 @@ class ViewsConfiguratorMixin(object): 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.' + '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, @@ -842,9 +842,10 @@ class ViewsConfiguratorMixin(object): 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.' + '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, @@ -1804,7 +1805,8 @@ class ViewsConfiguratorMixin(object): config.add_notfound_view(append_slash=HTTPMovedPermanently) The above means that a redirect to a slash-appended route will be - attempted, but instead of :class:`~pyramid.httpexceptions.HTTPTemporaryRedirect` + attempted, but instead of + :class:`~pyramid.httpexceptions.HTTPTemporaryRedirect` being used, :class:`~pyramid.httpexceptions.HTTPMovedPermanently will be used` for the redirect response if a slash-appended route is found. @@ -1827,7 +1829,8 @@ class ViewsConfiguratorMixin(object): .. versionchanged: 1.10 - Default response was changed from :class:`~pyramid.httpexceptions.HTTPFound` + Default response was changed from + :class:`~pyramid.httpexceptions.HTTPFound` to :class:`~pyramid.httpexceptions.HTTPTemporaryRedirect`. """ diff --git a/src/pyramid/csrf.py b/src/pyramid/csrf.py index 582d6a641..fba5d9baa 100644 --- a/src/pyramid/csrf.py +++ b/src/pyramid/csrf.py @@ -247,8 +247,8 @@ def check_csrf_origin(request, trusted_origins=None, raises=True): Check the ``Origin`` of the request to see if it is a cross site request or not. - If the value supplied by the ``Origin`` or ``Referer`` header isn't one of the - trusted origins and ``raises`` is ``True``, this function will raise a + If the value supplied by the ``Origin`` or ``Referer`` header isn't one of + the trusted origins and ``raises`` is ``True``, this function will raise a :exc:`pyramid.exceptions.BadCSRFOrigin` exception, but if ``raises`` is ``False``, this function will return ``False`` instead. If the CSRF origin checks are successful this function will return ``True`` unconditionally. diff --git a/src/pyramid/httpexceptions.py b/src/pyramid/httpexceptions.py index 359601399..959a45f37 100644 --- a/src/pyramid/httpexceptions.py +++ b/src/pyramid/httpexceptions.py @@ -158,8 +158,7 @@ def _no_escape(value): @implementer(IExceptionResponse) class HTTPException(Response, Exception): - - ## You should set in subclasses: + # You should set in subclasses: # code = 200 # title = 'OK' # explanation = 'why this happens' @@ -187,7 +186,8 @@ class HTTPException(Response, Exception): # implies that this class' ``exception`` property always returns # ``self`` (it exists only for bw compat at this point). # - # - documentation improvements (Pyramid-specific docstrings where necessary) + # - documentation improvements (Pyramid-specific docstrings where + # necessary) # code = 520 title = 'Unknown Error' @@ -220,7 +220,7 @@ ${body}''' ''' ) - ## Set this to True for responses that should have no request body + # Set this to True for responses that should have no request body empty_body = False def __init__( @@ -381,7 +381,7 @@ class HTTPSuccessful(HTTPException): ############################################################ -## 2xx success +# 2xx success ############################################################ @@ -488,10 +488,10 @@ class HTTPPartialContent(HTTPSuccessful): title = 'Partial Content' -## FIXME: add 207 Multi-Status (but it's complicated) +# FIXME: add 207 Multi-Status (but it's complicated) ############################################################ -## 3xx redirection +# 3xx redirection ############################################################ @@ -675,7 +675,7 @@ class HTTPPermanentRedirect(_HTTPMove): ############################################################ -## 4xx client error +# 4xx client error ############################################################ @@ -1067,7 +1067,7 @@ class HTTPUnprocessableEntity(HTTPClientError): code: 422, title: Unprocessable Entity """ - ## Note: from WebDAV + # Note: from WebDAV code = 422 title = 'Unprocessable Entity' explanation = 'Unable to process the contained instructions' @@ -1082,7 +1082,7 @@ class HTTPLocked(HTTPClientError): code: 423, title: Locked """ - ## Note: from WebDAV + # Note: from WebDAV code = 423 title = 'Locked' explanation = 'The resource is locked' @@ -1098,7 +1098,7 @@ class HTTPFailedDependency(HTTPClientError): code: 424, title: Failed Dependency """ - ## Note: from WebDAV + # Note: from WebDAV code = 424 title = 'Failed Dependency' explanation = ( @@ -1169,7 +1169,7 @@ class HTTPRequestHeaderFieldsTooLarge(HTTPClientError): ############################################################ -## 5xx Server Error +# 5xx Server Error ############################################################ # Response status codes beginning with the digit "5" indicate cases in # which the server is aware that it has erred or is incapable of diff --git a/src/pyramid/i18n.py b/src/pyramid/i18n.py index 9f5f898be..e99a29aab 100644 --- a/src/pyramid/i18n.py +++ b/src/pyramid/i18n.py @@ -82,13 +82,13 @@ class Localizer(object): and ``plural`` objects should be unicode strings. There is no reason to use translation string objects as arguments as all metadata is ignored. - + ``n`` represents the number of elements. ``domain`` is the translation domain to use to do the pluralization, and ``mapping`` is the interpolation mapping that should be used on the result. If the ``domain`` is not supplied, a default domain is used (usually ``messages``). - + Example:: num = 1 @@ -110,7 +110,7 @@ class Localizer(object): num, mapping={'num':num}) - + """ if self.pluralizer is None: self.pluralizer = Pluralizer(self.translations) @@ -127,7 +127,7 @@ def default_locale_negotiator(request): the request object (possibly set by a view or a listener for an :term:`event`). If the attribute exists and it is not ``None``, its value will be used. - + - Then it looks for the ``request.params['_LOCALE_']`` value. - Then it looks for the ``request.cookies['_LOCALE_']`` value. @@ -176,7 +176,7 @@ def get_locale_name(request): def make_localizer(current_locale_name, translation_directories): """ Create a :class:`pyramid.i18n.Localizer` object - corresponding to the provided locale name from the + corresponding to the provided locale name from the translations found in the list of translation directories.""" translations = Translations() translations._catalog = {} @@ -344,7 +344,7 @@ class Translations(gettext.GNUTranslations, object): return self._domains.get(domain, self).gettext(message) def ldgettext(self, domain, message): - """Like ``lgettext()``, but look the message up in the specified + """Like ``lgettext()``, but look the message up in the specified domain. """ return self._domains.get(domain, self).lgettext(message) diff --git a/src/pyramid/interfaces.py b/src/pyramid/interfaces.py index 26a9d7025..e2e211b67 100644 --- a/src/pyramid/interfaces.py +++ b/src/pyramid/interfaces.py @@ -1341,7 +1341,7 @@ class IIntrospectable(Interface): method = getattr(introspector, methodname) method((i.category_name, i.discriminator), (category_name, discriminator)) - """ + """ # noqa: E501 def __hash__(): @@ -1354,7 +1354,8 @@ class IIntrospectable(Interface): class IActionInfo(Interface): """ Class which provides code introspection capability associated with an - action. The ParserInfo class used by ZCML implements the same interface.""" + action. The ParserInfo class used by ZCML implements the same interface. + """ file = Attribute('Filename of action-invoking code as a string') line = Attribute( diff --git a/src/pyramid/location.py b/src/pyramid/location.py index b8542d1b9..d90aed0d1 100644 --- a/src/pyramid/location.py +++ b/src/pyramid/location.py @@ -48,7 +48,7 @@ def lineage(resource): Calling ``lineage(thing2)`` will return a generator. When we turn it into a list, we will get:: - + list(lineage(thing2)) [ , ] """ diff --git a/src/pyramid/paster.py b/src/pyramid/paster.py index eda479476..22c09e41a 100644 --- a/src/pyramid/paster.py +++ b/src/pyramid/paster.py @@ -87,7 +87,7 @@ def bootstrap(config_uri, request=None, options=None): for you if none is provided. You can mutate the request's ``environ`` later to setup a specific host/port/scheme/etc. - ``options`` Is passed to get_app for use as variable assignments like + ``options`` Is passed to get_app for use as variable assignments like {'http_port': 8080} and then use %(http_port)s in the config file. diff --git a/src/pyramid/resource.py b/src/pyramid/resource.py index 1454114fd..8ddf4c447 100644 --- a/src/pyramid/resource.py +++ b/src/pyramid/resource.py @@ -1,6 +1,6 @@ """ Backwards compatibility shim module (forever). """ -from pyramid.asset import * # b/w compat +from pyramid.asset import * # noqa 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 +resolve_resource_spec = resolve_asset_spec # noqa +resource_spec_from_abspath = asset_spec_from_abspath # noqa +abspath_from_resource_spec = abspath_from_asset_spec # noqa diff --git a/src/pyramid/scaffolds/__init__.py b/src/pyramid/scaffolds/__init__.py index 95c51a2d8..eff71d204 100644 --- a/src/pyramid/scaffolds/__init__.py +++ b/src/pyramid/scaffolds/__init__.py @@ -44,7 +44,7 @@ class PyramidTemplate(Template): Welcome to Pyramid. Sorry for the convenience. %(separator)s - """ + """ # noqa: E501 % {'separator': separator} ) diff --git a/src/pyramid/security.py b/src/pyramid/security.py index 9e256f73c..2e3896976 100644 --- a/src/pyramid/security.py +++ b/src/pyramid/security.py @@ -54,7 +54,7 @@ def has_permission(permission, context, request): """ A function that calls :meth:`pyramid.request.Request.has_permission` and returns its result. - + .. deprecated:: 1.5 Use :meth:`pyramid.request.Request.has_permission` instead. @@ -77,7 +77,7 @@ def authenticated_userid(request): """ A function that returns the value of the property :attr:`pyramid.request.Request.authenticated_userid`. - + .. deprecated:: 1.5 Use :attr:`pyramid.request.Request.authenticated_userid` instead. """ @@ -93,10 +93,10 @@ deprecated( def unauthenticated_userid(request): - """ + """ A function that returns the value of the property :attr:`pyramid.request.Request.unauthenticated_userid`. - + .. deprecated:: 1.5 Use :attr:`pyramid.request.Request.unauthenticated_userid` instead. """ @@ -115,7 +115,7 @@ def effective_principals(request): """ A function that returns the value of the property :attr:`pyramid.request.Request.effective_principals`. - + .. deprecated:: 1.5 Use :attr:`pyramid.request.Request.effective_principals` instead. """ diff --git a/src/pyramid/traversal.py b/src/pyramid/traversal.py index 322f57ed6..338b49083 100644 --- a/src/pyramid/traversal.py +++ b/src/pyramid/traversal.py @@ -369,8 +369,8 @@ def resource_path_tuple(resource, *elements): The :term:`root` resource *must* have a ``__name__`` attribute with a value of either ``None`` or the empty string for path tuples to be generated properly. If the root resource has a non-null ``__name__`` - attribute, its name will be the first element in the generated path tuple - rather than the empty string. + attribute, its name will be the first element in the generated path + tuple rather than the empty string. """ return tuple(_resource_path_list(resource, *elements)) @@ -379,7 +379,8 @@ 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""" + """ Implementation detail shared by resource_path and + resource_path_tuple""" path = [loc.__name__ or '' for loc in lineage(resource)] path.reverse() path.extend(elements) @@ -516,11 +517,11 @@ def traversal_path_info(path): This function does not generate the same type of tuples that :func:`pyramid.traversal.resource_path_tuple` does. In particular, the - leading empty string is not present in the tuple it returns, unlike tuples - returned by :func:`pyramid.traversal.resource_path_tuple`. As a result, - tuples generated by ``traversal_path`` are not resolveable by the - :func:`pyramid.traversal.find_resource` API. ``traversal_path`` is a - function mostly used by the internals of :app:`Pyramid` and by people + leading empty string is not present in the tuple it returns, unlike + tuples returned by :func:`pyramid.traversal.resource_path_tuple`. As a + result, tuples generated by ``traversal_path`` are not resolveable by + the :func:`pyramid.traversal.find_resource` API. ``traversal_path`` is + a function mostly used by the internals of :app:`Pyramid` and by people writing their own traversal machinery, as opposed to users writing applications in :app:`Pyramid`. """ diff --git a/src/pyramid/url.py b/src/pyramid/url.py index c6cf3da7f..00dd13bfe 100644 --- a/src/pyramid/url.py +++ b/src/pyramid/url.py @@ -172,10 +172,10 @@ class URLMethodsMixin(object): Python data structures that are passed as ``_query`` which are sequences or dictionaries are turned into a string under the same - rules as when run through :func:`urllib.urlencode` with the ``doseq`` - argument equal to ``True``. This means that sequences can be passed - as values, and a k=v pair will be placed into the query string for - each value. + rules as when run through :func:`urllib.urlencode` with the + ``doseq`` argument equal to ``True``. This means that sequences can + be passed as values, and a k=v pair will be placed into the query + string for each value. If a keyword argument ``_anchor`` is present, its string representation will be quoted per :rfc:`3986#section-3.5` and used as @@ -200,7 +200,7 @@ class URLMethodsMixin(object): ``_host='foo.com'``, and the URL that would have been generated without the host replacement is ``http://example.com/a``, the result will be ``http://foo.com/a``. - + Note that if ``_scheme`` is passed as ``https``, and ``_port`` is not passed, the ``_port`` value is assumed to have been passed as ``443``. Likewise, if ``_scheme`` is passed as ``http`` and @@ -363,10 +363,10 @@ class URLMethodsMixin(object): Python data structures that are passed as ``query`` which are sequences or dictionaries are turned into a string under the same - rules as when run through :func:`urllib.urlencode` with the ``doseq`` - argument equal to ``True``. This means that sequences can be passed - as values, and a k=v pair will be placed into the query string for - each value. + rules as when run through :func:`urllib.urlencode` with the + ``doseq`` argument equal to ``True``. This means that sequences can + be passed as values, and a k=v pair will be placed into the query + string for each value. If a keyword argument ``anchor`` is present, its string representation will be used as a named anchor in the generated URL @@ -390,7 +390,7 @@ class URLMethodsMixin(object): ``host='foo.com'``, and the URL that would have been generated without the host replacement is ``http://example.com/a``, the result will be ``http://foo.com/a``. - + If ``scheme`` is passed as ``https``, and an explicit ``port`` is not passed, the ``port`` value is assumed to have been passed as ``443``. Likewise, if ``scheme`` is passed as ``http`` and ``port`` is not @@ -415,11 +415,11 @@ class URLMethodsMixin(object): If the ``resource`` passed in has a ``__resource_url__`` method, it will be used to generate the URL (scheme, host, port, path) for the base resource which is operated upon by this function. - + .. seealso:: See also :ref:`overriding_resource_url_generation`. - + If ``route_name`` is passed, this function will delegate its URL production to the ``route_url`` function. Calling ``resource_url(someresource, 'element1', 'element2', query={'a':1}, @@ -472,21 +472,21 @@ class URLMethodsMixin(object): is passed, the ``__resource_url__`` method of the resource passed is ignored unconditionally. This feature is incompatible with resources which generate their own URLs. - + .. note:: - If the :term:`resource` used is the result of a :term:`traversal`, it - must be :term:`location`-aware. The resource can also be the context - of a :term:`URL dispatch`; contexts found this way do not need to be - location-aware. + If the :term:`resource` used is the result of a :term:`traversal`, + it must be :term:`location`-aware. The resource can also be the + context of a :term:`URL dispatch`; contexts found this way do not + need to be location-aware. .. note:: If a 'virtual root path' is present in the request environment (the value of the WSGI environ key ``HTTP_X_VHM_ROOT``), and the resource - was obtained via :term:`traversal`, the URL path will not include the - virtual root prefix (it will be stripped off the left hand side of - the generated URL). + was obtained via :term:`traversal`, the URL path will not include + the virtual root prefix (it will be stripped off the left hand side + of the generated URL). .. note:: diff --git a/src/pyramid/view.py b/src/pyramid/view.py index c45b976bb..9f58e72ae 100644 --- a/src/pyramid/view.py +++ b/src/pyramid/view.py @@ -198,14 +198,14 @@ class view_config(object): See the :py:func:`venusian.attach` function in Venusian for more information about the ``_depth`` and ``_category`` arguments. - + .. seealso:: - + See also :ref:`mapping_views_using_a_decorator_section` for details about using :class:`pyramid.view.view_config`. .. warning:: - + ``view_config`` will work ONLY on module top level members because of the limitation of ``venusian.Scanner.scan``. @@ -402,7 +402,8 @@ class notfound_view_config(object): return HTTPNotFound('not found') The above means that a redirect to a slash-appended route will be - attempted, but instead of :class:`~pyramid.httpexceptions.HTTPTemporaryRedirect` + attempted, but instead of + :class:`~pyramid.httpexceptions.HTTPTemporaryRedirect` being used, :class:`~pyramid.httpexceptions.HTTPMovedPermanently will be used` for the redirect response if a slash-appended route is found. diff --git a/src/pyramid/viewderivers.py b/src/pyramid/viewderivers.py index 0b4758554..fbe0c252c 100644 --- a/src/pyramid/viewderivers.py +++ b/src/pyramid/viewderivers.py @@ -48,8 +48,8 @@ class DefaultViewMapper(object): 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`' + 'Unbound method calls are not supported, please set the ' + 'class as your `view` and the method as your `attr`' ) ) -- cgit v1.2.3