diff options
Diffstat (limited to 'src')
39 files changed, 389 insertions, 823 deletions
diff --git a/src/pyramid/asset.py b/src/pyramid/asset.py index 0d7575a85..a32babe6c 100644 --- a/src/pyramid/asset.py +++ b/src/pyramid/asset.py @@ -1,13 +1,11 @@ import os import pkg_resources -from pyramid.compat import string_types - from pyramid.path import package_path, package_name def resolve_asset_spec(spec, pname='__main__'): - if pname and not isinstance(pname, string_types): + if pname and not isinstance(pname, str): pname = pname.__name__ # as package if os.path.isabs(spec): return None, spec diff --git a/src/pyramid/authentication.py b/src/pyramid/authentication.py index 7cb6b6811..21cfc0c0e 100644 --- a/src/pyramid/authentication.py +++ b/src/pyramid/authentication.py @@ -6,28 +6,18 @@ import hashlib import base64 import re import time as time_mod +from urllib.parse import quote, unquote import warnings from zope.interface import implementer from webob.cookies import CookieProfile -from pyramid.compat import ( - long, - text_type, - binary_type, - url_unquote, - url_quote, - bytes_, - ascii_native_, - native_, -) - from pyramid.interfaces import IAuthenticationPolicy, IDebugLogger from pyramid.security import Authenticated, Everyone -from pyramid.util import strings_differ +from pyramid.util import strings_differ, bytes_, ascii_, text_ from pyramid.util import SimpleSerializer VALID_TOKEN = re.compile(r"^[A-Za-z][A-Za-z0-9+_-]*$") @@ -727,11 +717,7 @@ class AuthTicket(object): ) 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), quote(self.userid)) if self.tokens: v += self.tokens + '!' v += self.user_data @@ -759,7 +745,7 @@ def parse_ticket(secret, ticket, ip, hashalg='md5'): If the ticket cannot be parsed, a ``BadTicket`` exception will be raised with an explanation. """ - ticket = native_(ticket).strip('"') + ticket = text_(ticket).strip('"') digest_size = hashlib.new(hashalg).digest_size * 2 digest = ticket[:digest_size] try: @@ -770,7 +756,7 @@ def parse_ticket(secret, ticket, ip, hashalg='md5'): userid, data = ticket[digest_size + 8 :].split('!', 1) except ValueError: raise BadTicket('userid is not followed by !') - userid = url_unquote(userid) + userid = unquote(userid) if '!' in data: tokens, user_data = data.split('!', 1) else: # pragma: no cover (never generated) @@ -857,9 +843,8 @@ class AuthTktCookieHelper(object): 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)), + str: ('b64unicode', lambda x: b64encode(utf_8_encode(x)[0])), + bytes: ('b64str', lambda x: b64encode(x)), } def __init__( @@ -879,16 +864,13 @@ class AuthTktCookieHelper(object): domain=None, samesite='Lax', ): - - serializer = SimpleSerializer() - self.cookie_profile = CookieProfile( cookie_name=cookie_name, secure=secure, max_age=max_age, httponly=http_only, path=path, - serializer=serializer, + serializer=SimpleSerializer(), samesite=samesite, ) @@ -1048,7 +1030,7 @@ class AuthTktCookieHelper(object): "type provided.".format(type(userid)), RuntimeWarning, ) - encoding, encoder = self.userid_type_encoders.get(text_type) + encoding, encoder = self.userid_type_encoders.get(str) userid = str(userid) userid = encoder(userid) @@ -1056,9 +1038,9 @@ class AuthTktCookieHelper(object): new_tokens = [] for token in tokens: - if isinstance(token, text_type): + if isinstance(token, str): try: - token = ascii_native_(token) + token = ascii_(token) except UnicodeEncodeError: raise ValueError("Invalid token %r" % (token,)) if not (isinstance(token, str) and VALID_TOKEN.match(token)): diff --git a/src/pyramid/authorization.py b/src/pyramid/authorization.py index 974748765..6056a8d25 100644 --- a/src/pyramid/authorization.py +++ b/src/pyramid/authorization.py @@ -4,10 +4,10 @@ from pyramid.interfaces import IAuthorizationPolicy from pyramid.location import lineage -from pyramid.compat import is_nonstr_iter - from pyramid.security import ACLAllowed, ACLDenied, Allow, Deny, Everyone +from pyramid.util import is_nonstr_iter + @implementer(IAuthorizationPolicy) class ACLAuthorizationPolicy(object): diff --git a/src/pyramid/compat.py b/src/pyramid/compat.py deleted file mode 100644 index 31832c874..000000000 --- a/src/pyramid/compat.py +++ /dev/null @@ -1,326 +0,0 @@ -import inspect -import platform -import sys -import types - -WIN = platform.system() == 'Windows' - -try: # pragma: no cover - import __pypy__ - - PYPY = True -except BaseException: # pragma: no cover - __pypy__ = None - PYPY = False - -try: - import cPickle as pickle -except ImportError: # pragma: no cover - import pickle - -try: - from functools import lru_cache -except ImportError: - from repoze.lru import lru_cache - -# PY3 is left as bw-compat but PY2 should be used for most checks. -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 - -if PY2: - 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,) - 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``""" - if isinstance(s, binary_type): - 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``""" - if isinstance(s, text_type): - 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')`` - -Python 2: If ``s`` is an instance of ``text_type``, return -``s.encode('ascii')``, otherwise return ``str(s)`` -""" - - -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)``""" - if isinstance(s, text_type): - 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)`` - -Python 2: If ``s`` is an instance of ``text_type``, return -``s.encode(encoding, errors)``, otherwise return ``str(s)`` -""" - -if PY2: - import urlparse - from urllib import quote as url_quote - from urllib import quote_plus as url_quote_plus - from urllib import unquote as url_unquote - 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 - v = url_unquote(v) - return v.decode(encoding, errors) - - 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: - frame = sys._getframe(1) - globs = frame.f_globals - if locs is None: - locs = frame.f_locals - del frame - elif locs is None: - locs = globs - exec("""exec code in globs, locs""") - - 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): - if value is None: - value = tp - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - - del builtins - - -if PY2: # pragma: no cover - - def iteritems_(d): - return d.iteritems() - - def itervalues_(d): - return d.itervalues() - - def iterkeys_(d): - return d.iterkeys() - - -else: # pragma: no cover - - def iteritems_(d): - return d.items() - - def itervalues_(d): - return d.values() - - def iterkeys_(d): - return d.keys() - - -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' -else: - im_func = '__func__' - im_self = '__self__' - -try: - import configparser -except ImportError: - import ConfigParser as configparser - -try: - from http.cookies import SimpleCookie -except ImportError: - from Cookie import SimpleCookie - -if PY2: - from cgi import escape -else: - from html import escape - -if PY2: - input_ = raw_input -else: - input_ = input - -if PY2: - from io import BytesIO as NativeIO -else: - from io import StringIO as NativeIO - -# "json" is not an API; it's here to support older pyramid_debugtoolbar -# versions which attempt to import it -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 - from urllib.parse import unquote_to_bytes - - def unquote_bytes_to_wsgi(bytestring): - return unquote_to_bytes(bytestring).decode('latin-1') - - -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 -else: - from inspect import getfullargspec as getargspec - -if PY2: - from itertools import izip_longest as zip_longest -else: - from itertools import zip_longest - - -def is_unbound_method(fn): - """ - This consistently verifies that the callable is bound to a - class. - """ - is_bound = is_bound_method(fn) - - if not is_bound and inspect.isroutine(fn): - spec = getargspec(fn) - has_self = len(spec.args) > 0 and spec.args[0] == 'self' - - if PY2 and inspect.ismethod(fn): - return True - elif inspect.isfunction(fn) and has_self: - return True - - return False diff --git a/src/pyramid/config/__init__.py b/src/pyramid/config/__init__.py index 475f0d9a2..072b654c4 100644 --- a/src/pyramid/config/__init__.py +++ b/src/pyramid/config/__init__.py @@ -19,8 +19,6 @@ from pyramid.asset import resolve_asset_spec from pyramid.authorization import ACLAuthorizationPolicy -from pyramid.compat import text_, string_types - from pyramid.events import ApplicationCreated from pyramid.exceptions import ConfigurationError @@ -37,7 +35,7 @@ from pyramid.settings import aslist from pyramid.threadlocal import manager -from pyramid.util import WeakOrderedSet, object_description +from pyramid.util import WeakOrderedSet, get_callable_name, object_description from pyramid.config.actions import action_method, ActionState from pyramid.config.predicates import not_ @@ -59,7 +57,6 @@ from pyramid.config.zca import ZCAConfiguratorMixin from pyramid.path import DottedNameResolver -empty = text_('') _marker = object() not_ = not_ # api @@ -367,7 +364,7 @@ class Configurator( self._set_settings(settings) - if isinstance(debug_logger, string_types): + if isinstance(debug_logger, str): debug_logger = logging.getLogger(debug_logger) if debug_logger is None: @@ -489,11 +486,7 @@ class Configurator( if not hasattr(_registry, 'registerSelfAdapter'): def registerSelfAdapter( - required=None, - provided=None, - name=empty, - info=empty, - event=True, + required=None, provided=None, name='', info='', event=True ): return _registry.registerAdapter( lambda x: x, @@ -705,6 +698,7 @@ class Configurator( ``add_directive`` does not participate in conflict detection, and later calls to ``add_directive`` will override earlier calls. """ + name = get_callable_name(name) c = self.maybe_dotted(directive) if not hasattr(self.registry, '_directives'): self.registry._directives = {} @@ -759,7 +753,7 @@ class Configurator( when generating an absolute asset specification. If the provided ``relative_spec`` argument is already absolute, or if the ``relative_spec`` is not a string, it is simply returned.""" - if not isinstance(relative_spec, string_types): + if not isinstance(relative_spec, str): return relative_spec return self._make_spec(relative_spec) diff --git a/src/pyramid/config/actions.py b/src/pyramid/config/actions.py index 9c1227d4a..4a526e242 100644 --- a/src/pyramid/config/actions.py +++ b/src/pyramid/config/actions.py @@ -5,7 +5,6 @@ import sys import traceback from zope.interface import implementer -from pyramid.compat import reraise from pyramid.exceptions import ( ConfigurationConflictError, ConfigurationError, @@ -14,6 +13,7 @@ from pyramid.exceptions import ( from pyramid.interfaces import IActionInfo from pyramid.registry import undefer from pyramid.util import is_nonstr_iter +from pyramid.util import reraise class ActionConfiguratorMixin(object): diff --git a/src/pyramid/config/predicates.py b/src/pyramid/config/predicates.py index 31e770562..3eb07c17d 100644 --- a/src/pyramid/config/predicates.py +++ b/src/pyramid/config/predicates.py @@ -1,12 +1,12 @@ from hashlib import md5 from webob.acceptparse import Accept -from pyramid.compat import bytes_, is_nonstr_iter from pyramid.exceptions import ConfigurationError from pyramid.interfaces import IPredicateList, PHASE1_CONFIG from pyramid.predicates import Notted from pyramid.registry import predvalseq from pyramid.util import TopologicalSorter +from pyramid.util import is_nonstr_iter, bytes_ MAX_ORDER = 1 << 30 diff --git a/src/pyramid/config/routes.py b/src/pyramid/config/routes.py index 52540c935..4b26b7481 100644 --- a/src/pyramid/config/routes.py +++ b/src/pyramid/config/routes.py @@ -1,7 +1,7 @@ import contextlib +from urllib.parse import urlparse import warnings -from pyramid.compat import urlparse from pyramid.interfaces import ( IRequest, IRouteRequest, @@ -358,7 +358,7 @@ class RoutesConfiguratorMixin(object): # check for an external route; an external route is one which is # is a full url (e.g. 'http://example.com/{id}') - parsed = urlparse.urlparse(pattern) + parsed = urlparse(pattern) external_url = pattern if parsed.hostname: diff --git a/src/pyramid/config/testing.py b/src/pyramid/config/testing.py index bba5054e6..9c998840a 100644 --- a/src/pyramid/config/testing.py +++ b/src/pyramid/config/testing.py @@ -9,7 +9,7 @@ from pyramid.interfaces import ( from pyramid.renderers import RendererHelper -from pyramid.traversal import decode_path_info, split_path_info +from pyramid.traversal import split_path_info from pyramid.config.actions import action_method @@ -95,7 +95,7 @@ class TestingConfiguratorMixin(object): self.context = context def __call__(self, request): - path = decode_path_info(request.environ['PATH_INFO']) + path = request.path_info ob = resources[path] traversed = split_path_info(path) return { diff --git a/src/pyramid/config/tweens.py b/src/pyramid/config/tweens.py index 7fc786a97..c85639d14 100644 --- a/src/pyramid/config/tweens.py +++ b/src/pyramid/config/tweens.py @@ -2,13 +2,15 @@ from zope.interface import implementer from pyramid.interfaces import ITweens -from pyramid.compat import string_types, is_nonstr_iter - from pyramid.exceptions import ConfigurationError from pyramid.tweens import MAIN, INGRESS, EXCVIEW -from pyramid.util import is_string_or_iterable, TopologicalSorter +from pyramid.util import ( + is_nonstr_iter, + is_string_or_iterable, + TopologicalSorter, +) from pyramid.config.actions import action_method @@ -105,7 +107,7 @@ class TweensConfiguratorMixin(object): @action_method def _add_tween(self, tween_factory, under=None, over=None, explicit=False): - if not isinstance(tween_factory, string_types): + if not isinstance(tween_factory, str): raise ConfigurationError( 'The "tween_factory" argument to add_tween must be a ' 'dotted name to a globally importable object, not %r' diff --git a/src/pyramid/config/views.py b/src/pyramid/config/views.py index 0c4a17376..ac531ecb2 100644 --- a/src/pyramid/config/views.py +++ b/src/pyramid/config/views.py @@ -5,6 +5,7 @@ import operator import os import warnings +from urllib.parse import quote, urljoin, urlparse, urlunparse from webob.acceptparse import Accept from zope.interface import Interface, implementedBy, implementer from zope.interface.interfaces import IInterface @@ -32,13 +33,6 @@ from pyramid.interfaces import ( from pyramid import renderers from pyramid.asset import resolve_asset_spec -from pyramid.compat import ( - string_types, - urlparse, - url_quote, - WIN, - is_nonstr_iter, -) from pyramid.decorator import reify @@ -59,7 +53,12 @@ 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, + is_nonstr_iter, + TopologicalSorter, + WIN, +) import pyramid.predicates import pyramid.viewderivers @@ -83,9 +82,6 @@ from pyramid.config.predicates import ( 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 @@ -889,7 +885,7 @@ class ViewsConfiguratorMixin(object): if not IInterface.providedBy(r_context): r_context = implementedBy(r_context) - if isinstance(renderer, string_types): + if isinstance(renderer, str): renderer = renderers.RendererHelper( name=renderer, package=self.package, registry=self.registry ) @@ -1582,7 +1578,7 @@ class ViewsConfiguratorMixin(object): ): view = self.maybe_dotted(view) mapper = self.maybe_dotted(mapper) - if isinstance(renderer, string_types): + if isinstance(renderer, str): renderer = renderers.RendererHelper( name=renderer, package=self.package, registry=self.registry ) @@ -2197,14 +2193,12 @@ class StaticURLInfo(object): return request.route_url(route_name, **kw) else: app_url, qs, anchor = parse_url_overrides(request, kw) - parsed = url_parse(url) + parsed = urlparse(url) if not parsed.scheme: - url = urlparse.urlunparse( - parsed._replace( - scheme=request.environ['wsgi.url_scheme'] - ) + url = urlunparse( + parsed._replace(scheme=request.scheme) ) - subpath = url_quote(subpath) + subpath = quote(subpath) result = urljoin(url, subpath) return result + qs + anchor @@ -2233,7 +2227,7 @@ class StaticURLInfo(object): # make sure it ends with a slash name = name + '/' - if url_parse(name).netloc: + if urlparse(name).netloc: # it's a URL # url, spec, route_name url = name diff --git a/src/pyramid/csrf.py b/src/pyramid/csrf.py index fba5d9baa..26c628acc 100644 --- a/src/pyramid/csrf.py +++ b/src/pyramid/csrf.py @@ -1,14 +1,20 @@ +from urllib.parse import urlparse import uuid 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.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, + bytes_, + text_, +) @implementer(ICSRFStoragePolicy) @@ -117,7 +123,6 @@ class CookieCSRFStoragePolicy(object): path='/', samesite='Lax', ): - serializer = SimpleSerializer() self.cookie_profile = CookieProfile( cookie_name=cookie_name, secure=secure, @@ -125,7 +130,7 @@ class CookieCSRFStoragePolicy(object): httponly=httponly, path=path, domains=[domain], - serializer=serializer, + serializer=SimpleSerializer(), samesite=samesite, ) self.cookie_name = cookie_name @@ -303,7 +308,7 @@ def check_csrf_origin(request, trusted_origins=None, raises=True): # Parse our origin so we we can extract the required information from # it. - originp = urlparse.urlparse(origin) + originp = urlparse(origin) # Ensure that our Referer is also secure. if originp.scheme != "https": diff --git a/src/pyramid/encode.py b/src/pyramid/encode.py index 2cf2247da..ed8e177b8 100644 --- a/src/pyramid/encode.py +++ b/src/pyramid/encode.py @@ -1,17 +1,14 @@ -from pyramid.compat import ( - text_type, - binary_type, - is_nonstr_iter, - url_quote as _url_quote, - url_quote_plus as _quote_plus, -) +from urllib.parse import quote as _url_quote +from urllib.parse import quote_plus as _quote_plus + +from pyramid.util import is_nonstr_iter def url_quote(val, safe=''): # bw compat api cls = val.__class__ - if cls is text_type: + if cls is str: val = val.encode('utf-8') - elif cls is not binary_type: + elif cls is not bytes: val = str(val).encode('utf-8') return _url_quote(val, safe=safe) @@ -19,9 +16,9 @@ def url_quote(val, safe=''): # bw compat api # bw compat api (dnr) def quote_plus(val, safe=''): cls = val.__class__ - if cls is text_type: + if cls is str: val = val.encode('utf-8') - elif cls is not binary_type: + elif cls is not bytes: val = str(val).encode('utf-8') return _quote_plus(val, safe=safe) @@ -29,8 +26,8 @@ def quote_plus(val, safe=''): def urlencode(query, doseq=True, quote_via=quote_plus): """ An alternate implementation of Python's stdlib - :func:`urllib.parse.urlencode` function which accepts unicode keys and - values within the ``query`` dict/sequence; all Unicode keys and values are + :func:`urllib.parse.urlencode` function which accepts string keys and + values within the ``query`` dict/sequence; all string keys and values are first converted to UTF-8 before being used to compose the query string. The value of ``query`` must be a sequence of two-tuples diff --git a/src/pyramid/httpexceptions.py b/src/pyramid/httpexceptions.py index 959a45f37..56797dc88 100644 --- a/src/pyramid/httpexceptions.py +++ b/src/pyramid/httpexceptions.py @@ -137,22 +137,21 @@ 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.interfaces import IExceptionResponse from pyramid.response import Response +from pyramid.util import text_ def _no_escape(value): if value is None: return '' - if not isinstance(value, text_type): + if not isinstance(value, str): if hasattr(value, '__unicode__'): value = value.__unicode__() - if isinstance(value, binary_type): + if isinstance(value, bytes): value = text_(value, 'utf-8') else: - value = text_type(value) + value = str(value) return value @@ -326,7 +325,7 @@ ${body}''' args[k.lower()] = escape(v) body = body_tmpl.substitute(args) page = page_template.substitute(status=self.status, body=body) - if isinstance(page, text_type): + if isinstance(page, str): page = page.encode(self.charset if self.charset else 'UTF-8') self.app_iter = [page] self.body = page @@ -1331,7 +1330,7 @@ status_map = {} code = None for name, value in list(globals().items()): if ( - isinstance(value, class_types) + isinstance(value, type) and issubclass(value, HTTPException) and value not in {HTTPClientError, HTTPServerError} and not name.startswith('_') diff --git a/src/pyramid/i18n.py b/src/pyramid/i18n.py index e99a29aab..a20503be2 100644 --- a/src/pyramid/i18n.py +++ b/src/pyramid/i18n.py @@ -8,7 +8,6 @@ from translationstring import ( TranslationStringFactory, # API ) -from pyramid.compat import PY2 from pyramid.decorator import reify from pyramid.interfaces import ( @@ -46,12 +45,11 @@ class Localizer(object): ``translate`` method accepts three arguments: ``tstring`` (required), ``domain`` (optional) and ``mapping`` (optional). When called, it will translate the ``tstring`` translation - string to a ``unicode`` object using the current locale. If - the current locale could not be determined, the result of - interpolation of the default value is returned. The optional - ``domain`` argument can be used to specify or override the - domain of the ``tstring`` (useful when ``tstring`` is a normal - string rather than a translation string). The optional + string using the current locale. If the current locale could not be + determined, the result of interpolation of the default value is + returned. The optional ``domain`` argument can be used to specify + or override the domain of the ``tstring`` (useful when ``tstring`` + is a normal string rather than a translation string). The optional ``mapping`` argument can specify or override the ``tstring`` interpolation mapping, useful when the ``tstring`` argument is a simple string instead of a translation string. @@ -75,11 +73,11 @@ class Localizer(object): def pluralize(self, singular, plural, n, domain=None, mapping=None): """ - Return a Unicode string translation by using two + Return a string translation by using two :term:`message identifier` objects as a singular/plural pair and an ``n`` value representing the number that appears in the message using gettext plural forms support. The ``singular`` - and ``plural`` objects should be unicode strings. There is no + and ``plural`` objects should be strings. There is no reason to use translation string objects as arguments as all metadata is ignored. @@ -353,10 +351,7 @@ class Translations(gettext.GNUTranslations, object): """Like ``ugettext()``, but look the message up in the specified domain. """ - if PY2: - return self._domains.get(domain, self).ugettext(message) - else: - return self._domains.get(domain, self).gettext(message) + 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 @@ -374,14 +369,7 @@ class Translations(gettext.GNUTranslations, object): """Like ``ungettext()`` but look the message up in the specified domain. """ - if PY2: - return self._domains.get(domain, self).ungettext( - singular, plural, num - ) - else: - return self._domains.get(domain, self).ngettext( - singular, plural, num - ) + return self._domains.get(domain, self).ngettext(singular, plural, num) class LocalizerRequestMixin(object): diff --git a/src/pyramid/interfaces.py b/src/pyramid/interfaces.py index 31bcd7e88..f1e238c6b 100644 --- a/src/pyramid/interfaces.py +++ b/src/pyramid/interfaces.py @@ -1,7 +1,5 @@ from zope.interface import Attribute, Interface -from pyramid.compat import PY2 - # public API interfaces @@ -366,19 +364,6 @@ class IDict(Interface): def values(): """ Return a list of values from the dictionary """ - if PY2: - - def iterkeys(): - """ Return an iterator of keys from the dictionary """ - - def iteritems(): - """ Return an iterator of (k,v) pairs from the dictionary """ - - def itervalues(): - """ Return an iterator of values from the dictionary """ - - has_key = __contains__ - def pop(k, default=None): """ Pop the key k from the dictionary and return its value. If k doesn't exist, and default is provided, return the default. If k diff --git a/src/pyramid/path.py b/src/pyramid/path.py index c70be99db..47877ce5d 100644 --- a/src/pyramid/path.py +++ b/src/pyramid/path.py @@ -7,8 +7,6 @@ from zope.interface import implementer 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] @@ -101,7 +99,7 @@ class Resolver(object): if package in (None, CALLER_PACKAGE): self.package = package else: - if isinstance(package, string_types): + if isinstance(package, str): try: __import__(package) except ImportError: @@ -307,7 +305,7 @@ class DottedNameResolver(Resolver): v = r.resolve('xml') # v is the xml module """ - if not isinstance(dotted, string_types): + if not isinstance(dotted, str): raise ValueError('%r is not a string' % (dotted,)) package = self.package if package is CALLER_PACKAGE: @@ -328,7 +326,7 @@ class DottedNameResolver(Resolver): v = r.maybe_resolve(xml) # v is the xml module; no exception raised """ - if isinstance(dotted, string_types): + if isinstance(dotted, str): package = self.package if package is CALLER_PACKAGE: package = caller_package() diff --git a/src/pyramid/predicates.py b/src/pyramid/predicates.py index 280f6c03c..5a1127fb3 100644 --- a/src/pyramid/predicates.py +++ b/src/pyramid/predicates.py @@ -2,8 +2,6 @@ import re from pyramid.exceptions import ConfigurationError -from pyramid.compat import is_nonstr_iter - from pyramid.csrf import check_csrf_token from pyramid.traversal import ( find_interface, @@ -12,7 +10,7 @@ from pyramid.traversal import ( ) from pyramid.urldispatch import _compile_route -from pyramid.util import as_sorted_tuple, object_description +from pyramid.util import as_sorted_tuple, is_nonstr_iter, object_description _marker = object() diff --git a/src/pyramid/registry.py b/src/pyramid/registry.py index c24125830..7b2547dd7 100644 --- a/src/pyramid/registry.py +++ b/src/pyramid/registry.py @@ -4,15 +4,12 @@ import threading from zope.interface import implementer 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.path import CALLER_PACKAGE, caller_package -empty = text_('') - class Registry(Components, dict): """ A registry object is an :term:`application registry`. @@ -77,7 +74,7 @@ class Registry(Components, dict): return result def registerSelfAdapter( - self, required=None, provided=None, name=empty, info=empty, event=True + self, required=None, provided=None, name='', info='', event=True ): # registerAdapter analogue which always returns the object itself # when required is matched diff --git a/src/pyramid/renderers.py b/src/pyramid/renderers.py index a8e3ec16f..832369fd4 100644 --- a/src/pyramid/renderers.py +++ b/src/pyramid/renderers.py @@ -8,8 +8,6 @@ from zope.interface.registry import Components from pyramid.interfaces import IJSONAdapter, IRendererFactory, IRendererInfo -from pyramid.compat import string_types, text_type - from pyramid.csrf import get_csrf_token from pyramid.decorator import reify @@ -169,7 +167,7 @@ def get_renderer(renderer_name, package=None, registry=None): def string_renderer_factory(info): def _render(value, system): - if not isinstance(value, string_types): + if not isinstance(value, str): value = str(value) request = system.get('request') if request is not None: @@ -485,7 +483,7 @@ class RendererHelper(object): response = response_factory(request) if result is not None: - if isinstance(result, text_type): + if isinstance(result, str): response.text = result elif isinstance(result, bytes): response.body = result diff --git a/src/pyramid/request.py b/src/pyramid/request.py index 907b4477f..23c00468d 100644 --- a/src/pyramid/request.py +++ b/src/pyramid/request.py @@ -13,14 +13,17 @@ from pyramid.interfaces import ( ISessionFactory, ) -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.url import URLMethodsMixin -from pyramid.util import InstancePropertyHelper, InstancePropertyMixin +from pyramid.util import ( + InstancePropertyHelper, + InstancePropertyMixin, + text_, + bytes_, +) from pyramid.view import ViewMethodsMixin @@ -281,7 +284,7 @@ def call_app_with_subpath_as_path_info(request, app): # compute new_path_info new_path_info = '/' + '/'.join( - [native_(x.encode('utf-8'), 'latin-1') for x in subpath] + [text_(x.encode('utf-8'), 'latin-1') for x in subpath] ) if new_path_info != '/': # don't want a sole double-slash @@ -328,7 +331,7 @@ def apply_request_extensions(request, extensions=None): if extensions is None: extensions = request.registry.queryUtility(IRequestExtensions) if extensions is not None: - for name, fn in iteritems_(extensions.methods): + for name, fn in extensions.methods.items(): method = fn.__get__(request, request.__class__) setattr(request, name, method) diff --git a/src/pyramid/response.py b/src/pyramid/response.py index 38f9fa1ce..ea4677226 100644 --- a/src/pyramid/response.py +++ b/src/pyramid/response.py @@ -100,14 +100,12 @@ class FileIter(object): def __iter__(self): return self - def next(self): + def __next__(self): val = self.file.read(self.block_size) if not val: raise StopIteration return val - __next__ = next # py3 - def close(self): self.file.close() @@ -214,8 +212,4 @@ def _guess_type(path): 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 - # on Windows where mimetypes.guess_type returns unicode for the - # content_type. - content_type = str(content_type) return content_type, content_encoding diff --git a/src/pyramid/scripts/prequest.py b/src/pyramid/scripts/prequest.py index e8f5ff8b3..eb2032419 100644 --- a/src/pyramid/scripts/prequest.py +++ b/src/pyramid/scripts/prequest.py @@ -2,8 +2,8 @@ import base64 import argparse import sys import textwrap +from urllib.parse import unquote -from pyramid.compat import url_unquote from pyramid.request import Request from pyramid.scripts.common import get_config_loader from pyramid.scripts.common import parse_vars @@ -152,7 +152,7 @@ class PRequestCommand(object): except ValueError: qs = '' - path = url_unquote(path) + path = unquote(path) headers = {} if self.args.login: diff --git a/src/pyramid/scripts/proutes.py b/src/pyramid/scripts/proutes.py index 2bce7d1de..78c2295d5 100644 --- a/src/pyramid/scripts/proutes.py +++ b/src/pyramid/scripts/proutes.py @@ -7,7 +7,6 @@ import re from zope.interface import Interface from pyramid.paster import bootstrap -from pyramid.compat import string_types from pyramid.interfaces import IRouteRequest from pyramid.config import not_ @@ -188,7 +187,7 @@ def get_route_data(route, registry): view_request_methods[view_module] = [] view_request_methods_order.append(view_module) - if isinstance(request_method, string_types): + if isinstance(request_method, str): request_method = (request_method,) elif isinstance(request_method, not_): request_method = ('!%s' % request_method.value,) diff --git a/src/pyramid/scripts/pserve.py b/src/pyramid/scripts/pserve.py index 581479d65..7d68521a4 100644 --- a/src/pyramid/scripts/pserve.py +++ b/src/pyramid/scripts/pserve.py @@ -19,8 +19,6 @@ import webbrowser import hupper -from pyramid.compat import PY2 - from pyramid.scripts.common import get_config_loader from pyramid.scripts.common import parse_vars from pyramid.path import AssetResolver @@ -380,18 +378,15 @@ def cherrypy_server_runner( 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 - else: - # creates wsgiserver.ssl_builtin as side-effect - try: - from cheroot.server import get_ssl_adapter_class - from cheroot.ssl.builtin import BuiltinSSLAdapter - except ImportError: - from cherrypy.wsgiserver import get_ssl_adapter_class - from cherrypy.wsgiserver.ssl_builtin import BuiltinSSLAdapter - get_ssl_adapter_class() - server.ssl_adapter = BuiltinSSLAdapter(ssl_pem, ssl_pem) + # creates wsgiserver.ssl_builtin as side-effect + try: + from cheroot.server import get_ssl_adapter_class + from cheroot.ssl.builtin import BuiltinSSLAdapter + except ImportError: + from cherrypy.wsgiserver import get_ssl_adapter_class + from cherrypy.wsgiserver.ssl_builtin import BuiltinSSLAdapter + get_ssl_adapter_class() + server.ssl_adapter = BuiltinSSLAdapter(ssl_pem, ssl_pem) if protocol_version: server.protocol = protocol_version diff --git a/src/pyramid/scripts/pshell.py b/src/pyramid/scripts/pshell.py index e63114d18..a9f02e408 100644 --- a/src/pyramid/scripts/pshell.py +++ b/src/pyramid/scripts/pshell.py @@ -6,7 +6,6 @@ import sys import textwrap import pkg_resources -from pyramid.compat import exec_ from pyramid.util import DottedNameResolver from pyramid.util import make_contextmanager from pyramid.paster import bootstrap @@ -214,7 +213,7 @@ class PShellCommand(object): if self.pystartup and os.path.isfile(self.pystartup): with open(self.pystartup, 'rb') as fp: - exec_(fp.read().decode('utf-8'), env) + exec(fp.read().decode('utf-8'), env) if '__builtins__' in env: del env['__builtins__'] diff --git a/src/pyramid/scripts/pviews.py b/src/pyramid/scripts/pviews.py index 891dc4709..d2a4bfa40 100644 --- a/src/pyramid/scripts/pviews.py +++ b/src/pyramid/scripts/pviews.py @@ -70,7 +70,7 @@ class PViewsCommand(object): def _find_multi_routes(self, mapper, request): infos = [] - path = request.environ['PATH_INFO'] + path = request.path_info # find all routes that match path, regardless of predicates for route in mapper.get_routes(): match = route.match(path) diff --git a/src/pyramid/security.py b/src/pyramid/security.py index 08ae295d8..61819588b 100644 --- a/src/pyramid/security.py +++ b/src/pyramid/security.py @@ -8,7 +8,6 @@ from pyramid.interfaces import ( IViewClassifier, ) -from pyramid.compat import map_ from pyramid.threadlocal import get_current_registry Everyone = 'system.Everyone' @@ -113,7 +112,7 @@ def 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 + string, if an :term:`authorization policy` is in effect, return a sequence of :term:`principal` ids that possess the permission in the ``context``. If no authorization policy is in effect, this will return a sequence with the single value @@ -149,7 +148,7 @@ def view_execution_permitted(context, request, name=''): """ reg = _get_registry(request) - provides = [IViewClassifier] + map_(providedBy, (request, context)) + provides = [IViewClassifier] + [providedBy(x) for x in (request, context)] # XXX not sure what to do here about using _find_views or analogue; # for now let's just keep it as-is view = reg.adapters.lookup(provides, ISecuredView, name=name) @@ -341,7 +340,7 @@ class AuthorizationAPIMixin(object): ``request.context`` attribute. :param permission: Does this request have the given permission? - :type permission: unicode, str + :type permission: str :param context: A resource object or ``None`` :type context: object :returns: Either :class:`pyramid.security.Allowed` or diff --git a/src/pyramid/session.py b/src/pyramid/session.py index 68e0c506c..70ac4f55f 100644 --- a/src/pyramid/session.py +++ b/src/pyramid/session.py @@ -1,5 +1,6 @@ import binascii import os +import pickle import time from zope.deprecation import deprecated @@ -7,11 +8,12 @@ from zope.interface import implementer 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.interfaces import ISession +from pyramid.util import text_, bytes_ + def manage_accessed(wrapped): """ Decorator which causes a cookie to be renewed when an accessor @@ -255,12 +257,6 @@ def BaseCookieSessionFactory( __len__ = manage_accessed(dict.__len__) __iter__ = manage_accessed(dict.__iter__) - if PY2: - iteritems = manage_accessed(dict.iteritems) - itervalues = manage_accessed(dict.itervalues) - iterkeys = manage_accessed(dict.iterkeys) - has_key = manage_accessed(dict.has_key) - # modifying dictionary methods clear = manage_changed(dict.clear) update = manage_changed(dict.update) @@ -309,7 +305,7 @@ def BaseCookieSessionFactory( exception is not None ): # dont set a cookie during exceptions return False - cookieval = native_( + cookieval = text_( serializer.dumps((self.accessed, self.created, dict(self))) ) if len(cookieval) > 4064: diff --git a/src/pyramid/settings.py b/src/pyramid/settings.py index af9433840..d1eb4ff14 100644 --- a/src/pyramid/settings.py +++ b/src/pyramid/settings.py @@ -1,5 +1,3 @@ -from pyramid.compat import string_types - truthy = frozenset(('t', 'true', 'y', 'yes', 'on', '1')) falsey = frozenset(('f', 'false', 'n', 'no', 'off', '0')) @@ -17,7 +15,7 @@ def asbool(s): def aslist_cronly(value): - if isinstance(value, string_types): + if isinstance(value, str): value = filter(None, [x.strip() for x in value.splitlines()]) return list(value) diff --git a/src/pyramid/static.py b/src/pyramid/static.py index 58ad97a46..e3561e93e 100644 --- a/src/pyramid/static.py +++ b/src/pyramid/static.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from functools import lru_cache import json import os @@ -8,8 +9,6 @@ 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 @@ -18,8 +17,6 @@ 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 @@ -91,7 +88,7 @@ class static_view(object): if self.use_subpath: path_tuple = request.subpath else: - path_tuple = traversal_path_info(request.environ['PATH_INFO']) + path_tuple = traversal_path_info(request.path_info) path = _secure_path(path_tuple) if path is None: @@ -160,7 +157,7 @@ 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 = '/'.join(path_tuple) # will be unicode return encoded diff --git a/src/pyramid/testing.py b/src/pyramid/testing.py index f700b5a4e..6831ea4e2 100644 --- a/src/pyramid/testing.py +++ b/src/pyramid/testing.py @@ -8,8 +8,6 @@ from zope.interface import implementer, alsoProvides from pyramid.interfaces import IRequest, ISession -from pyramid.compat import PY3, PYPY, class_types, text_ - from pyramid.config import Configurator from pyramid.decorator import reify from pyramid.path import caller_package @@ -28,7 +26,7 @@ from pyramid.threadlocal import get_current_registry, manager from pyramid.i18n import LocalizerRequestMixin from pyramid.request import CallbackMethodsMixin from pyramid.url import URLMethodsMixin -from pyramid.util import InstancePropertyMixin +from pyramid.util import InstancePropertyMixin, PYPY, text_ from pyramid.view import ViewMethodsMixin @@ -640,11 +638,9 @@ def skip_on(*platforms): # pragma: no cover skip = True if platform == 'pypy' and PYPY: skip = True - if platform == 'py3' and PY3: - skip = True def decorator(func): - if isinstance(func, class_types): + if isinstance(func, type): if skip: return None else: diff --git a/src/pyramid/traversal.py b/src/pyramid/traversal.py index 338b49083..9ed5754b7 100644 --- a/src/pyramid/traversal.py +++ b/src/pyramid/traversal.py @@ -1,3 +1,6 @@ +from functools import lru_cache +from urllib.parse import unquote_to_bytes + from zope.interface import implementer from zope.interface.interfaces import IInterface @@ -8,29 +11,15 @@ from pyramid.interfaces import ( VH_ROOT_KEY, ) -from pyramid.compat import ( - PY2, - native_, - text_, - ascii_native_, - text_type, - binary_type, - is_nonstr_iter, - 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 +from pyramid.util import ascii_, is_nonstr_iter, text_ 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`` @@ -68,16 +57,12 @@ def find_resource(resource, path): object supplied to the function as the ``resource`` argument. If an empty string is passed as ``path``, the ``resource`` passed in will be returned. Resource path strings must be escaped in the following - manner: each Unicode path segment must be encoded as UTF-8 and as - each path segment must escaped via Python's :mod:`urllib.quote`. - For example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or - ``to%20the/La%20Pe%C3%B1a`` (relative). The + manner: each path segment must be UTF-8 encoded and escaped via Python's + :mod:`urllib.quote`. For example, ``/path/to%20the/La%20Pe%C3%B1a`` + (absolute) or ``to%20the/La%20Pe%C3%B1a`` (relative). The :func:`pyramid.traversal.resource_path` function generates strings - which follow these rules (albeit only absolute ones). - - Rules for passing *text* (Unicode) as the ``path`` argument are the same - as those for a string. In particular, the text may not have any nonascii - characters in it. + which follow these rules (albeit only absolute ones). The text may not + have any non-ASCII characters in it. Rules for passing a *tuple* as the ``path`` argument: if the first element in the path tuple is the empty string (for example ``('', @@ -88,14 +73,13 @@ def find_resource(resource, path): traversal will begin at the resource object supplied to the function as the ``resource`` argument. If an empty sequence is passed as ``path``, the ``resource`` passed in itself will be returned. No - URL-quoting or UTF-8-encoding of individual path segments within - the tuple is required (each segment may be any string or unicode - object representing a resource name). Resource path tuples generated by - :func:`pyramid.traversal.resource_path_tuple` can always be - resolved by ``find_resource``. + URL-quoting of individual path segments within the tuple is required + (each segment may be any string representing a resource name). Resource + path tuples generated by :func:`pyramid.traversal.resource_path_tuple` can + always be resolved by ``find_resource``. """ - if isinstance(path, text_type): - path = ascii_native_(path) + if isinstance(path, str): + path = ascii_(path) D = traverse(resource, path) view_name = D['view_name'] context = D['context'] @@ -137,8 +121,8 @@ def resource_path(resource, *elements): ``/`` character in a path string represents that the path is absolute). Resource path strings returned will be escaped in the following - manner: each unicode path segment will be encoded as UTF-8 and - each path segment will be escaped via Python's :mod:`urllib.quote`. + manner: each path segment will be encoded as UTF-8 and escaped via + Python's :mod:`urllib.quote`. For example, ``/path/to%20the/La%20Pe%C3%B1a``. This function is a logical inverse of @@ -151,7 +135,7 @@ def resource_path(resource, *elements): Each segment in the path string returned will use the ``__name__`` attribute of the resource it represents within the resource tree. Each - of these segments *should* be a unicode or string object (as per the + of these segments *should* be a string (as per the contract of :term:`location`-awareness). However, no conversion or safety checking of resource names is performed. For instance, if one of the resources in your tree has a ``__name__`` which (by error) is a @@ -187,47 +171,49 @@ def traverse(resource, path): A definition of each value in the returned dictionary: - ``context``: The :term:`context` (a :term:`resource` object) found - via traversal or url dispatch. If the ``path`` passed in is the + via traversal or URL dispatch. If the ``path`` passed in is the empty string, the value of the ``resource`` argument passed to this function is returned. - ``root``: The resource object at which :term:`traversal` begins. - If the ``resource`` passed in was found via url dispatch or if the + If the ``resource`` passed in was found via URL dispatch or if the ``path`` passed in was relative (non-absolute), the value of the ``resource`` argument passed to this function is returned. - ``view_name``: The :term:`view name` found during - :term:`traversal` or :term:`url dispatch`; if the ``resource`` was + :term:`traversal` or :term:`URL dispatch`; if the ``resource`` was found via traversal, this is usually a representation of the path segment which directly follows the path to the ``context`` - in the ``path``. The ``view_name`` will be a Unicode object or - the empty string. The ``view_name`` will be the empty string if + in the ``path``. The ``view_name`` will be a string. The + ``view_name`` will be the empty string if there is no element which follows the ``context`` path. An example: if the path passed is ``/foo/bar``, and a resource object is found at ``/foo`` (but not at ``/foo/bar``), the 'view - name' will be ``u'bar'``. If the ``resource`` was found via - urldispatch, the view_name will be the name the route found was - registered with. + name' will be ``'bar'``. If the ``resource`` was found via + URL dispatch, the ``view_name`` will be the empty string unless + the ``traverse`` predicate was specified or the ``*traverse`` route + pattern was used, at which point normal traversal rules dictate the + result. - ``subpath``: For a ``resource`` found via :term:`traversal`, this is a sequence of path segments found in the ``path`` that follow - the ``view_name`` (if any). Each of these items is a Unicode - object. If no path segments follow the ``view_name``, the + the ``view_name`` (if any). Each of these items is a string. + If no path segments follow the ``view_name``, the subpath will be the empty sequence. An example: if the path passed is ``/foo/bar/baz/buz``, and a resource object is found at ``/foo`` (but not ``/foo/bar``), the 'view name' will be - ``u'bar'`` and the :term:`subpath` will be ``[u'baz', u'buz']``. - For a ``resource`` found via url dispatch, the subpath will be a + ``'bar'`` and the :term:`subpath` will be ``['baz', 'buz']``. + For a ``resource`` found via URL dispatch, the subpath will be a sequence of values discerned from ``*subpath`` in the route pattern matched or the empty sequence. - ``traversed``: The sequence of path elements traversed from the root to find the ``context`` object during :term:`traversal`. - Each of these items is a Unicode object. If no path segments + Each of these items is a string. If no path segments were traversed to find the ``context`` object (e.g. if the ``path`` provided is the empty string), the ``traversed`` value will be the empty sequence. If the ``resource`` is a resource found - via :term:`url dispatch`, traversed will be None. + via :term:`URL dispatch`, traversed will be None. - ``virtual_root``: A resource object representing the 'virtual' root of the resource tree being traversed during :term:`traversal`. @@ -243,10 +229,10 @@ def traverse(resource, path): - ``virtual_root_path`` -- If :term:`traversal` was used to find the ``resource``, this will be the sequence of path elements traversed to find the ``virtual_root`` resource. Each of these - items is a Unicode object. If no path segments were traversed + items is a string. If no path segments were traversed to find the ``virtual_root`` resource (e.g. if virtual hosting is not in effect), the ``traversed`` value will be the empty list. - If url dispatch was used to find the ``resource``, this will be + If URL dispatch was used to find the ``resource``, this will be ``None``. If the path cannot be resolved, a :exc:`KeyError` will be raised. @@ -260,9 +246,9 @@ def traverse(resource, path): object supplied to the function as the ``resource`` argument. If an empty string is passed as ``path``, the ``resource`` passed in will be returned. Resource path strings must be escaped in the following - manner: each Unicode path segment must be encoded as UTF-8 and - each path segment must escaped via Python's :mod:`urllib.quote`. - For example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or + manner: each path segment must be encoded as UTF-8 and escaped via + Python's :mod:`urllib.quote`. For example, + ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or ``to%20the/La%20Pe%C3%B1a`` (relative). The :func:`pyramid.traversal.resource_path` function generates strings which follow these rules (albeit only absolute ones). @@ -277,26 +263,24 @@ def traverse(resource, path): as the ``resource`` argument. If an empty sequence is passed as ``path``, the ``resource`` passed in itself will be returned. No URL-quoting or UTF-8-encoding of individual path segments within - the tuple is required (each segment may be any string or unicode - object representing a resource name). + the tuple is required (each segment may be any string representing + a resource name). - Explanation of the conversion of ``path`` segment values to - Unicode during traversal: Each segment is URL-unquoted, and - decoded into Unicode. Each segment is assumed to be encoded using - the UTF-8 encoding (or a subset, such as ASCII); a + Explanation of the decoding of ``path`` segment values during traversal: + Each segment is URL-unquoted, and UTF-8 decoded. Each segment is assumed + to be encoded using the UTF-8 encoding (or a subset, such as ASCII); a :exc:`pyramid.exceptions.URLDecodeError` is raised if a segment cannot be decoded. If a segment name is empty or if it is ``.``, it is ignored. If a segment name is ``..``, the previous segment is deleted, and the ``..`` is ignored. As a result of this process, the return values ``view_name``, each element in the ``subpath``, each element in ``traversed``, and each element in - the ``virtual_root_path`` will be Unicode as opposed to a string, - and will be URL-decoded. + the ``virtual_root_path`` will be decoded strings. """ if is_nonstr_iter(path): - # the traverser factory expects PATH_INFO to be a string, not - # unicode and it expects path segments to be utf-8 and + # the traverser factory expects PATH_INFO to be a string and it + # expects path segments to be utf-8 and # urlencoded (it's the same traverser which accepts PATH_INFO # from user agents; user agents always send strings). if path: @@ -312,7 +296,7 @@ def traverse(resource, path): # step rather than later down the line as the result of calling # ``traversal_path``). - path = ascii_native_(path) + path = ascii_(path) if path and path[0] == '/': resource = find_root(resource) @@ -357,7 +341,7 @@ def resource_path_tuple(resource, *elements): Each segment in the path tuple returned will equal the ``__name__`` attribute of the resource it represents within the resource tree. Each - of these segments *should* be a unicode or string object (as per the + of these segments *should* be a string (as per the contract of :term:`location`-awareness). However, no conversion or safety checking of resource names is performed. For instance, if one of the resources in your tree has a ``__name__`` which (by error) is a @@ -439,14 +423,13 @@ def traversal_path(path): """ Variant of :func:`pyramid.traversal.traversal_path_info` suitable for decoding paths that are URL-encoded. - If this function is passed a Unicode object instead of a sequence of - bytes as ``path``, that Unicode object *must* directly encodeable to - ASCII. For example, u'/foo' will work but u'/<unprintable unicode>' (a - Unicode object with characters that cannot be encoded to ascii) will - not. A :exc:`UnicodeEncodeError` will be raised if the Unicode cannot be + If this function is passed a string, it *must* be directly encodeable to + ASCII. For example, '/foo' will work but '/<unprintable unicode>' (a + string object with characters that cannot be encoded to ASCII) will + not. A :exc:`UnicodeEncodeError` will be raised if the string cannot be encoded directly to ASCII. """ - if isinstance(path, text_type): + if isinstance(path, str): # must not possess characters outside ascii path = path.encode('ascii') # we unquote this path exactly like a PEP 3333 server would @@ -461,18 +444,11 @@ def traversal_path_info(path): already-URL-decoded ``str`` type as if it had come to us from an upstream WSGI server as the ``PATH_INFO`` environ variable. - The ``path`` is first decoded to from its WSGI representation to Unicode; - it is decoded differently depending on platform: - - - On Python 2, ``path`` is decoded to Unicode from bytes using the UTF-8 - decoding directly; a :exc:`pyramid.exc.URLDecodeError` is raised if a the - URL cannot be decoded. - - - On Python 3, as per the PEP 3333 spec, ``path`` is first encoded to - bytes using the Latin-1 encoding; the resulting set of bytes is - subsequently decoded to text using the UTF-8 encoding; a - :exc:`pyramid.exc.URLDecodeError` is raised if a the URL cannot be - decoded. + The ``path`` is first decoded from its WSGI representation to text. + Per the :pep:`3333` spec, ``path`` is first encoded to bytes using the + Latin-1 encoding; the resulting set of bytes is subsequently decoded to + text using the UTF-8 encoding; a :exc:`pyramid.exc.URLDecodeError` is + raised if the URL cannot be decoded. The ``path`` is split on slashes, creating a list of segments. If a segment name is empty or if it is ``.``, it is ignored. If a segment @@ -487,31 +463,31 @@ def traversal_path_info(path): ``/foo/bar/baz`` - (u'foo', u'bar', u'baz') + ('foo', 'bar', 'baz') ``foo/bar/baz`` - (u'foo', u'bar', u'baz') + ('foo', 'bar', 'baz') ``/foo/bar/baz/`` - (u'foo', u'bar', u'baz') + ('foo', 'bar', 'baz') ``/foo//bar//baz/`` - (u'foo', u'bar', u'baz') + ('foo', 'bar', 'baz') ``/foo/bar/baz/..`` - (u'foo', u'bar') + ('foo', 'bar') ``/my%20archives/hello`` - (u'my archives', u'hello') + ('my archives', 'hello') ``/archives/La%20Pe%C3%B1a`` - (u'archives', u'<unprintable unicode>') + ('archives', '<unprintable unicode>') .. note:: @@ -549,83 +525,60 @@ def split_path_info(path): return tuple(clean) +# see PEP 3333 for why we encode to latin-1 then decode to utf-8 +def decode_path_info(path): + return path.encode('latin-1').decode('utf-8') + + +# see PEP 3333 for why we decode the path to latin-1 +def unquote_bytes_to_wsgi(bytestring): + return unquote_to_bytes(bytestring).decode('latin-1') + + _segment_cache = {} -quote_path_segment_doc = """ \ -Return a quoted representation of a 'path segment' (such as -the string ``__name__`` attribute of a resource) as a string. If the -``segment`` passed in is a unicode object, it is converted to a -UTF-8 string, then it is URL-quoted using Python's -``urllib.quote``. If the ``segment`` passed in is a string, it is -URL-quoted using Python's :mod:`urllib.quote`. If the segment -passed in is not a string or unicode object, an error will be -raised. The return value of ``quote_path_segment`` is always a -string, never Unicode. - -You may pass a string of characters that need not be encoded as -the ``safe`` argument to this function. This corresponds to the -``safe`` argument to :mod:`urllib.quote`. - -.. note:: - - The return value for each segment passed to this - function is cached in a module-scope dictionary for - speed: the cached version is returned when possible - rather than recomputing the quoted version. No cache - emptying is ever done for the lifetime of an - application, however. If you pass arbitrary - user-supplied strings to this function (as opposed to - some bounded set of values from a 'working set' known to - your application), it may become a memory leak. -""" - - -if PY2: - # special-case on Python 2 for speed? unchecked - 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 - # optimization: we cache all the computation of URL path segments - # in this module-scope dictionary with the original string (or - # unicode value) as the key, so we can look it up later without - # needing to reencode or re-url-quote it - try: - return _segment_cache[(segment, safe)] - except KeyError: - 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) - # we don't need a lock to mutate _segment_cache, as the below - # 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 - # optimization: we cache all the computation of URL path segments - # in this module-scope dictionary with the original string (or - # unicode value) as the key, so we can look it up later without - # needing to reencode or re-url-quote it - try: - return _segment_cache[(segment, safe)] - except KeyError: - if segment.__class__ not in (text_type, binary_type): - segment = str(segment) - result = url_quote(native_(segment, 'utf-8'), safe) - # we don't need a lock to mutate _segment_cache, as the below - # will generate exactly one Python bytecode (STORE_SUBSCR) - _segment_cache[(segment, safe)] = result - return result - - -slash = text_('/') + +def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE): + """ + Return a quoted representation of a 'path segment' (such as + the string ``__name__`` attribute of a resource) as a string. If the + ``segment`` passed in is a bytes object, it is decoded as a UTF-8 string. + The result is then URL-quoted using Python's ``urllib.quote``. + If the segment passed in is not bytes nor a string, an error will be + raised. The return value of ``quote_path_segment`` is always a string. + + You may pass a string of characters that need not be encoded as + the ``safe`` argument to this function. This corresponds to the + ``safe`` argument to :mod:`urllib.quote`. + + .. note:: + + The return value for each segment passed to this + function is cached in a module-scope dictionary for + speed: the cached version is returned when possible + rather than recomputing the quoted version. No cache + emptying is ever done for the lifetime of an + application, however. If you pass arbitrary + user-supplied strings to this function (as opposed to + some bounded set of values from a 'working set' known to + your application), it may become a memory leak. + + """ + # The bit of this code that deals with ``_segment_cache`` is an + # optimization: we cache all the computation of URL path segments + # in this module-scope dictionary with the original string as the + # key, so we can look it up later without needing to reencode + # or re-url-quote it + try: + if segment.__class__ not in (str, bytes): + segment = str(segment) + return _segment_cache[(segment, safe)] + except KeyError: + result = url_quote(text_(segment, 'utf-8'), safe) + # we don't need a lock to mutate _segment_cache, as the below + # will generate exactly one Python bytecode (STORE_SUBSCR) + _segment_cache[(segment, safe)] = result + return result @implementer(ITraverser) @@ -647,12 +600,12 @@ class ResourceTreeTraverser(object): if matchdict is not None: - path = matchdict.get('traverse', slash) or slash + path = matchdict.get('traverse', '/') or '/' if is_nonstr_iter(path): # this is a *traverse stararg (not a {traverse}) # routing has already decoded these elements, so we just # need to join them - path = '/' + slash.join(path) or slash + path = '/' + '/'.join(path) or '/' subpath = matchdict.get('subpath', ()) if not is_nonstr_iter(subpath): @@ -666,10 +619,10 @@ class ResourceTreeTraverser(object): subpath = () try: # empty if mounted under a path in mod_wsgi, for example - path = request.path_info or slash + path = request.path_info or '/' except KeyError: # if environ['PATH_INFO'] is just not there - path = slash + path = '/' except UnicodeDecodeError as e: raise URLDecodeError( e.encoding, e.object, e.start, e.end, e.reason @@ -691,7 +644,7 @@ class ResourceTreeTraverser(object): root = self.root ob = vroot = root - if vpath == slash: # invariant: vpath must not be empty + if vpath == '/': # 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 = () @@ -745,7 +698,7 @@ class ResourceTreeTraverser(object): return { 'context': ob, - 'view_name': empty, + 'view_name': '', 'subpath': subpath, 'traversed': vpath_tuple, 'virtual_root': vroot, diff --git a/src/pyramid/tweens.py b/src/pyramid/tweens.py index 839c53b8f..b5660b44b 100644 --- a/src/pyramid/tweens.py +++ b/src/pyramid/tweens.py @@ -1,7 +1,7 @@ import sys -from pyramid.compat import reraise from pyramid.httpexceptions import HTTPNotFound +from pyramid.util import reraise def _error_handler(request, exc): diff --git a/src/pyramid/url.py b/src/pyramid/url.py index 00dd13bfe..22551a349 100644 --- a/src/pyramid/url.py +++ b/src/pyramid/url.py @@ -1,13 +1,14 @@ """ Utility functions for dealing with URLs in pyramid """ +from functools import lru_cache import os 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.path import caller_package from pyramid.threadlocal import get_current_registry +from pyramid.util import bytes_ from pyramid.traversal import ( ResourceURL, @@ -45,7 +46,7 @@ def parse_url_overrides(request, kw): qs = '' if query: - if isinstance(query, string_types): + if isinstance(query, str): qs = '?' + url_quote(query, QUERY_SAFE) else: qs = '?' + urlencode(query, doseq=True) diff --git a/src/pyramid/urldispatch.py b/src/pyramid/urldispatch.py index de8a69d2a..73b7be9f3 100644 --- a/src/pyramid/urldispatch.py +++ b/src/pyramid/urldispatch.py @@ -3,21 +3,12 @@ from zope.interface import implementer from pyramid.interfaces import IRoutesMapper, IRoute -from pyramid.compat import ( - PY2, - native_, - text_, - text_type, - string_types, - 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.util import is_nonstr_iter, text_ + _marker = object() @@ -82,10 +73,9 @@ class RoutesMapper(object): return self.routes[name].generate(kw) def __call__(self, request): - environ = request.environ try: # empty if mounted under a path in mod_wsgi, for example - path = decode_path_info(environ['PATH_INFO'] or '/') + path = request.path_info or '/' except KeyError: path = '/' except UnicodeDecodeError as e: @@ -127,7 +117,7 @@ def _compile_route(route): # using the ASCII decoding. We decode it using ASCII because we don't # want to accept bytestrings with high-order characters in them here as # we have no idea what the encoding represents. - if route.__class__ is not text_type: + if route.__class__ is not str: try: route = text_(route, 'ascii') except UnicodeDecodeError: @@ -174,7 +164,7 @@ def _compile_route(route): name, reg = name.split(':', 1) else: reg = '[^/]+' - gen.append('%%(%s)s' % native_(name)) # native + gen.append('%%(%s)s' % name) # native name = '(?P<%s>%s)' % (name, reg) # unicode rpat.append(name) s = pat.pop() # unicode @@ -189,34 +179,22 @@ def _compile_route(route): if remainder: rpat.append('(?P<%s>.*?)' % remainder) # unicode - gen.append('%%(%s)s' % native_(remainder)) # native + gen.append('%%(%s)s' % remainder) # native 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 - # to Unicode using the ASCII decoding. We decode it using ASCII - # because we don't want to accept bytestrings with high-order - # characters in them here as we have no idea what the encoding - # represents. - if path.__class__ is not text_type: - path = text_(path, 'ascii') m = match(path) if m is None: return None d = {} for k, v in m.groupdict().items(): - # k and v will be Unicode 2.6.4 and lower doesnt accept unicode - # kwargs as **kw, so we explicitly cast the keys to native - # strings in case someone wants to pass the result as **kw - nk = native_(k, 'ascii') if k == remainder: - d[nk] = split_path_info(v) + d[k] = split_path_info(v) else: - d[nk] = v + d[k] = v return d gen = ''.join(gen) @@ -227,27 +205,21 @@ def _compile_route(route): def generator(dict): newdict = {} for k, v in dict.items(): - if PY2: - if v.__class__ is text_type: - # url_quote below needs bytes, not unicode on Py2 - v = v.encode('utf-8') - else: - if v.__class__ is binary_type: - # url_quote below needs a native string, not bytes on Py3 - v = v.decode('utf-8') + if v.__class__ is bytes: + # url_quote below needs a native string + v = v.decode('utf-8') if k == remainder: # a stararg argument if is_nonstr_iter(v): v = '/'.join([q(x) for x in v]) # native else: - if v.__class__ not in string_types: + if v.__class__ is not str: v = str(v) v = q(v) else: - if v.__class__ not in string_types: + if v.__class__ is not str: v = str(v) - # v may be bytes (py2) or native string (py3) v = q(v) # at this point, the value will be a native string diff --git a/src/pyramid/util.py b/src/pyramid/util.py index bebf9e7d3..e552b37de 100644 --- a/src/pyramid/util.py +++ b/src/pyramid/util.py @@ -1,32 +1,24 @@ from contextlib import contextmanager import functools - -try: - # py2.7.7+ and py3.3+ have native comparison support - from hmac import compare_digest -except ImportError: # pragma: no cover - compare_digest = None +from hmac import compare_digest import inspect +import platform import weakref -from pyramid.exceptions import ConfigurationError, CyclicDependencyError - -from pyramid.compat import ( - getargspec, - im_func, - is_nonstr_iter, - integer_types, - string_types, - bytes_, - text_, - PY2, - native_, -) - from pyramid.path import DottedNameResolver as _DottedNameResolver _marker = object() +WIN = platform.system() == 'Windows' + +try: # pragma: no cover + import __pypy__ + + PYPY = True +except BaseException: # pragma: no cover + __pypy__ = None + PYPY = False + class DottedNameResolver(_DottedNameResolver): def __init__( @@ -35,8 +27,40 @@ class DottedNameResolver(_DottedNameResolver): _DottedNameResolver.__init__(self, package) +def text_(s, encoding='latin-1', errors='strict'): + """ If ``s`` is an instance of ``bytes``, return + ``s.decode(encoding, errors)``, otherwise return ``s``""" + if isinstance(s, bytes): + return s.decode(encoding, errors) + return s + + +def bytes_(s, encoding='latin-1', errors='strict'): + """ If ``s`` is an instance of ``str``, return + ``s.encode(encoding, errors)``, otherwise return ``s``""" + if isinstance(s, str): + return s.encode(encoding, errors) + return s + + +def ascii_(s): + """ + If ``s`` is an instance of ``str``, return + ``s.encode('ascii')``, otherwise return ``str(s, 'ascii', 'strict')`` + """ + if isinstance(s, str): + s = s.encode('ascii') + return str(s, 'ascii', 'strict') + + +def is_nonstr_iter(v): + if isinstance(v, str): + return False + return hasattr(v, '__iter__') + + def is_string_or_iterable(v): - if isinstance(v, string_types): + if isinstance(v, str): return True if hasattr(v, '__iter__'): return True @@ -277,7 +301,7 @@ class WeakOrderedSet(object): return self._items[oid]() -def strings_differ(string1, string2, compare_digest=compare_digest): +def strings_differ(string1, string2): """Check whether two strings differ while avoiding timing attacks. This function returns True if the given strings differ and False @@ -301,11 +325,7 @@ def strings_differ(string1, string2, compare_digest=compare_digest): left = string2 right = string2 - if compare_digest is not None: - invalid_bits += not compare_digest(left, right) - else: - for a, b in zip(left, right): - invalid_bits += a != b + invalid_bits += not compare_digest(left, right) return invalid_bits != 0 @@ -314,14 +334,14 @@ def object_description(object): usually involving a Python dotted name. For example: >>> object_description(None) - u'None' + 'None' >>> from xml.dom import minidom >>> object_description(minidom) - u'module xml.dom.minidom' + 'module xml.dom.minidom' >>> object_description(minidom.Attr) - u'class xml.dom.minidom.Attr' + 'class xml.dom.minidom.Attr' >>> object_description(minidom.Attr.appendChild) - u'method appendChild of class xml.dom.minidom.Attr' + 'method appendChild of class xml.dom.minidom.Attr' If this method cannot identify the type of the object, a generic description ala ``object <object.__name__>`` will be returned. @@ -330,17 +350,14 @@ def object_description(object): is a boolean, an integer, a list, a tuple, a set, or ``None``, a (possibly shortened) string representation is returned. """ - if isinstance(object, string_types): - return text_(object) - if isinstance(object, integer_types): - return text_(str(object)) + if isinstance(object, str): + return object + if isinstance(object, int): + return str(object) if isinstance(object, (bool, float, type(None))): - return text_(str(object)) + return str(object) if isinstance(object, set): - if PY2: - return shortrepr(object, ')') - else: - return shortrepr(object, '}') + return shortrepr(object, '}') if isinstance(object, tuple): return shortrepr(object, ')') if isinstance(object, list): @@ -349,26 +366,25 @@ def object_description(object): return shortrepr(object, '}') module = inspect.getmodule(object) if module is None: - return text_('object %s' % str(object)) + return 'object %s' % str(object) modulename = module.__name__ if inspect.ismodule(object): - return text_('module %s' % modulename) + return 'module %s' % modulename if inspect.ismethod(object): oself = getattr(object, '__self__', None) - 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 'method %s of class %s.%s' % ( + object.__name__, + modulename, + oself.__class__.__name__, ) if inspect.isclass(object): dottedname = '%s.%s' % (modulename, object.__name__) - return text_('class %s' % dottedname) + return 'class %s' % dottedname if inspect.isfunction(object): dottedname = '%s.%s' % (modulename, object.__name__) - return text_('function %s' % dottedname) - return text_('object %s' % str(object)) + return 'function %s' % dottedname + return 'object %s' % str(object) def shortrepr(object, closer): @@ -499,11 +515,17 @@ class TopologicalSorter(object): has_after.add(b) if not self.req_before.issubset(has_before): + # avoid circular dependency + from pyramid.exceptions import ConfigurationError + raise ConfigurationError( 'Unsatisfied before dependencies: %s' % (', '.join(sorted(self.req_before - has_before))) ) if not self.req_after.issubset(has_after): + # avoid circular dependency + from pyramid.exceptions import ConfigurationError + raise ConfigurationError( 'Unsatisfied after dependencies: %s' % (', '.join(sorted(self.req_after - has_after))) @@ -524,6 +546,9 @@ class TopologicalSorter(object): del graph[root] if graph: + # avoid circular dependency + from pyramid.exceptions import CyclicDependencyError + # loop in input cycledeps = {} for k, v in graph.items(): @@ -545,8 +570,11 @@ def get_callable_name(name): if it is not. """ try: - return native_(name, 'ascii') + return ascii_(name) except (UnicodeEncodeError, UnicodeDecodeError): + # avoid circular dependency + from pyramid.exceptions import ConfigurationError + msg = ( '`name="%s"` is invalid. `name` must be ascii because it is ' 'used on __name__ of the method' @@ -615,10 +643,7 @@ def takes_one_arg(callee, attr=None, argname=None): if inspect.isroutine(callee): fn = callee elif inspect.isclass(callee): - try: - fn = callee.__init__ - except AttributeError: - return False + fn = callee.__init__ ismethod = hasattr(fn, '__call__') else: try: @@ -626,15 +651,11 @@ def takes_one_arg(callee, attr=None, argname=None): except AttributeError: return False - try: - argspec = getargspec(fn) - except TypeError: - return False - + argspec = inspect.getfullargspec(fn) args = argspec[0] - if hasattr(fn, im_func) or ismethod: - # it's an instance method (or unbound method on py2) + if hasattr(fn, '__func__') or ismethod: + # it's an instance method if not args: return False args = args[1:] @@ -660,7 +681,40 @@ def takes_one_arg(callee, attr=None, argname=None): class SimpleSerializer(object): def loads(self, bstruct): - return native_(bstruct) + return text_(bstruct) def dumps(self, appstruct): return bytes_(appstruct) + + +def is_bound_method(ob): + return inspect.ismethod(ob) and getattr(ob, '__self__', None) is not None + + +def is_unbound_method(fn): + """ + This consistently verifies that the callable is bound to a + class. + """ + is_bound = is_bound_method(fn) + + if not is_bound and inspect.isroutine(fn): + spec = inspect.getfullargspec(fn) + has_self = len(spec.args) > 0 and spec.args[0] == 'self' + + if inspect.isfunction(fn) and has_self: + return True + + return False + + +def reraise(tp, value, tb=None): + try: + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None + tb = None diff --git a/src/pyramid/view.py b/src/pyramid/view.py index 9f58e72ae..944ad93ea 100644 --- a/src/pyramid/view.py +++ b/src/pyramid/view.py @@ -15,9 +15,6 @@ from pyramid.interfaces import ( IExceptionViewClassifier, ) -from pyramid.compat import decode_path_info -from pyramid.compat import reraise as reraise_ - from pyramid.exceptions import ConfigurationError, PredicateMismatch from pyramid.httpexceptions import ( @@ -29,6 +26,7 @@ from pyramid.httpexceptions import ( from pyramid.threadlocal import get_current_registry, manager from pyramid.util import hide_attrs +from pyramid.util import reraise as reraise_ _marker = object() @@ -305,7 +303,7 @@ class AppendSlashNotFoundViewFactory(object): self.redirect_class = redirect_class def __call__(self, context, request): - path = decode_path_info(request.environ['PATH_INFO'] or '/') + path = request.path_info registry = request.registry mapper = registry.queryUtility(IRoutesMapper) if mapper is not None and not path.endswith('/'): diff --git a/src/pyramid/viewderivers.py b/src/pyramid/viewderivers.py index fbe0c252c..181cc9e5c 100644 --- a/src/pyramid/viewderivers.py +++ b/src/pyramid/viewderivers.py @@ -17,11 +17,14 @@ from pyramid.interfaces import ( IViewMapperFactory, ) -from pyramid.compat import is_bound_method, is_unbound_method - 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, + is_bound_method, + is_unbound_method, +) from pyramid.view import render_view_to_response from pyramid import renderers |
