Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 45 additions & 143 deletions beaker/_compat.py
Original file line number Diff line number Diff line change
@@ -1,169 +1,71 @@
from __future__ import absolute_import
import sys
"""Compatibility module for Python 3.8+.

This module provides compatibility aliases and utility functions
that were previously used to support both Python 2 and Python 3.
It is now simplified for Python 3.8+ only.
"""
import pickle
import http.cookies as http_cookies
from base64 import b64decode as _b64decode, b64encode as _b64encode
from urllib.parse import urlencode as url_encode
from urllib.parse import quote as url_quote
from urllib.parse import unquote as url_unquote
from urllib.parse import urlparse as url_parse
from urllib.request import url2pathname
from inspect import signature as func_signature

# True if we are running on Python 2.
PY2 = sys.version_info[0] == 2
PYVER = sys.version_info[:2]
JYTHON = sys.platform.startswith('java')

if PY2 and not JYTHON: # pragma: no cover
import cPickle as pickle
else: # pragma: no cover
import pickle


if not PY2: # pragma: no cover
xrange_ = range
NoneType = type(None)

string_type = str
unicode_text = str
byte_string = bytes

from urllib.parse import urlencode as url_encode
from urllib.parse import quote as url_quote
from urllib.parse import unquote as url_unquote
from urllib.parse import urlparse as url_parse
from urllib.request import url2pathname
import http.cookies as http_cookies
from base64 import b64decode as _b64decode, b64encode as _b64encode

try:
import dbm.gnu as anydbm
except ImportError:
import dbm.dumb as anydbm

def b64decode(b):
return _b64decode(b.encode('ascii'))

def b64encode(s):
return _b64encode(s).decode('ascii')

def u_(s):
return str(s)

def bytes_(s):
if isinstance(s, byte_string):
return s
return str(s).encode('ascii', 'strict')
try:
import dbm.gnu as anydbm
except ImportError:
import dbm.dumb as anydbm

def dictkeyslist(d):
return list(d.keys())

else:
xrange_ = xrange
from types import NoneType
def b64decode(b):
"""Base64 decode a string, returning bytes."""
return _b64decode(b.encode('ascii'))

string_type = basestring
unicode_text = unicode
byte_string = str

from urllib import urlencode as url_encode
from urllib import quote as url_quote
from urllib import unquote as url_unquote
from urlparse import urlparse as url_parse
from urllib import url2pathname
import Cookie as http_cookies
from base64 import b64decode, b64encode
import anydbm
def b64encode(s):
"""Base64 encode bytes, returning a string."""
return _b64encode(s).decode('ascii')

def u_(s):
if isinstance(s, unicode_text):
return s

if not isinstance(s, byte_string):
s = str(s)
return unicode(s, 'utf-8')
def bytes_(s):
"""Convert to bytes."""
if isinstance(s, bytes):
return s
return str(s).encode('ascii', 'strict')

def bytes_(s):
if isinstance(s, byte_string):
return s
return str(s)

def dictkeyslist(d):
return d.keys()
def dictkeyslist(d):
"""Return dictionary keys as a list."""
return list(d.keys())


def im_func(f):
if not PY2: # pragma: no cover
return getattr(f, '__func__', None)
else:
return getattr(f, 'im_func', None)
"""Get the function from a bound method."""
return getattr(f, '__func__', None)


def default_im_func(f):
if not PY2: # pragma: no cover
return getattr(f, '__func__', f)
else:
return getattr(f, 'im_func', f)
"""Get the function from a bound method, or return the function itself."""
return getattr(f, '__func__', f)


def im_self(f):
if not PY2: # pragma: no cover
return getattr(f, '__self__', None)
else:
return getattr(f, 'im_self', None)
"""Get the instance from a bound method."""
return getattr(f, '__self__', None)


def im_class(f):
if not PY2: # pragma: no cover
self = im_self(f)
if self is not None:
return self.__class__
else:
return None
else:
return getattr(f, 'im_class', None)


