summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMichael Merickel <github@m.merickel.org>2018-11-26 17:10:21 -0600
committerGitHub <noreply@github.com>2018-11-26 17:10:21 -0600
commit587fe72fae0efda3a860d37a1ea2449a41dab622 (patch)
treead938e23efd1be67821ddfb710748e746c92c420 /src
parenteea97ca673a53f8aa039a78e61833f78d5d59583 (diff)
parent81171e861d25d394c0ccb8a6139a9b89dc4f039c (diff)
downloadpyramid-587fe72fae0efda3a860d37a1ea2449a41dab622.tar.gz
pyramid-587fe72fae0efda3a860d37a1ea2449a41dab622.tar.bz2
pyramid-587fe72fae0efda3a860d37a1ea2449a41dab622.zip
Merge pull request #3421 from mmerickel/drop-py2
remove py2 from the codebase
Diffstat (limited to 'src')
-rw-r--r--src/pyramid/asset.py4
-rw-r--r--src/pyramid/authentication.py40
-rw-r--r--src/pyramid/authorization.py4
-rw-r--r--src/pyramid/compat.py326
-rw-r--r--src/pyramid/config/__init__.py16
-rw-r--r--src/pyramid/config/actions.py2
-rw-r--r--src/pyramid/config/predicates.py2
-rw-r--r--src/pyramid/config/routes.py4
-rw-r--r--src/pyramid/config/testing.py4
-rw-r--r--src/pyramid/config/tweens.py10
-rw-r--r--src/pyramid/config/views.py34
-rw-r--r--src/pyramid/csrf.py15
-rw-r--r--src/pyramid/encode.py23
-rw-r--r--src/pyramid/httpexceptions.py13
-rw-r--r--src/pyramid/i18n.py30
-rw-r--r--src/pyramid/interfaces.py15
-rw-r--r--src/pyramid/path.py8
-rw-r--r--src/pyramid/predicates.py4
-rw-r--r--src/pyramid/registry.py5
-rw-r--r--src/pyramid/renderers.py6
-rw-r--r--src/pyramid/request.py13
-rw-r--r--src/pyramid/response.py8
-rw-r--r--src/pyramid/scripts/prequest.py4
-rw-r--r--src/pyramid/scripts/proutes.py3
-rw-r--r--src/pyramid/scripts/pserve.py23
-rw-r--r--src/pyramid/scripts/pshell.py3
-rw-r--r--src/pyramid/scripts/pviews.py2
-rw-r--r--src/pyramid/security.py7
-rw-r--r--src/pyramid/session.py12
-rw-r--r--src/pyramid/settings.py4
-rw-r--r--src/pyramid/static.py9
-rw-r--r--src/pyramid/testing.py8
-rw-r--r--src/pyramid/traversal.py295
-rw-r--r--src/pyramid/tweens.py2
-rw-r--r--src/pyramid/url.py5
-rw-r--r--src/pyramid/urldispatch.py54
-rw-r--r--src/pyramid/util.py180
-rw-r--r--src/pyramid/view.py6
-rw-r--r--src/pyramid/viewderivers.py9
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