Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore compat.py in master/2.X #3443

Closed
wants to merge 8 commits into from
162 changes: 162 additions & 0 deletions docs/api/compat.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
.. _compat_module:

:mod:`pyramid.compat`
----------------------

The ``pyramid.compat`` module provides platform and version compatibility for
Pyramid and its add-ons across Python platform and version differences. The
origininal intent for this module was that its APIs would be removed from this
module over time as Pyramid ceased to support systems which require
compatibility imports (e.g. Pyramid no longer supports Python 2 past Pyramid
2.X). However, the reality is that it requires almost no effort to continue to
support all the APIs that this module has acccreted over time, and removing any
would require software that depends on them to make "recreational" releases
that serve no real purpose. Therefore, we have decided to keep this module
even though its presence might be considered anachronistic.

.. automodule:: pyramid.compat

.. autofunction:: ascii_native_

.. attribute:: binary_type

Binary type for this platform. For Python 3, it's ``bytes``. For
Python 2, it's ``str``.

.. autofunction:: bytes_

.. attribute:: class_types

Sequence of class types for this platform. For Python 3, it's
``(type,)``. For Python 2, it's ``(type, types.ClassType)``.

.. attribute:: configparser

On Python 2, the ``ConfigParser`` module, on Python 3, the
``configparser`` module.

.. function:: escape(v)

On Python 2, the ``cgi.escape`` function, on Python 3, the
``html.escape`` function.

.. function:: exec_(code, globs=None, locs=None)

Exec code in a compatible way on both Python 2 and 3.

.. attribute:: im_func

On Python 2, the string value ``im_func``, on Python 3, the string
value ``__func__``.

.. function:: input_(v)

On Python 2, the ``raw_input`` function, on Python 3, the
``input`` function.

.. attribute:: integer_types

Sequence of integer types for this platform. For Python 3, it's
``(int,)``. For Python 2, it's ``(int, long)``.

.. function:: is_nonstr_iter(v)

Return ``True`` if ``v`` is a non-``str`` iterable on both Python 2 and
Python 3.

.. function:: iteritems_(d)

Return ``d.items()`` on Python 3, ``d.iteritems()`` on Python 2.

.. function:: itervalues_(d)

Return ``d.values()`` on Python 3, ``d.itervalues()`` on Python 2.

.. function:: iterkeys_(d)

Return ``d.keys()`` on Python 3, ``d.iterkeys()`` on Python 2.

.. attribute:: long

Long type for this platform. For Python 3, it's ``int``. For
Python 2, it's ``long``.

.. function:: map_(v)

Return ``list(map(v))`` on Python 3, ``map(v)`` on Python 2.

.. attribute:: pickle

``cPickle`` module if it exists, ``pickle`` module otherwise.

.. attribute:: PY3

``True`` if running on Python 3, ``False`` otherwise.

.. attribute:: PYPY

``True`` if running on PyPy, ``False`` otherwise.

.. function:: reraise(tp, value, tb=None)

Reraise an exception in a compatible way on both Python 2 and Python 3,
e.g. ``reraise(*sys.exc_info())``.

.. attribute:: string_types

Sequence of string types for this platform. For Python 3, it's
``(str,)``. For Python 2, it's ``(basestring,)``.

.. attribute:: SimpleCookie

On Python 2, the ``Cookie.SimpleCookie`` class, on Python 3, the
``http.cookies.SimpleCookie`` module.

.. autofunction:: text_

.. attribute:: text_type

Text type for this platform. For Python 3, it's ``str``. For Python
2, it's ``unicode``.

.. autofunction:: native_

.. attribute:: urlparse

``urlparse`` module on Python 2, ``urllib.parse`` module on Python 3.

.. attribute:: url_quote

``urllib.quote`` function on Python 2, ``urllib.parse.quote`` function
on Python 3.

.. attribute:: url_quote_plus

``urllib.quote_plus`` function on Python 2, ``urllib.parse.quote_plus``
function on Python 3.

.. attribute:: url_unquote

``urllib.unquote`` function on Python 2, ``urllib.parse.unquote``
function on Python 3.

.. attribute:: url_encode

``urllib.urlencode`` function on Python 2, ``urllib.parse.urlencode``
function on Python 3.

.. attribute:: url_open

``urllib2.urlopen`` function on Python 2, ``urllib.request.urlopen``
function on Python 3.

.. function:: url_unquote_text(v, encoding='utf-8', errors='replace')

On Python 2, return ``url_unquote(v).decode(encoding(encoding, errors))``;
on Python 3, return the result of ``urllib.parse.unquote``.

.. function:: url_unquote_native(v, encoding='utf-8', errors='replace')

On Python 2, return ``native_(url_unquote_text_v, encoding, errors))``;
on Python 3, return the result of ``urllib.parse.unquote``.

188 changes: 188 additions & 0 deletions src/pyramid/compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# This is not actually an API and *should not* be used by Pyramid 2.X apps or
# Pyramid 2.X itself. But many apps in the wild depend on this stuff, and it's
# just more pragmatic to just leave it here, even though we no longer support
# Python 2.

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

import pickle

from functools import lru_cache

PY3 = sys.version_info[0] == 3

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'): # pragma: no cover
""" 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions are already defined in pyramid.util. Please import them from there and re-export them instead of re-defining them here.



def bytes_(s, encoding='latin-1', errors='strict'): # pragma: no cover
""" 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


def ascii_native_(s): # pragma: no cover
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)``
"""


def native_(s, encoding='latin-1', errors='strict'): # pragma: no cover
""" 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)``
"""

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


import builtins

exec_ = getattr(builtins, "exec")


def reraise(tp, value, tb=None): # pragma: no cover
if value is None:
value = tp
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value


del builtins


def iteritems_(d): # pragma: no cover
return d.items()


def itervalues_(d): # pragma: no cover
return d.values()


def iterkeys_(d): # pragma: no cover
return d.keys()


def map_(*arg): # pragma: no cover
return list(map(*arg))


def is_nonstr_iter(v): # pragma: no cover
if isinstance(v, str):
return False
return hasattr(v, '__iter__')


im_func = '__func__'
im_self = '__self__'

import configparser

from http.cookies import SimpleCookie

from html import escape

input_ = input

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

# see PEP 3333 for why we encode WSGI PATH_INFO to latin-1 before
# decoding it to utf-8
def decode_path_info(path): # pragma: no cover
return path.encode('latin-1').decode('utf-8')


# 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): # pragma: no cover
return unquote_to_bytes(bytestring).decode('latin-1')


def is_bound_method(ob): # pragma: no cover
return inspect.ismethod(ob) and getattr(ob, im_self, None) is not None


# support annotations and keyword-only arguments in PY3
from inspect import getfullargspec as getargspec

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 inspect.isfunction(fn) and has_self:
return True

return False
32 changes: 32 additions & 0 deletions tests/test_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python 3 only has new-style classes, some of these tests can be removed.