diff options
72 files changed, 556 insertions, 986 deletions
diff --git a/.travis.yml b/.travis.yml index 6efbee21b..c4860d2de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,20 +4,16 @@ sudo: false matrix: include: - - python: 2.7 - env: TOXENV=py27 - python: 3.4 env: TOXENV=py34 - python: 3.5 env: TOXENV=py35 - python: 3.6 env: TOXENV=py36 - - python: pypy - env: TOXENV=pypy - python: pypy3 env: TOXENV=pypy3 - python: 3.6 - env: TOXENV=py2-cover,py3-cover,coverage + env: TOXENV=py36-cover,coverage - python: 3.5 env: TOXENV=docs - python: 3.6 diff --git a/appveyor.yml b/appveyor.yml index 8c9d158e1..a9bcd40f1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,8 +8,6 @@ environment: TOXENV: "py35" - PYTHON: "C:\\Python34" TOXENV: "py34" - - PYTHON: "C:\\Python27" - TOXENV: "py27" cache: - '%LOCALAPPDATA%\pip\Cache' @@ -69,7 +69,6 @@ setup( "Development Status :: 6 - Mature", "Intended Audience :: Developers", "Programming Language :: Python", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", @@ -96,13 +95,9 @@ setup( package_dir={'': 'src'}, include_package_data=True, zip_safe=False, - python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*', + python_requires='>=3.4', install_requires=install_requires, - extras_require={ - ':python_version<"3.2"': ['repoze.lru >= 0.4'], - 'testing': testing_extras, - 'docs': docs_extras, - }, + extras_require={'testing': testing_extras, 'docs': docs_extras}, tests_require=tests_require, test_suite="tests", entry_points="""\ 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..11d3a7787 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) 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..45f528852 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 ( @@ -353,10 +352,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 +370,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..8a2ba8929 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() 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..f8743e9a7 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' @@ -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) 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..7de4c3f91 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`` @@ -94,8 +83,8 @@ def find_resource(resource, path): :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'] @@ -312,7 +301,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) @@ -446,7 +435,7 @@ def traversal_path(path): not. A :exc:`UnicodeEncodeError` will be raised if the Unicode 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 @@ -549,83 +538,63 @@ 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 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. + + """ + # 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 (str, bytes): + segment = str(segment) + 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 +616,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 +635,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 +660,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 +714,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..1180fce83 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 @@ -330,17 +354,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 +370,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 +519,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 +550,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 +574,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 +647,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 +655,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 +685,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 diff --git a/tests/pkgs/forbiddenapp/__init__.py b/tests/pkgs/forbiddenapp/__init__.py index 9ebf62a9d..31ea4dd52 100644 --- a/tests/pkgs/forbiddenapp/__init__.py +++ b/tests/pkgs/forbiddenapp/__init__.py @@ -1,6 +1,6 @@ from webob import Response from pyramid.httpexceptions import HTTPForbidden -from pyramid.compat import bytes_ +from pyramid.util import bytes_ def x_view(request): # pragma: no cover diff --git a/tests/pkgs/permbugapp/__init__.py b/tests/pkgs/permbugapp/__init__.py index aedd405f8..72b5d9c17 100644 --- a/tests/pkgs/permbugapp/__init__.py +++ b/tests/pkgs/permbugapp/__init__.py @@ -1,4 +1,4 @@ -from pyramid.compat import escape +from html import escape from pyramid.security import view_execution_permitted from pyramid.response import Response diff --git a/tests/test_authentication.py b/tests/test_authentication.py index fc3e60587..8671eba05 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -1,7 +1,8 @@ +from http.cookies import SimpleCookie import unittest import warnings from pyramid import testing -from pyramid.compat import text_, bytes_ +from pyramid.util import text_, bytes_ class TestCallbackAuthenticationPolicyDebugging(unittest.TestCase): @@ -706,8 +707,6 @@ class TestAuthTktCookieHelper(unittest.TestCase): return cookie def _parseCookie(self, cookie): - from pyramid.compat import SimpleCookie - cookies = SimpleCookie() cookies.load(cookie) return cookies.get('auth_tkt') @@ -1272,18 +1271,6 @@ class TestAuthTktCookieHelper(unittest.TestCase): self.assertEqual(val['userid'], '1') self.assertEqual(val['user_data'], 'userid_type:int') - def test_remember_long_userid(self): - from pyramid.compat import long - - helper = self._makeOne('secret') - request = self._makeRequest() - result = helper.remember(request, long(1)) - values = self._parseHeaders(result) - self.assertEqual(len(result), 3) - val = self._cookieValue(values[0]) - self.assertEqual(val['userid'], '1') - self.assertEqual(val['user_data'], 'userid_type:int') - def test_remember_unicode_userid(self): import base64 diff --git a/tests/test_compat.py b/tests/test_compat.py deleted file mode 100644 index 4a14caedf..000000000 --- a/tests/test_compat.py +++ /dev/null @@ -1,32 +0,0 @@ -import unittest -from pyramid.compat import is_unbound_method - - -class TestUnboundMethods(unittest.TestCase): - def test_old_style_bound(self): - self.assertFalse(is_unbound_method(OldStyle().run)) - - def test_new_style_bound(self): - self.assertFalse(is_unbound_method(NewStyle().run)) - - def test_old_style_unbound(self): - self.assertTrue(is_unbound_method(OldStyle.run)) - - def test_new_style_unbound(self): - self.assertTrue(is_unbound_method(NewStyle.run)) - - def test_normal_func_unbound(self): - def func(): # pragma: no cover - return 'OK' - - self.assertFalse(is_unbound_method(func)) - - -class OldStyle: - def run(self): # pragma: no cover - return 'OK' - - -class NewStyle(object): - def run(self): # pragma: no cover - return 'OK' diff --git a/tests/test_config/test_adapters.py b/tests/test_config/test_adapters.py index d871e8825..60a4f3090 100644 --- a/tests/test_config/test_adapters.py +++ b/tests/test_config/test_adapters.py @@ -1,6 +1,5 @@ import unittest -from pyramid.compat import PY2 from . import IDummy @@ -270,10 +269,7 @@ class AdaptersConfiguratorMixinTests(unittest.TestCase): from pyramid.interfaces import IResponse config = self._makeOne(autocommit=True) - if PY2: - str_name = '__builtin__.str' - else: - str_name = 'builtins.str' + str_name = 'builtins.str' config.add_response_adapter('pyramid.response.Response', str_name) result = config.registry.queryAdapter('foo', IResponse) self.assertTrue(result.body, b'foo') diff --git a/tests/test_config/test_factories.py b/tests/test_config/test_factories.py index c03d3f68b..bbc38b6cd 100644 --- a/tests/test_config/test_factories.py +++ b/tests/test_config/test_factories.py @@ -160,8 +160,7 @@ class TestFactoriesMixin(unittest.TestCase): config = self._makeOne(autocommit=True) self.assertRaises(AttributeError, config.add_request_method) - def test_add_request_method_with_text_type_name(self): - from pyramid.compat import text_, PY2 + def test_add_request_method_with_text_name(self): from pyramid.exceptions import ConfigurationError config = self._makeOne(autocommit=True) @@ -170,11 +169,7 @@ class TestFactoriesMixin(unittest.TestCase): pass def get_bad_name(): - if PY2: - name = text_(b'La Pe\xc3\xb1a', 'utf-8') - else: - name = b'La Pe\xc3\xb1a' - + name = b'La Pe\xc3\xb1a' config.add_request_method(boomshaka, name=name) self.assertRaises(ConfigurationError, get_bad_name) diff --git a/tests/test_config/test_init.py b/tests/test_config/test_init.py index 811672fb3..ce2b042ec 100644 --- a/tests/test_config/test_init.py +++ b/tests/test_config/test_init.py @@ -1,9 +1,6 @@ import os import unittest -from pyramid.compat import im_func -from pyramid.testing import skip_on - from . import dummy_tween_factory from . import dummy_include from . import dummy_extend @@ -1157,7 +1154,6 @@ test_config.dummy_include2""" "@view_config(name='two', renderer='string')" in which ) - @skip_on('py3') def test_hook_zca(self): from zope.component import getSiteManager @@ -1173,7 +1169,6 @@ test_config.dummy_include2""" finally: getSiteManager.reset() - @skip_on('py3') def test_unhook_zca(self): from zope.component import getSiteManager @@ -1208,7 +1203,7 @@ test_config.dummy_include2""" directives = {'foo': (foo, True)} config.registry._directives = directives foo_meth = config.foo - self.assertTrue(getattr(foo_meth, im_func).__docobj__ is foo) + self.assertTrue(getattr(foo_meth, '__func__').__docobj__ is foo) def test___getattr__matches_no_action_wrap(self): config = self._makeOne() @@ -1219,7 +1214,7 @@ test_config.dummy_include2""" directives = {'foo': (foo, False)} config.registry._directives = directives foo_meth = config.foo - self.assertTrue(getattr(foo_meth, im_func) is foo) + self.assertTrue(getattr(foo_meth, '__func__') is foo) class TestConfigurator_add_directive(unittest.TestCase): diff --git a/tests/test_config/test_predicates.py b/tests/test_config/test_predicates.py index 079652b39..c27b41639 100644 --- a/tests/test_config/test_predicates.py +++ b/tests/test_config/test_predicates.py @@ -1,6 +1,6 @@ import unittest -from pyramid.compat import text_ +from pyramid.util import text_ class TestPredicateList(unittest.TestCase): diff --git a/tests/test_config/test_routes.py b/tests/test_config/test_routes.py index e6540c343..4ff67cf66 100644 --- a/tests/test_config/test_routes.py +++ b/tests/test_config/test_routes.py @@ -2,7 +2,7 @@ import unittest from . import dummyfactory from . import DummyContext -from pyramid.compat import text_ +from pyramid.util import text_ class RoutesConfiguratorMixinTests(unittest.TestCase): diff --git a/tests/test_config/test_testing.py b/tests/test_config/test_testing.py index ede31e1b6..0fb73d268 100644 --- a/tests/test_config/test_testing.py +++ b/tests/test_config/test_testing.py @@ -1,8 +1,8 @@ import unittest from zope.interface import implementer -from pyramid.compat import text_ from pyramid.security import AuthenticationAPIMixin, AuthorizationAPIMixin +from pyramid.util import text_ from . import IDummy @@ -69,23 +69,27 @@ class TestingConfiguratorMixinTests(unittest.TestCase): config = self._makeOne(autocommit=True) config.testing_resources(resources) adapter = config.registry.getAdapter(None, ITraverser) - result = adapter(DummyRequest({'PATH_INFO': '/ob1'})) + request = DummyRequest() + request.path_info = '/ob1' + result = adapter(request) self.assertEqual(result['context'], ob1) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) self.assertEqual(result['traversed'], (text_('ob1'),)) self.assertEqual(result['virtual_root'], ob1) self.assertEqual(result['virtual_root_path'], ()) - result = adapter(DummyRequest({'PATH_INFO': '/ob2'})) + request = DummyRequest() + request.path_info = '/ob2' + result = adapter(request) self.assertEqual(result['context'], ob2) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) self.assertEqual(result['traversed'], (text_('ob2'),)) self.assertEqual(result['virtual_root'], ob2) self.assertEqual(result['virtual_root_path'], ()) - self.assertRaises( - KeyError, adapter, DummyRequest({'PATH_INFO': '/ob3'}) - ) + request = DummyRequest() + request.path_info = '/ob3' + self.assertRaises(KeyError, adapter, request) try: config.begin() self.assertEqual(find_resource(None, '/ob1'), ob1) diff --git a/tests/test_config/test_views.py b/tests/test_config/test_views.py index b72b9b36a..685b81a0f 100644 --- a/tests/test_config/test_views.py +++ b/tests/test_config/test_views.py @@ -3,11 +3,11 @@ import unittest from zope.interface import implementer from pyramid import testing -from pyramid.compat import im_func, text_ from pyramid.exceptions import ConfigurationError from pyramid.exceptions import ConfigurationExecutionError from pyramid.exceptions import ConfigurationConflictError from pyramid.interfaces import IResponse, IRequest, IMultiView +from pyramid.util import text_ from . import IDummy from . import dummy_view @@ -1357,6 +1357,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): request_method='POST', ) request = self._makeRequest(config) + request.path_info = '/' request.method = 'POST' request.params = {} router = Router(config.registry) @@ -1412,6 +1413,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): request_method='POST', ) request = self._makeRequest(config) + request.path_info = '/' request.method = 'POST' request.params = {} router = Router(config.registry) @@ -2722,7 +2724,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): view, renderer=null_renderer, append_slash=True ) request = self._makeRequest(config) - request.environ['PATH_INFO'] = '/foo' + request.path_info = '/foo' request.query_string = 'a=1&b=2' request.path = '/scriptname/foo' view = self._getViewCallable( @@ -2751,7 +2753,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): view, renderer=null_renderer, append_slash=HTTPMovedPermanently ) request = self._makeRequest(config) - request.environ['PATH_INFO'] = '/foo' + request.path_info = '/foo' request.query_string = 'a=1&b=2' request.path = '/scriptname/foo' view = self._getViewCallable( @@ -2795,15 +2797,6 @@ class TestViewsConfigurationMixin(unittest.TestCase): request = self._makeRequest(config) self.assertRaises(PredicateMismatch, wrapper, context, request) - # Since Python 3 has to be all cool and fancy and different... - def _assertBody(self, response, value): - from pyramid.compat import text_type - - if isinstance(value, text_type): # pragma: no cover - self.assertEqual(response.text, value) - else: # pragma: no cover - self.assertEqual(response.body, value) - def test_add_notfound_view_with_renderer(self): from zope.interface import implementedBy from pyramid.interfaces import IRequest @@ -2820,7 +2813,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): request_iface=IRequest, ) result = view(None, request) - self._assertBody(result, '{}') + self.assertEqual(result.text, '{}') def test_add_forbidden_view_with_renderer(self): from zope.interface import implementedBy @@ -2838,7 +2831,7 @@ class TestViewsConfigurationMixin(unittest.TestCase): request_iface=IRequest, ) result = view(None, request) - self._assertBody(result, '{}') + self.assertEqual(result.text, '{}') def test_set_view_mapper(self): from pyramid.interfaces import IViewMapperFactory @@ -3732,16 +3725,16 @@ class Test_preserve_view_attrs(unittest.TestCase): self.assertTrue(view1.__module__ is view2.__module__) self.assertTrue(view1.__name__ is view2.__name__) self.assertTrue( - getattr(view1.__call_permissive__, im_func) - is getattr(view2.__call_permissive__, im_func) + getattr(view1.__call_permissive__, '__func__') + is getattr(view2.__call_permissive__, '__func__') ) self.assertTrue( - getattr(view1.__permitted__, im_func) - is getattr(view2.__permitted__, im_func) + getattr(view1.__permitted__, '__func__') + is getattr(view2.__permitted__, '__func__') ) self.assertTrue( - getattr(view1.__predicated__, im_func) - is getattr(view2.__predicated__, im_func) + getattr(view1.__predicated__, '__func__') + is getattr(view2.__predicated__, '__func__') ) diff --git a/tests/test_encode.py b/tests/test_encode.py index f70050cac..4df08d509 100644 --- a/tests/test_encode.py +++ b/tests/test_encode.py @@ -1,5 +1,5 @@ import unittest -from pyramid.compat import text_, native_ +from pyramid.util import text_ class UrlEncodeTests(unittest.TestCase): @@ -74,7 +74,7 @@ class URLQuoteTests(unittest.TestCase): self.assertEqual(result, 'La%2FPe%C3%B1a') def test_it_native(self): - la = native_(b'La/Pe\xc3\xb1a', 'utf-8') + la = text_(b'La/Pe\xc3\xb1a', 'utf-8') result = self._callFUT(la) self.assertEqual(result, 'La%2FPe%C3%B1a') diff --git a/tests/test_httpexceptions.py b/tests/test_httpexceptions.py index 4c13e096d..5decfc39c 100644 --- a/tests/test_httpexceptions.py +++ b/tests/test_httpexceptions.py @@ -1,6 +1,6 @@ import unittest -from pyramid.compat import bytes_, string_types, text_ +from pyramid.util import bytes_, text_ class Test_exception_response(unittest.TestCase): @@ -67,6 +67,12 @@ class Test__no_escape(unittest.TestCase): def test_not_basestring(self): self.assertEqual(self._callFUT(42), '42') + def test_bytes(self): + self.assertEqual( + self._callFUT(b'/La Pe\xc3\xb1a/{x}'), + b'/La Pe\xc3\xb1a/{x}'.decode('utf-8'), + ) + def test_unicode(self): class DummyUnicodeObject(object): def __unicode__(self): @@ -406,7 +412,7 @@ class TestHTTPException(unittest.TestCase): def test_allow_detail_non_str(self): exc = self._makeOne(detail={'error': 'This is a test'}) - self.assertIsInstance(exc.__str__(), string_types) + self.assertIsInstance(exc.__str__(), str) class TestRenderAllExceptionsWithoutArguments(unittest.TestCase): diff --git a/tests/test_integration.py b/tests/test_integration.py index d57a7cf6e..d1f65274b 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -4,14 +4,15 @@ import gc import locale import os import unittest +from urllib.parse import quote from webtest import TestApp from zope.interface import Interface -from pyramid.wsgi import wsgiapp -from pyramid.view import view_config from pyramid.static import static_view from pyramid.testing import skip_on -from pyramid.compat import text_, url_quote +from pyramid.util import text_ +from pyramid.view import view_config +from pyramid.wsgi import wsgiapp from .pkgs.exceptionviewapp.models import AnException, NotAnException @@ -108,7 +109,7 @@ class StaticAppBase(IntegrationBase): os.makedirs(pathdir) with open(path, 'wb') as fp: fp.write(body) - url = url_quote('/static/héhé/index.html') + url = quote('/static/héhé/index.html') res = self.testapp.get(url, status=200) self.assertEqual(res.body, body) finally: @@ -123,7 +124,7 @@ class StaticAppBase(IntegrationBase): with open(path, 'wb') as fp: fp.write(body) try: - url = url_quote('/static/héhé.html') + url = quote('/static/héhé.html') res = self.testapp.get(url, status=200) self.assertEqual(res.body, body) finally: diff --git a/tests/test_path.py b/tests/test_path.py index 626bb1139..da7cd64e1 100644 --- a/tests/test_path.py +++ b/tests/test_path.py @@ -1,6 +1,5 @@ import unittest import os -from pyramid.compat import PY2 here = os.path.abspath(os.path.dirname(__file__)) @@ -429,10 +428,7 @@ class TestDottedNameResolver(unittest.TestCase): def test_zope_dottedname_style_resolve_builtin(self): typ = self._makeOne() - if PY2: - result = typ._zope_dottedname_style('__builtin__.str', None) - else: - result = typ._zope_dottedname_style('builtins.str', None) + result = typ._zope_dottedname_style('builtins.str', None) self.assertEqual(result, str) def test_zope_dottedname_style_resolve_absolute(self): diff --git a/tests/test_predicates.py b/tests/test_predicates.py index c072b4229..a99651a8f 100644 --- a/tests/test_predicates.py +++ b/tests/test_predicates.py @@ -2,7 +2,7 @@ import unittest from pyramid import testing -from pyramid.compat import text_ +from pyramid.util import text_ class TestXHRPredicate(unittest.TestCase): diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 0eacfa996..db8b3b4f2 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -1,8 +1,8 @@ import unittest -from pyramid.testing import cleanUp from pyramid import testing -from pyramid.compat import text_ +from pyramid.testing import cleanUp +from pyramid.util import text_ class TestJSON(unittest.TestCase): @@ -774,7 +774,7 @@ class DummyResponse: body = b'' # compat for renderer that will set unicode on py3 - def _set_text(self, val): # pragma: no cover + def _set_text(self, val): self.body = val.encode('utf8') text = property(fset=_set_text) diff --git a/tests/test_request.py b/tests/test_request.py index dcac501aa..484d86e01 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -1,8 +1,8 @@ import unittest from pyramid import testing -from pyramid.compat import PY2, text_, bytes_, native_ from pyramid.security import AuthenticationAPIMixin, AuthorizationAPIMixin +from pyramid.util import text_, bytes_ class TestRequest(unittest.TestCase): @@ -352,10 +352,7 @@ class TestRequest(unittest.TestCase): inp = text_( b'/\xe6\xb5\x81\xe8\xa1\x8c\xe8\xb6\x8b\xe5\x8a\xbf', 'utf-8' ) - if PY2: - body = json.dumps({'a': inp}).decode('utf-8').encode('utf-16') - else: - body = bytes(json.dumps({'a': inp}), 'utf-16') + body = bytes(json.dumps({'a': inp}), 'utf-16') request.body = body request.content_type = 'application/json; charset=utf-16' self.assertEqual(request.json_body, {'a': inp}) @@ -481,7 +478,7 @@ class Test_call_app_with_subpath_as_path_info(unittest.TestCase): self.assertEqual(request.environ['PATH_INFO'], '/hello/') def test_subpath_path_info_and_script_name_have_utf8(self): - encoded = native_(text_(b'La Pe\xc3\xb1a')) + encoded = text_(b'La Pe\xc3\xb1a') decoded = text_(bytes_(encoded), 'utf-8') request = DummyRequest( {'PATH_INFO': '/' + encoded, 'SCRIPT_NAME': '/' + encoded} diff --git a/tests/test_response.py b/tests/test_response.py index 5231e47f0..18d4335ad 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -73,9 +73,9 @@ class TestFileResponse(unittest.TestCase): # function returns Unicode for the content_type, unlike any previous # version of Python. See https://github.com/Pylons/pyramid/issues/1360 # for more information. - from pyramid.compat import text_ import mimetypes as old_mimetypes from pyramid import response + from pyramid.util import text_ class FakeMimetypesModule(object): def guess_type(self, *arg, **kw): diff --git a/tests/test_scripts/dummy.py b/tests/test_scripts/dummy.py index 8e340f645..bb3475d39 100644 --- a/tests/test_scripts/dummy.py +++ b/tests/test_scripts/dummy.py @@ -81,8 +81,11 @@ class DummyRoute(object): class DummyRequest: application_url = 'http://example.com:5432' script_name = '' + path_info = '/' - def __init__(self, environ): + def __init__(self, environ=None): + if environ is None: + environ = {} self.environ = environ self.matchdict = {} diff --git a/tests/test_scripts/test_prequest.py b/tests/test_scripts/test_prequest.py index 1521172bc..aadde719a 100644 --- a/tests/test_scripts/test_prequest.py +++ b/tests/test_scripts/test_prequest.py @@ -1,3 +1,4 @@ +from io import StringIO import unittest from . import dummy @@ -134,13 +135,11 @@ class TestPRequestCommand(unittest.TestCase): self.assertEqual(self._out, ['abc']) def test_command_method_post(self): - from pyramid.compat import NativeIO - command = self._makeOne( ['', '--method=POST', 'development.ini', '/'], [('Content-Type', 'text/html; charset=UTF-8')], ) - stdin = NativeIO() + stdin = StringIO() command.stdin = stdin command.run() self.assertEqual(self._environ['REQUEST_METHOD'], 'POST') @@ -150,13 +149,11 @@ class TestPRequestCommand(unittest.TestCase): self.assertEqual(self._out, ['abc']) def test_command_method_put(self): - from pyramid.compat import NativeIO - command = self._makeOne( ['', '--method=PUT', 'development.ini', '/'], [('Content-Type', 'text/html; charset=UTF-8')], ) - stdin = NativeIO() + stdin = StringIO() command.stdin = stdin command.run() self.assertEqual(self._environ['REQUEST_METHOD'], 'PUT') @@ -166,13 +163,11 @@ class TestPRequestCommand(unittest.TestCase): self.assertEqual(self._out, ['abc']) def test_command_method_patch(self): - from pyramid.compat import NativeIO - command = self._makeOne( ['', '--method=PATCH', 'development.ini', '/'], [('Content-Type', 'text/html; charset=UTF-8')], ) - stdin = NativeIO() + stdin = StringIO() command.stdin = stdin command.run() self.assertEqual(self._environ['REQUEST_METHOD'], 'PATCH') @@ -182,13 +177,11 @@ class TestPRequestCommand(unittest.TestCase): self.assertEqual(self._out, ['abc']) def test_command_method_propfind(self): - from pyramid.compat import NativeIO - command = self._makeOne( ['', '--method=PROPFIND', 'development.ini', '/'], [('Content-Type', 'text/html; charset=UTF-8')], ) - stdin = NativeIO() + stdin = StringIO() command.stdin = stdin command.run() self.assertEqual(self._environ['REQUEST_METHOD'], 'PROPFIND') @@ -196,13 +189,11 @@ class TestPRequestCommand(unittest.TestCase): self.assertEqual(self._out, ['abc']) def test_command_method_options(self): - from pyramid.compat import NativeIO - command = self._makeOne( ['', '--method=OPTIONS', 'development.ini', '/'], [('Content-Type', 'text/html; charset=UTF-8')], ) - stdin = NativeIO() + stdin = StringIO() command.stdin = stdin command.run() self.assertEqual(self._environ['REQUEST_METHOD'], 'OPTIONS') diff --git a/tests/test_scripts/test_pserve.py b/tests/test_scripts/test_pserve.py index b85f4ddb7..a573f2e5b 100644 --- a/tests/test_scripts/test_pserve.py +++ b/tests/test_scripts/test_pserve.py @@ -1,3 +1,4 @@ +from io import StringIO import os import unittest from . import dummy @@ -8,9 +9,7 @@ here = os.path.abspath(os.path.dirname(__file__)) class TestPServeCommand(unittest.TestCase): def setUp(self): - from pyramid.compat import NativeIO - - self.out_ = NativeIO() + self.out_ = StringIO() def out(self, msg): self.out_.write(msg) diff --git a/tests/test_scripts/test_pviews.py b/tests/test_scripts/test_pviews.py index 0b26a9cf3..c8d29113f 100644 --- a/tests/test_scripts/test_pviews.py +++ b/tests/test_scripts/test_pviews.py @@ -53,7 +53,8 @@ class TestPViewsCommand(unittest.TestCase): class View1(object): pass - request = dummy.DummyRequest({'PATH_INFO': '/a'}) + request = dummy.DummyRequest() + request.path_info = '/a' root = DefaultRootFactory(request) root_iface = providedBy(root) registry.registerAdapter( @@ -78,7 +79,8 @@ class TestPViewsCommand(unittest.TestCase): def view1(): # pragma: no cover pass - request = dummy.DummyRequest({'PATH_INFO': '/a'}) + request = dummy.DummyRequest() + request.path_info = '/a' root = DefaultRootFactory(request) root_iface = providedBy(root) registry.registerAdapter( @@ -105,7 +107,8 @@ class TestPViewsCommand(unittest.TestCase): class View1(object): pass - request = dummy.DummyRequest({'PATH_INFO': '/a'}) + request = dummy.DummyRequest() + request.path_info = '/a' root = DefaultRootFactory(request) root_iface = providedBy(root) view = View1() @@ -267,7 +270,8 @@ class TestPViewsCommand(unittest.TestCase): dummy.DummyRoute('b', '/a', factory=factory, matchdict={}), ] mapper = dummy.DummyMapper(*routes) - request = dummy.DummyRequest({'PATH_INFO': '/a'}) + request = dummy.DummyRequest() + request.path_info = '/a' result = command._find_multi_routes(mapper, request) self.assertEqual( result, @@ -288,7 +292,8 @@ class TestPViewsCommand(unittest.TestCase): dummy.DummyRoute('b', '/a', factory=factory, matchdict={}), ] mapper = dummy.DummyMapper(*routes) - request = dummy.DummyRequest({'PATH_INFO': '/a'}) + request = dummy.DummyRequest() + request.path_info = '/a' result = command._find_multi_routes(mapper, request) self.assertEqual(result, [{'match': {}, 'route': routes[1]}]) @@ -303,7 +308,8 @@ class TestPViewsCommand(unittest.TestCase): dummy.DummyRoute('b', '/a', factory=factory), ] mapper = dummy.DummyMapper(*routes) - request = dummy.DummyRequest({'PATH_INFO': '/a'}) + request = dummy.DummyRequest() + request.path_info = '/a' result = command._find_multi_routes(mapper, request) self.assertEqual(result, []) diff --git a/tests/test_session.py b/tests/test_session.py index 5e2a1ff55..8e5e82bb2 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -1,8 +1,8 @@ import base64 import json +import pickle import unittest from pyramid import testing -from pyramid.compat import pickle class SharedCookieSessionTests(object): @@ -607,13 +607,7 @@ class DummySerializer(object): return base64.b64encode(json.dumps(value).encode('utf-8')) def loads(self, value): - try: - return json.loads(base64.b64decode(value).decode('utf-8')) - - # base64.b64decode raises a TypeError on py2 instead of a ValueError - # and a ValueError is required for the session to handle it properly - except TypeError: - raise ValueError + return json.loads(base64.b64decode(value).decode('utf-8')) class DummySessionFactory(dict): diff --git a/tests/test_traversal.py b/tests/test_traversal.py index 61e480cbc..de712a6e8 100644 --- a/tests/test_traversal.py +++ b/tests/test_traversal.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- import unittest +from urllib.parse import quote from pyramid.testing import cleanUp -from pyramid.compat import text_, native_, text_type, url_quote, PY2 +from pyramid.util import text_ class TraversalPathTests(unittest.TestCase): @@ -14,7 +15,7 @@ class TraversalPathTests(unittest.TestCase): def test_utf8(self): la = b'La Pe\xc3\xb1a' - encoded = url_quote(la) + encoded = quote(la) decoded = text_(la, 'utf-8') path = '/'.join([encoded, encoded]) result = self._callFUT(path) @@ -24,7 +25,7 @@ class TraversalPathTests(unittest.TestCase): from pyramid.exceptions import URLDecodeError la = text_(b'La Pe\xc3\xb1a', 'utf-8').encode('utf-16') - encoded = url_quote(la) + encoded = quote(la) path = '/'.join([encoded, encoded]) self.assertRaises(URLDecodeError, self._callFUT, path) @@ -71,8 +72,8 @@ class TraversalPathInfoTests(unittest.TestCase): def test_segments_are_unicode(self): result = self._callFUT('/foo/bar') - self.assertEqual(type(result[0]), text_type) - self.assertEqual(type(result[1]), text_type) + self.assertEqual(type(result[0]), str) + self.assertEqual(type(result[1]), str) def test_same_value_returned_if_cached(self): result1 = self._callFUT('/foo/bar') @@ -86,15 +87,14 @@ class TraversalPathInfoTests(unittest.TestCase): def test_highorder(self): la = b'La Pe\xc3\xb1a' - latin1 = native_(la) + latin1 = text_(la) result = self._callFUT(latin1) self.assertEqual(result, (text_(la, 'utf-8'),)) def test_highorder_undecodeable(self): from pyramid.exceptions import URLDecodeError - la = text_(b'La Pe\xc3\xb1a', 'utf-8') - notlatin1 = native_(la) + notlatin1 = text_(b'La Pe\xc3\xb1a', 'utf-8') self.assertRaises(URLDecodeError, self._callFUT, notlatin1) @@ -346,10 +346,7 @@ class ResourceTreeTraverserTests(unittest.TestCase): foo = DummyContext(bar, path) root = DummyContext(foo, 'root') policy = self._makeOne(root) - if PY2: - vhm_root = b'/Qu\xc3\xa9bec' - else: - vhm_root = b'/Qu\xc3\xa9bec'.decode('latin-1') + vhm_root = b'/Qu\xc3\xa9bec'.decode('latin-1') environ = self._getEnviron(HTTP_X_VHM_ROOT=vhm_root) request = DummyRequest(environ, path_info=text_('/bar')) result = policy(request) @@ -873,15 +870,6 @@ class QuotePathSegmentTests(unittest.TestCase): result = self._callFUT(s) self.assertEqual(result, '12345') - def test_long(self): - from pyramid.compat import long - import sys - - s = long(sys.maxsize + 1) - result = self._callFUT(s) - expected = str(s) - self.assertEqual(result, expected) - def test_other(self): class Foo(object): def __str__(self): diff --git a/tests/test_url.py b/tests/test_url.py index 94a0a61c9..648f48d53 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -3,7 +3,7 @@ import unittest from pyramid import testing -from pyramid.compat import text_, WIN +from pyramid.util import WIN, text_ class TestURLMethodsMixin(unittest.TestCase): @@ -25,6 +25,7 @@ class TestURLMethodsMixin(unittest.TestCase): def __init__(self, environ): self.environ = environ + self.scheme = environ.get('wsgi.url_scheme', 'http') request = Request(environ) request.registry = self.config.registry diff --git a/tests/test_urldispatch.py b/tests/test_urldispatch.py index 772250e89..5d77042ae 100644 --- a/tests/test_urldispatch.py +++ b/tests/test_urldispatch.py @@ -1,6 +1,6 @@ import unittest from pyramid import testing -from pyramid.compat import text_, PY2 +from pyramid.util import text_ class TestRoute(unittest.TestCase): @@ -59,9 +59,7 @@ class RoutesMapperTests(unittest.TestCase): def _getRequest(self, **kw): from pyramid.threadlocal import get_current_registry - environ = {'SERVER_NAME': 'localhost', 'wsgi.url_scheme': 'http'} - environ.update(kw) - request = DummyRequest(environ) + request = DummyRequest(**kw) reg = get_current_registry() request.registry = reg return request @@ -83,7 +81,7 @@ class RoutesMapperTests(unittest.TestCase): def test_no_route_matches(self): mapper = self._makeOne() - request = self._getRequest(PATH_INFO='/') + request = self._getRequest(path_info='/') result = mapper(request) self.assertEqual(result['match'], None) self.assertEqual(result['route'], None) @@ -130,19 +128,39 @@ class RoutesMapperTests(unittest.TestCase): def test___call__pathinfo_cant_be_decoded(self): from pyramid.exceptions import URLDecodeError + from pyramid.threadlocal import get_current_registry + + class DummyRequest: + @property + def path_info(self): + return b'\xff\xfe\xe6\x00'.decode('utf-8') mapper = self._makeOne() - if PY2: - path_info = b'\xff\xfe\xe6\x00' - else: - path_info = b'\xff\xfe\xe6\x00'.decode('latin-1') - request = self._getRequest(PATH_INFO=path_info) + request = DummyRequest() + request.registry = get_current_registry() self.assertRaises(URLDecodeError, mapper, request) + def test___call__pathinfo_KeyError(self): + from pyramid.threadlocal import get_current_registry + + class DummyRequest: + @property + def path_info(self): + # if the PATH_INFO is missing from the environ + raise KeyError + + mapper = self._makeOne() + mapper.connect('root', '') + request = DummyRequest() + request.registry = get_current_registry() + result = mapper(request) + self.assertEqual(result['route'], mapper.routes['root']) + self.assertEqual(result['match'], {}) + def test___call__route_matches(self): mapper = self._makeOne() mapper.connect('foo', 'archives/:action/:article') - request = self._getRequest(PATH_INFO='/archives/action1/article1') + request = self._getRequest(path_info='/archives/action1/article1') result = mapper(request) self.assertEqual(result['route'], mapper.routes['foo']) self.assertEqual(result['match']['action'], 'action1') @@ -153,7 +171,7 @@ class RoutesMapperTests(unittest.TestCase): mapper.connect( 'foo', 'archives/:action/:article', predicates=[lambda *arg: True] ) - request = self._getRequest(PATH_INFO='/archives/action1/article1') + request = self._getRequest(path_info='/archives/action1/article1') result = mapper(request) self.assertEqual(result['route'], mapper.routes['foo']) self.assertEqual(result['match']['action'], 'action1') @@ -167,7 +185,7 @@ class RoutesMapperTests(unittest.TestCase): predicates=[lambda *arg: True, lambda *arg: False], ) mapper.connect('bar', 'archives/:action/:article') - request = self._getRequest(PATH_INFO='/archives/action1/article1') + request = self._getRequest(path_info='/archives/action1/article1') result = mapper(request) self.assertEqual(result['route'], mapper.routes['bar']) self.assertEqual(result['match']['action'], 'action1') @@ -182,7 +200,7 @@ class RoutesMapperTests(unittest.TestCase): return True mapper.connect('foo', 'archives/:action/article1', predicates=[pred]) - request = self._getRequest(PATH_INFO='/archives/action1/article1') + request = self._getRequest(path_info='/archives/action1/article1') mapper(request) def test_cc_bug(self): @@ -194,13 +212,13 @@ class RoutesMapperTests(unittest.TestCase): 'juri', 'licenses/:license_code/:license_version/:jurisdiction' ) - request = self._getRequest(PATH_INFO='/licenses/1/v2/rdf') + request = self._getRequest(path_info='/licenses/1/v2/rdf') result = mapper(request) self.assertEqual(result['route'], mapper.routes['rdf']) self.assertEqual(result['match']['license_code'], '1') self.assertEqual(result['match']['license_version'], 'v2') - request = self._getRequest(PATH_INFO='/licenses/1/v2/usa') + request = self._getRequest(path_info='/licenses/1/v2/usa') result = mapper(request) self.assertEqual(result['route'], mapper.routes['juri']) self.assertEqual(result['match']['license_code'], '1') @@ -210,7 +228,7 @@ class RoutesMapperTests(unittest.TestCase): def test___call__root_route_matches(self): mapper = self._makeOne() mapper.connect('root', '') - request = self._getRequest(PATH_INFO='/') + request = self._getRequest(path_info='/') result = mapper(request) self.assertEqual(result['route'], mapper.routes['root']) self.assertEqual(result['match'], {}) @@ -218,7 +236,7 @@ class RoutesMapperTests(unittest.TestCase): def test___call__root_route_matches2(self): mapper = self._makeOne() mapper.connect('root', '/') - request = self._getRequest(PATH_INFO='/') + request = self._getRequest(path_info='/') result = mapper(request) self.assertEqual(result['route'], mapper.routes['root']) self.assertEqual(result['match'], {}) @@ -226,7 +244,7 @@ class RoutesMapperTests(unittest.TestCase): def test___call__root_route_when_path_info_empty(self): mapper = self._makeOne() mapper.connect('root', '/') - request = self._getRequest(PATH_INFO='') + request = self._getRequest(path_info='') result = mapper(request) self.assertEqual(result['route'], mapper.routes['root']) self.assertEqual(result['match'], {}) @@ -234,7 +252,7 @@ class RoutesMapperTests(unittest.TestCase): def test___call__root_route_when_path_info_notempty(self): mapper = self._makeOne() mapper.connect('root', '/') - request = self._getRequest(PATH_INFO='/') + request = self._getRequest(path_info='/') result = mapper(request) self.assertEqual(result['route'], mapper.routes['root']) self.assertEqual(result['match'], {}) @@ -242,7 +260,7 @@ class RoutesMapperTests(unittest.TestCase): def test___call__no_path_info(self): mapper = self._makeOne() mapper.connect('root', '/') - request = self._getRequest() + request = self._getRequest(path_info='') result = mapper(request) self.assertEqual(result['route'], mapper.routes['root']) self.assertEqual(result['match'], {}) @@ -646,8 +664,10 @@ class DummyContext(object): class DummyRequest(object): - def __init__(self, environ): - self.environ = environ + scheme = 'http' + + def __init__(self, **kw): + self.__dict__.update(kw) class DummyRoute(object): diff --git a/tests/test_util.py b/tests/test_util.py index a36655f6f..0f313955b 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,5 +1,6 @@ +import sys import unittest -from pyramid.compat import PY2, text_, bytes_ +from pyramid.util import text_, bytes_ class Test_InstancePropertyHelper(unittest.TestCase): @@ -170,14 +171,10 @@ class Test_InstancePropertyHelper(unittest.TestCase): self.assertEqual(2, foo.y) def test_make_property_unicode(self): - from pyramid.compat import text_ from pyramid.exceptions import ConfigurationError cls = self._getTargetClass() - if PY2: - name = text_(b'La Pe\xc3\xb1a', 'utf-8') - else: - name = b'La Pe\xc3\xb1a' + name = b'La Pe\xc3\xb1a' def make_bad_name(): cls.make_property(lambda x: 1, name=name, reify=True) @@ -498,10 +495,7 @@ class Test_object_description(unittest.TestCase): self.assertEqual(self._callFUT(('a', 'b')), "('a', 'b')") def test_set(self): - if PY2: - self.assertEqual(self._callFUT(set(['a'])), "set(['a'])") - else: - self.assertEqual(self._callFUT(set(['a'])), "{'a'}") + self.assertEqual(self._callFUT(set(['a'])), "{'a'}") def test_list(self): self.assertEqual(self._callFUT(['a']), "['a']") @@ -839,31 +833,26 @@ class TestSentinel(unittest.TestCase): class TestCallableName(unittest.TestCase): - def test_valid_ascii(self): + def _callFUT(self, val): from pyramid.util import get_callable_name - from pyramid.compat import text_ - if PY2: - name = text_(b'hello world', 'utf-8') - else: - name = b'hello world' + return get_callable_name(val) - self.assertEqual(get_callable_name(name), 'hello world') + def test_valid_ascii_bytes(self): + name = b'hello world' + self.assertEqual(self._callFUT(name), 'hello world') - def test_invalid_ascii(self): - from pyramid.util import get_callable_name - from pyramid.compat import text_ + def test_valid_ascii_string(self): from pyramid.exceptions import ConfigurationError - def get_bad_name(): - if PY2: - name = text_(b'La Pe\xc3\xb1a', 'utf-8') - else: - name = b'La Pe\xc3\xb1a' + name = b'La Pe\xc3\xb1a'.decode('utf-8') + self.assertRaises(ConfigurationError, self._callFUT, name) - get_callable_name(name) + def test_invalid_ascii(self): + from pyramid.exceptions import ConfigurationError - self.assertRaises(ConfigurationError, get_bad_name) + name = b'La Pe\xc3\xb1a' + self.assertRaises(ConfigurationError, self._callFUT, name) class Test_hide_attrs(unittest.TestCase): @@ -1240,3 +1229,77 @@ class TestSimpleSerializer(unittest.TestCase): def test_dumps(self): inst = self._makeOne() self.assertEqual(inst.dumps('abc'), bytes_('abc')) + + +class TestUnboundMethods(unittest.TestCase): + class Dummy(object): + def run(self): # pragma: no cover + return 'OK' + + def _callFUT(self, val): + from pyramid.util import is_unbound_method + + return is_unbound_method(val) + + def test_bound_method(self): + self.assertFalse(self._callFUT(self.Dummy().run)) + + def test_unbound_method(self): + self.assertTrue(self._callFUT(self.Dummy.run)) + + def test_normal_func_unbound(self): + def func(): # pragma: no cover + return 'OK' + + self.assertFalse(self._callFUT(func)) + + +class TestReraise(unittest.TestCase): + def _callFUT(self, *args): + from pyramid.util import reraise + + return reraise(*args) + + def test_it(self): + # tests cribbed from six.py + def get_next(tb): + return tb.tb_next.tb_next.tb_next + + e = Exception('blah') + try: + raise e + except Exception: + tp, val, tb = sys.exc_info() + + try: + self._callFUT(tp, val, tb) + except Exception: + tp2, val2, tb2 = sys.exc_info() + self.assertIs(tp2, Exception) + self.assertIs(val2, e) + self.assertIs(get_next(tb2), tb) + + try: + self._callFUT(tp, val) + except Exception: + tp2, val2, tb2 = sys.exc_info() + self.assertIs(tp2, Exception) + self.assertIs(val2, e) + self.assertIsNot(get_next(tb2), tb) + + try: + self._callFUT(tp, val, tb2) + except Exception: + tp2, val2, tb3 = sys.exc_info() + self.assertIs(tp2, Exception) + self.assertIs(val2, e) + self.assertIs(get_next(tb3), tb2) + + try: + self._callFUT(tp, None, tb) + except Exception: + tp2, val2, tb2 = sys.exc_info() + self.assertIs(tp2, Exception) + self.assertIsNot(val2, val) + self.assertIsInstance(val2, Exception) + self.assertIs(get_next(tb2), tb) diff --git a/tests/test_view.py b/tests/test_view.py index f82480169..de40df1d5 100644 --- a/tests/test_view.py +++ b/tests/test_view.py @@ -417,12 +417,11 @@ class RenderViewToIterableTests(BaseTest, unittest.TestCase): from pyramid.request import Request from pyramid.config import Configurator from pyramid.view import render_view - from webob.compat import text_type config = Configurator(settings={}) def view(request): - request.response.text = text_type('<body></body>') + request.response.text = '<body></body>' return request.response config.add_view(name='test', view=view) @@ -1,23 +1,19 @@ [tox] envlist = lint, - py27,py34,py35,py36,py37,py38,pypy,pypy3, - docs,{py2,py3}-cover,coverage, + py34,py35,py36,py37,py38,pypy3, + docs,py36-cover,coverage, [testenv] # Most of these are defaults but if you specify any you can't fall back # to defaults for others. basepython = - py27: python2.7 py34: python3.4 py35: python3.5 py36: python3.6 py37: python3.7 py38: python3.8 - pypy: pypy pypy3: pypy3 - py2: python2.7 - py3: python3.6 commands = nosetests --with-xunit --xunit-file=nosetests-{envname}.xml {posargs:} @@ -59,21 +55,13 @@ extras = # we separate coverage into its own testenv because a) "last run wins" wrt # cobertura jenkins reporting and b) pypy and jython can't handle any # combination of versions of coverage and nosexcover that i can find. -[testenv:py2-cover] -commands = - coverage run {envbindir}/nosetests - coverage xml -o coverage-py2.xml -setenv = - COVERAGE_FILE=.coverage.py2 -extras = - testing - -[testenv:py3-cover] +[testenv:py36-cover] +basepython = python3.6 commands = coverage run {envbindir}/nosetests - coverage xml -o coverage-py3.xml + coverage xml -o coverage-{envname}.xml setenv = - COVERAGE_FILE=.coverage.py3 + COVERAGE_FILE=.coverage.{envname} extras = testing |