def add_metaclass(metaclass):
"""Class decorator for creating a class with a metaclass."""
def wrapper(cls):
orig_vars = cls.__dict__.copy()
slots = orig_vars.get('__slots__')
if slots is not None:
if isinstance(slots, str):
slots = [slots]
for slots_var in slots:
orig_vars.pop(slots_var)
orig_vars.pop('__dict__', None)
orig_vars.pop('__weakref__', None)
return metaclass(cls.__name__, cls.__bases__, orig_vars)
return wrapper


if not PY2: # pragma: no cover
import builtins
exec_ = getattr(builtins, "exec")

def reraise(tp, value, tb=None):
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value
else: # 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
""")


try:
from inspect import signature as func_signature
except ImportError:
from funcsigs import signature as func_signature
"""Get the class from a bound method."""
self = im_self(f)
if self is not None:
return self.__class__
return None


def bindfuncargs(arginfo, args, kwargs):
"""Bind function arguments to their parameters."""
boundargs = arginfo.bind(*args, **kwargs)
return boundargs.args, boundargs.kwargs
15 changes: 6 additions & 9 deletions beaker/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import warnings
from itertools import chain

from beaker._compat import u_, unicode_text, func_signature, bindfuncargs
from beaker._compat import func_signature, bindfuncargs
import beaker.container as container
import beaker.util as util
from beaker.crypto.util import sha1
Expand Down Expand Up @@ -101,10 +101,7 @@ def _init(self):
# Warn when there's a problem loading a NamespaceManager
if not isinstance(sys.exc_info()[1], DistributionNotFound):
import traceback
try:
from StringIO import StringIO # Python2
except ImportError:
from io import StringIO # Python3
from io import StringIO

tb = StringIO()
traceback.print_exc(file=tb)
Expand Down Expand Up @@ -330,7 +327,7 @@ def remove_value(self, key, **kw):
remove = remove_value

def _get_value(self, key, **kw):
if isinstance(key, unicode_text):
if isinstance(key, str):
key = key.encode('ascii', 'backslashreplace')

if 'type' in kw:
Expand Down Expand Up @@ -576,13 +573,13 @@ def cached(*args, **kwargs):
# kwargs provided, merge them in positional args
# to avoid having different cache keys.
args, kwargs = bindfuncargs(signature, args, kwargs)
cache_key_kwargs = [u_(':').join((u_(key), u_(value))) for key, value in kwargs.items()]
cache_key_kwargs = [':'.join((str(key), str(value))) for key, value in kwargs.items()]

cache_key_args = args
if skip_self:
cache_key_args = args[1:]

cache_key = u_(" ").join(map(u_, chain(deco_args, cache_key_args, cache_key_kwargs)))
cache_key = " ".join(map(str, chain(deco_args, cache_key_args, cache_key_kwargs)))

if region:
cachereg = cache_regions[region]
Expand Down Expand Up @@ -611,7 +608,7 @@ def go():
def _cache_decorator_invalidate(cache, key_length, args):
"""Invalidate a cache key based on function arguments."""

cache_key = u_(" ").join(map(u_, args))
cache_key = " ".join(map(str, args))
if len(cache_key) + len(cache.namespace_name) > key_length:
cache_key = sha1(cache_key.encode('utf-8')).hexdigest()
cache.remove_value(cache_key)
10 changes: 2 additions & 8 deletions beaker/container.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Container and Namespace classes"""
import errno

from ._compat import pickle, anydbm, add_metaclass, PYVER, unicode_text
from ._compat import pickle, anydbm

import beaker.util as util
import logging
Expand Down Expand Up @@ -597,11 +597,6 @@ def __getitem__(self, key):
return pickle.loads(self.dbm[key])

def __contains__(self, key):
if PYVER == (3, 2):
# Looks like this is a bug that got solved in PY3.3 and PY3.4
# http://bugs.python.org/issue19288
if isinstance(key, unicode_text):
key = key.encode('UTF-8')
return key in self.dbm

def __setitem__(self, key, value):
Expand Down Expand Up @@ -734,8 +729,7 @@ def __call__(self, key, context, namespace, createfunc=None,
return Value(key, ns, createfunc=createfunc,
expiretime=expiretime, starttime=starttime)

@add_metaclass(ContainerMeta)
class Container(object):
class Container(metaclass=ContainerMeta):
"""Implements synchronization and value-creation logic
for a 'value' stored in a :class:`.NamespaceManager`.

Expand Down
6 changes: 2 additions & 4 deletions beaker/converters.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
from beaker._compat import string_type

# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
def asbool(obj):
if isinstance(obj, string_type):
if isinstance(obj, str):
obj = obj.strip().lower()
if obj in ['true', 'yes', 'on', 'y', 't', '1']:
return True
Expand All @@ -16,7 +14,7 @@ def asbool(obj):


def aslist(obj, sep=None, strip=True):
if isinstance(obj, string_type):
if isinstance(obj, str):
lst = obj.split(sep)
if strip:
lst = [v.strip() for v in lst]
Expand Down
58 changes: 7 additions & 51 deletions beaker/cookie.py
Original file line number Diff line number Diff line change
@@ -1,70 +1,26 @@
import sys
from ._compat import http_cookies
"""Cookie handling utilities for Beaker.

# Some versions of Python 2.7 and later won't need this encoding bug fix:
_cookie_encodes_correctly = http_cookies.SimpleCookie().value_encode(';') == (';', '"\\073"')

# Cookie pickling bug is fixed in Python 2.7.9 and Python 3.4.3+
# http://bugs.python.org/issue22775
cookie_pickles_properly = (
(sys.version_info[:2] == (2, 7) and sys.version_info >= (2, 7, 9)) or
sys.version_info >= (3, 4, 3)
)

# Add support for the SameSite attribute (obsolete when PY37 is unsupported).
http_cookies.Morsel._reserved.setdefault('samesite', 'SameSite')
This module provides a SimpleCookie class that gracefully handles
invalid cookie keys while keeping around the session.
"""
import http.cookies as http_cookies


# Adapted from Django.http.cookies and always enabled the bad_cookies
# behaviour to cope with any invalid cookie key while keeping around
# the session.
class SimpleCookie(http_cookies.SimpleCookie):
if not cookie_pickles_properly:
def __setitem__(self, key, value):
# Apply the fix from http://bugs.python.org/issue22775 where
# it's not fixed in Python itself
if isinstance(value, http_cookies.Morsel):
# allow assignment of constructed Morsels (e.g. for pickling)
dict.__setitem__(self, key, value)
else:
super(SimpleCookie, self).__setitem__(key, value)

if not _cookie_encodes_correctly:
def value_encode(self, val):
# Some browsers do not support quoted-string from RFC 2109,
# including some versions of Safari and Internet Explorer.
# These browsers split on ';', and some versions of Safari
# are known to split on ', '. Therefore, we encode ';' and ','

# SimpleCookie already does the hard work of encoding and decoding.
# It uses octal sequences like '\\012' for newline etc.
# and non-ASCII chars. We just make use of this mechanism, to
# avoid introducing two encoding schemes which would be confusing
# and especially awkward for javascript.

# NB, contrary to Python docs, value_encode returns a tuple containing
# (real val, encoded_val)
val, encoded = super(SimpleCookie, self).value_encode(val)

encoded = encoded.replace(";", "\\073").replace(",", "\\054")
# If encoded now contains any quoted chars, we need double quotes
# around the whole string.
if "\\" in encoded and not encoded.startswith('"'):
encoded = '"' + encoded + '"'

return val, encoded

def load(self, rawdata):
self.bad_cookies = set()
super(SimpleCookie, self).load(rawdata)
super().load(rawdata)
for key in self.bad_cookies:
del self[key]

# override private __set() method:
# (needed for using our Morsel, and for laxness with CookieError
def _BaseCookie__set(self, key, real_value, coded_value):
try:
super(SimpleCookie, self)._BaseCookie__set(key, real_value, coded_value)
super()._BaseCookie__set(key, real_value, coded_value)
except http_cookies.CookieError:
if not hasattr(self, 'bad_cookies'):
self.bad_cookies = set()
Expand Down
Loading