Strip six module and rename to raven._compat

We're seeing some odd issues with our six module interacting
badly with the six module from other libraries for some users.
The suspicion is that our six module in some cases gets unvendored.
We thus rename it to `raven._compat` (which is also what some
other Python modules do).  We still use six normally for the
tests.
This commit is contained in:
Armin Ronacher 2016-01-12 21:40:51 +01:00
parent f2eedb742c
commit 97e93ea7ba
33 changed files with 350 additions and 535 deletions

View File

@ -20,13 +20,17 @@ def main():
from flake8.main import DEFAULT_CONFIG
from flake8.engine import get_style_guide
from flake8.hooks import run
from raven.utils import six
gitcmd = "git diff-index --cached --name-only HEAD"
_, files_modified, _ = run(gitcmd)
files_modified = [six.text_type(x) for x in files_modified]
try:
text_type = unicode
except NameError:
text_type = str
files_modified = [text_type(x) for x in files_modified]
# remove non-py files and files which no longer exist
files_modified = filter(

177
raven/_compat.py Normal file
View File

@ -0,0 +1,177 @@
"""Utilities for writing code that runs on Python 2 and 3"""
# flake8: noqa
# Copyright (c) 2010-2013 Benjamin Peterson
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import absolute_import
import operator
import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.3.0"
PY2 = sys.version_info[0] == 2
if not PY2:
string_types = str,
integer_types = int,
class_types = type,
text_type = str
binary_type = bytes
MAXSIZE = sys.maxsize
else:
string_types = basestring,
integer_types = (int, long)
class_types = (type, types.ClassType)
text_type = unicode
binary_type = str
if sys.platform.startswith("java"):
# Jython always uses 32 bits.
MAXSIZE = int((1 << 31) - 1)
else:
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
class X(object):
def __len__(self):
return 1 << 31
try:
len(X())
except OverflowError:
# 32-bit
MAXSIZE = int((1 << 31) - 1)
else:
# 64-bit
MAXSIZE = int((1 << 63) - 1)
del X
def _import_module(name):
"""Import module, returning the module after the last dot."""
__import__(name)
return sys.modules[name]
if not PY2:
_iterkeys = "keys"
_itervalues = "values"
_iteritems = "items"
_iterlists = "lists"
else:
_iterkeys = "iterkeys"
_itervalues = "itervalues"
_iteritems = "iteritems"
_iterlists = "iterlists"
try:
advance_iterator = next
except NameError:
def advance_iterator(it):
return it.next()
next = advance_iterator
try:
callable = callable
except NameError:
def callable(obj):
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
def iterkeys(d, **kw):
"""Return an iterator over the keys of a dictionary."""
return iter(getattr(d, _iterkeys)(**kw))
def itervalues(d, **kw):
"""Return an iterator over the values of a dictionary."""
return iter(getattr(d, _itervalues)(**kw))
def iteritems(d, **kw):
"""Return an iterator over the (key, value) pairs of a dictionary."""
return iter(getattr(d, _iteritems)(**kw))
def iterlists(d, **kw):
"""Return an iterator over the (key, [values]) pairs of a dictionary."""
return iter(getattr(d, _iterlists)(**kw))
if not PY2:
def b(s):
return s.encode("latin-1")
def u(s):
return s
if sys.version_info[1] <= 1:
def int2byte(i):
return bytes((i,))
else:
# This is about 2x faster than the implementation above on 3.2+
int2byte = operator.methodcaller("to_bytes", 1, "big")
import io
StringIO = io.StringIO
BytesIO = io.BytesIO
else:
def b(s): # NOQA
return s
def u(s): # NOQA
return unicode(s, "unicode_escape")
int2byte = chr
import StringIO
StringIO = BytesIO = StringIO.StringIO
if not PY2:
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
del builtins
else:
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
""")
def with_metaclass(meta, base=object):
"""Create a base class with a metaclass."""
return meta("NewBase", (base,), {})

View File

@ -30,7 +30,8 @@ from raven.conf import defaults
from raven.conf.remote import RemoteConfig
from raven.context import Context
from raven.exceptions import APIError, RateLimited
from raven.utils import six, json, get_versions, get_auth_header, merge_dicts
from raven.utils import json, get_versions, get_auth_header, merge_dicts
from raven._compat import text_type, iteritems
from raven.utils.encoding import to_unicode
from raven.utils.serializer import transform
from raven.utils.stacks import get_stack_info, iter_stack_frames, get_culprit
@ -145,7 +146,7 @@ class Client(object):
self.include_paths = set(o.get('include_paths') or [])
self.exclude_paths = set(o.get('exclude_paths') or [])
self.name = six.text_type(o.get('name') or o.get('machine') or defaults.NAME)
self.name = text_type(o.get('name') or o.get('machine') or defaults.NAME)
self.auto_log_stacks = bool(
o.get('auto_log_stacks') or defaults.AUTO_LOG_STACKS)
self.capture_locals = bool(
@ -252,7 +253,8 @@ class Client(object):
>>> result = client.capture(**kwargs)
>>> ident = client.get_ident(result)
"""
warnings.warn('Client.get_ident is deprecated. The event ID is now returned as the result of capture.',
warnings.warn('Client.get_ident is deprecated. The event ID is now '
'returned as the result of capture.',
DeprecationWarning)
return result
@ -306,7 +308,7 @@ class Client(object):
if data.get('culprit'):
culprit = data['culprit']
for k, v in six.iteritems(result):
for k, v in iteritems(result):
if k not in data:
data[k] = v
@ -394,11 +396,11 @@ class Client(object):
data['message'] = kwargs.get('message', handler.to_string(data))
# tags should only be key=>u'value'
for key, value in six.iteritems(data['tags']):
for key, value in iteritems(data['tags']):
data['tags'][key] = to_unicode(value)
# extra data can be any arbitrary value
for k, v in six.iteritems(data['extra']):
for k, v in iteritems(data['extra']):
data['extra'][k] = self.transform(v)
# It's important date is added **after** we serialize
@ -556,7 +558,8 @@ class Client(object):
if isinstance(exc, RateLimited):
retry_after = exc.retry_after
self.error_logger.error(
'Sentry responded with an API error: %s(%s)', type(exc).__name__, exc.message)
'Sentry responded with an API error: %s(%s)',
type(exc).__name__, exc.message)
else:
self.error_logger.error(
'Sentry responded with an error: %s (url: %s)\n%s',
@ -577,10 +580,10 @@ class Client(object):
if 'exception' in data and 'stacktrace' in data['exception']['values'][0]:
# try to reconstruct a reasonable version of the exception
for frame in data['exception']['values'][0]['stacktrace']['frames']:
output.append(' File "%(filename)s", line %(lineno)s, in %(function)s' % {
'filename': frame['filename'],
output.append(' File "%(fn)s", line %(lineno)s, in %(func)s' % {
'fn': frame['filename'],
'lineno': frame['lineno'],
'function': frame['function'],
'func': frame['function'],
})
self.uncaught_logger.error(output)

View File

@ -2,9 +2,9 @@ from __future__ import absolute_import
import warnings
from raven._compat import PY2, text_type
from raven.exceptions import InvalidDsn
from raven.transport.threaded import ThreadedHTTPTransport
from raven.utils import six
from raven.utils.encoding import to_string
from raven.utils.urlparse import parse_qsl, urlparse
@ -32,7 +32,7 @@ class RemoteConfig(object):
self._transport_cls = transport or DEFAULT_TRANSPORT
def __unicode__(self):
return six.text_type(self.base_url)
return text_type(self.base_url)
def is_active(self):
return all([self.base_url, self.project, self.public_key, self.secret_key])
@ -58,7 +58,7 @@ class RemoteConfig(object):
def from_string(cls, value, transport=None, transport_registry=None):
# in Python 2.x sending the DSN as a unicode value will eventually
# cause issues in httplib
if not six.PY3:
if PY2:
value = to_string(value)
url = urlparse(value)

View File

@ -10,7 +10,7 @@ from __future__ import absolute_import
from collections import Mapping, Iterable
from threading import local
from raven.utils import six
from raven._compat import iteritems
class Context(local, Mapping, Iterable):
@ -42,10 +42,10 @@ class Context(local, Mapping, Iterable):
def merge(self, data):
d = self.data
for key, value in six.iteritems(data):
for key, value in iteritems(data):
if key in ('tags', 'extra'):
d.setdefault(key, {})
for t_key, t_value in six.iteritems(value):
for t_key, t_value in iteritems(value):
d[key][t_key] = t_value
else:
d[key] = value

View File

@ -19,7 +19,7 @@ import warnings
from django.conf import settings
from hashlib import md5
from raven.utils import six
from raven._compat import PY2, binary_type, text_type
from raven.utils.imports import import_string
from raven.contrib.django.management import patch_cli_runner
@ -61,7 +61,7 @@ class ProxyClient(object):
__ne__ = lambda x, o: get_client() != o
__gt__ = lambda x, o: get_client() > o
__ge__ = lambda x, o: get_client() >= o
if not six.PY3:
if PY2:
__cmp__ = lambda x, o: cmp(get_client(), o) # NOQA
__hash__ = lambda x: hash(get_client())
# attributes are currently not callable
@ -92,11 +92,11 @@ class ProxyClient(object):
__invert__ = lambda x: ~(get_client())
__complex__ = lambda x: complex(get_client())
__int__ = lambda x: int(get_client())
if not six.PY3:
if PY2:
__long__ = lambda x: long(get_client()) # NOQA
__float__ = lambda x: float(get_client())
__str__ = lambda x: six.binary_type(get_client())
__unicode__ = lambda x: six.text_type(get_client())
__str__ = lambda x: binary_type(get_client())
__unicode__ = lambda x: text_type(get_client())
__oct__ = lambda x: oct(get_client())
__hex__ = lambda x: hex(get_client())
__index__ = lambda x: get_client().__index__()
@ -139,7 +139,7 @@ def get_client(client=None, reset=False):
options.setdefault('release', ga('RELEASE'))
transport = ga('TRANSPORT') or options.get('transport')
if isinstance(transport, six.string_types):
if isinstance(transport, string_types):
transport = import_string(transport)
options['transport'] = transport

View File

@ -12,7 +12,7 @@ from django.conf import settings
from django.http import HttpRequest
from django.utils.functional import Promise
from raven.utils.serializer import Serializer, register
from raven.utils import six
from raven._compat import text_type
__all__ = ('PromiseSerializer',)
@ -34,12 +34,15 @@ class PromiseSerializer(Serializer):
def serialize(self, value, **kwargs):
# EPIC HACK
# handles lazy model instances (which are proxy values that don't easily give you the actual function)
# handles lazy model instances (which are proxy values that don't
# easily give you the actual function)
pre = value.__class__.__name__[1:]
if hasattr(value, '%s__func' % pre):
value = getattr(value, '%s__func' % pre)(*getattr(value, '%s__args' % pre), **getattr(value, '%s__kw' % pre))
value = getattr(value, '%s__func' % pre)(
*getattr(value, '%s__args' % pre),
**getattr(value, '%s__kw' % pre))
else:
return self.recurse(six.text_type(value))
return self.recurse(text_type(value))
return self.recurse(value, **kwargs)
register(PromiseSerializer)

View File

@ -15,9 +15,9 @@ from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from raven._compat import string_types
from raven.contrib.django.models import client
from raven.utils import json
from raven.utils import six
def is_valid_origin(origin):
@ -32,7 +32,7 @@ def is_valid_origin(origin):
origin = origin.lower()
for value in settings.SENTRY_ALLOW_ORIGIN:
if isinstance(value, six.string_types):
if isinstance(value, string_types):
if value.lower() == origin:
return True
else:

View File

@ -23,11 +23,11 @@ from flask import request, current_app, g
from flask.signals import got_request_exception, request_finished
from werkzeug.exceptions import ClientDisconnected
from raven._compat import string_types
from raven.conf import setup_logging
from raven.base import Client
from raven.middleware import Sentry as SentryMiddleware
from raven.handlers.logging import SentryHandler
from raven.utils import six
from raven.utils.compat import _urlparse
from raven.utils.encoding import to_unicode
from raven.utils.imports import import_string
@ -38,13 +38,14 @@ def make_client(client_cls, app, dsn=None):
# TODO(dcramer): django and Flask share very similar concepts here, and
# should be refactored
transport = app.config.get('SENTRY_TRANSPORT')
if isinstance(transport, six.string_types):
if isinstance(transport, string_types):
transport = import_string(transport)
return client_cls(
dsn=dsn or app.config.get('SENTRY_DSN') or os.environ.get('SENTRY_DSN'),
transport=transport,
include_paths=set(app.config.get('SENTRY_INCLUDE_PATHS', [])) | set([app.import_name]),
include_paths=set(app.config.get(
'SENTRY_INCLUDE_PATHS', [])) | set([app.import_name]),
exclude_paths=app.config.get('SENTRY_EXCLUDE_PATHS'),
name=app.config.get('SENTRY_NAME'),
site=app.config.get('SENTRY_SITE_NAME'),
@ -131,17 +132,20 @@ class Sentry(object):
if not self.client:
return
ignored_exc_type_list = current_app.config.get('RAVEN_IGNORE_EXCEPTIONS', [])
ignored_exc_type_list = current_app.config.get(
'RAVEN_IGNORE_EXCEPTIONS', [])
exc = sys.exc_info()[1]
if any((isinstance(exc, ignored_exc_type) for ignored_exc_type in ignored_exc_type_list)):
if any((isinstance(exc, ignored_exc_type)
for ignored_exc_type in ignored_exc_type_list)):
return
self.captureException(exc_info=kwargs.get('exc_info'))
def get_user_info(self, request):
"""
Requires Flask-Login (https://pypi.python.org/pypi/Flask-Login/) to be installed
Requires Flask-Login (https://pypi.python.org/pypi/Flask-Login/)
to be installed
and setup
"""
if not has_flask_login:

View File

@ -1,6 +1,6 @@
from __future__ import absolute_import
from raven.utils import six
from raven._compat import text_type
class APIError(Exception):
@ -9,7 +9,7 @@ class APIError(Exception):
self.message = message
def __unicode__(self):
return six.text_type("%s: %s" % (self.message, self.code))
return text_type("%s: %s" % (self.message, self.code))
class RateLimited(APIError):

View File

@ -13,16 +13,16 @@ import logbook
import sys
import traceback
from raven._compat import string_types
from raven.base import Client
from raven.utils.encoding import to_string
from raven.utils import six
class SentryHandler(logbook.Handler):
def __init__(self, *args, **kwargs):
if len(args) == 1:
arg = args[0]
if isinstance(arg, six.string_types):
if isinstance(arg, string_types):
self.client = kwargs.pop('client_cls', Client)(dsn=arg, **kwargs)
elif isinstance(arg, Client):
self.client = arg

View File

@ -14,8 +14,8 @@ import logging
import sys
import traceback
from raven._compat import string_types, iteritems, text_type
from raven.base import Client
from raven.utils import six
from raven.utils.encoding import to_string
from raven.utils.stacks import iter_stack_frames, label_from_frame
@ -31,15 +31,14 @@ class SentryHandler(logging.Handler, object):
client = kwargs.get('client_cls', Client)
if len(args) == 1:
arg = args[0]
if isinstance(arg, six.string_types):
if isinstance(arg, string_types):
self.client = client(dsn=arg, **kwargs)
elif isinstance(arg, Client):
self.client = arg
else:
raise ValueError('The first argument to %s must be either a Client instance or a DSN, got %r instead.' % (
self.__class__.__name__,
arg,
))
raise ValueError('The first argument to %s must be either a '
'Client instance or a DSN, got %r instead.' %
(self.__class__.__name__, arg,))
elif 'client' in kwargs:
self.client = kwargs['client']
else:
@ -68,7 +67,8 @@ class SentryHandler(logging.Handler, object):
except Exception:
if self.client.raise_send_errors:
raise
print("Top level Sentry exception caught - failed creating log record", file=sys.stderr)
print("Top level Sentry exception caught - failed "
"creating log record", file=sys.stderr)
print(to_string(record.msg), file=sys.stderr)
print(to_string(traceback.format_exc()), file=sys.stderr)
@ -113,7 +113,7 @@ class SentryHandler(logging.Handler, object):
else:
extra = {}
for k, v in six.iteritems(vars(record)):
for k, v in iteritems(vars(record)):
if k in RESERVED:
continue
if k.startswith('_'):
@ -136,13 +136,13 @@ class SentryHandler(logging.Handler, object):
'params': record.args,
}
try:
handler_kwargs['message'] = six.text_type(record.msg)
handler_kwargs['message'] = text_type(record.msg)
except UnicodeDecodeError:
# Handle binary strings where it should be unicode...
handler_kwargs['message'] = repr(record.msg)[1:-1]
try:
handler_kwargs['formatted'] = six.text_type(record.message)
handler_kwargs['formatted'] = text_type(record.message)
except UnicodeDecodeError:
# Handle binary strings where it should be unicode...
handler_kwargs['formatted'] = repr(record.message)[1:-1]
@ -160,8 +160,12 @@ class SentryHandler(logging.Handler, object):
handler_kwargs = {'exc_info': record.exc_info}
# HACK: discover a culprit when we normally couldn't
elif not (data.get('stacktrace') or data.get('culprit')) and (record.name or record.funcName):
culprit = label_from_frame({'module': record.name, 'function': record.funcName})
elif not (data.get('stacktrace') or data.get('culprit')) \
and (record.name or record.funcName):
culprit = label_from_frame({
'module': record.name,
'function': record.funcName
})
if culprit:
data['culprit'] = culprit

View File

@ -9,8 +9,8 @@ from __future__ import absolute_import
import re
from raven._compat import string_types, text_type
from raven.utils import varmap
from raven.utils import six
class Processor(object):
@ -88,13 +88,13 @@ class SanitizePasswordsProcessor(Processor):
if value is None:
return
if isinstance(value, six.string_types) and self.VALUES_RE.match(value):
if isinstance(value, string_types) and self.VALUES_RE.match(value):
return self.MASK
if not key: # key can be a NoneType
return value
key = six.text_type(key).lower()
key = text_type(key).lower()
for field in self.FIELDS:
if field in key:
# store mask as a fixed length for security
@ -112,7 +112,7 @@ class SanitizePasswordsProcessor(Processor):
if n not in data:
continue
if isinstance(data[n], six.string_types) and '=' in data[n]:
if isinstance(data[n], string_types) and '=' in data[n]:
# at this point we've assumed it's a standard HTTP query
# or cookie
if n == 'cookies':

View File

@ -7,10 +7,10 @@ raven.transport.http
"""
from __future__ import absolute_import
from raven._compat import string_types
from raven.conf import defaults
from raven.exceptions import APIError, RateLimited
from raven.transport.base import Transport
from raven.utils import six
from raven.utils.http import urlopen
from raven.utils.compat import urllib2
@ -23,9 +23,9 @@ class HTTPTransport(Transport):
self._parsed_url = parsed_url
self._url = parsed_url.geturl().rsplit('+', 1)[-1]
if isinstance(timeout, six.string_types):
if isinstance(timeout, string_types):
timeout = int(timeout)
if isinstance(verify_ssl, six.string_types):
if isinstance(verify_ssl, string_types):
verify_ssl = bool(int(verify_ssl))
self.timeout = timeout

View File

@ -7,7 +7,7 @@ raven.utils
"""
from __future__ import absolute_import
from raven.utils import six
from raven._compat import iteritems, string_types
import logging
try:
import pkg_resources
@ -24,7 +24,7 @@ def merge_dicts(*dicts):
if not d:
continue
for k, v in six.iteritems(d):
for k, v in iteritems(d):
out[k] = v
return out
@ -42,7 +42,8 @@ def varmap(func, var, context=None, name=None):
return func(name, '<...>')
context[objid] = 1
if isinstance(var, dict):
ret = dict((k, varmap(func, v, context, k)) for k, v in six.iteritems(var))
ret = dict((k, varmap(func, v, context, k))
for k, v in iteritems(var))
elif isinstance(var, (list, tuple)):
ret = [varmap(func, f, context, name) for f in var]
else:
@ -79,7 +80,7 @@ def get_version_from_app(module_name, app):
if callable(version):
version = version()
if not isinstance(version, (six.string_types, list, tuple)):
if not isinstance(version, (string_types, list, tuple)):
version = None
if version is None:
@ -98,7 +99,8 @@ def get_versions(module_list=None):
ext_module_list = set()
for m in module_list:
parts = m.split('.')
ext_module_list.update('.'.join(parts[:idx]) for idx in range(1, len(parts) + 1))
ext_module_list.update('.'.join(parts[:idx])
for idx in range(1, len(parts) + 1))
versions = {}
for module_name in ext_module_list:
@ -128,7 +130,8 @@ def get_versions(module_list=None):
return versions
def get_auth_header(protocol, timestamp, client, api_key, api_secret=None, **kwargs):
def get_auth_header(protocol, timestamp, client, api_key,
api_secret=None, **kwargs):
header = [
('sentry_timestamp', timestamp),
('sentry_client', client),

View File

@ -8,7 +8,9 @@ raven.utils.encoding
from __future__ import absolute_import, unicode_literals
import warnings
from raven.utils import six
from raven._compat import integer_types, text_type, binary_type, \
string_types, PY2
def is_protected_type(obj):
@ -19,7 +21,7 @@ def is_protected_type(obj):
"""
import Decimal
import datetime
return isinstance(obj, six.integer_types + (type(None), float, Decimal,
return isinstance(obj, integer_types + (type(None), float, Decimal,
datetime.datetime, datetime.date, datetime.time))
@ -31,25 +33,25 @@ def force_text(s, encoding='utf-8', strings_only=False, errors='strict'):
If strings_only is True, don't convert (some) non-string-like objects.
"""
# Handle the common case first, saves 30-40% when s is an instance of
# six.text_type. This function gets called often in that setting.
if isinstance(s, six.text_type):
# text_type. This function gets called often in that setting.
if isinstance(s, text_type):
return s
if strings_only and is_protected_type(s):
return s
try:
if not isinstance(s, six.string_types):
if not isinstance(s, string_types):
if hasattr(s, '__unicode__'):
s = s.__unicode__()
else:
if six.PY3:
if not PY2:
if isinstance(s, bytes):
s = six.text_type(s, encoding, errors)
s = text_type(s, encoding, errors)
else:
s = six.text_type(s)
s = text_type(s)
else:
s = six.text_type(bytes(s), encoding, errors)
s = text_type(bytes(s), encoding, errors)
else:
# Note: We use .decode() here, instead of six.text_type(s, encoding,
# Note: We use .decode() here, instead of text_type(s, encoding,
# errors), so that if s is a SafeBytes, it ends up being a
# SafeText at the end.
s = s.decode(encoding, errors)
@ -69,19 +71,20 @@ def force_text(s, encoding='utf-8', strings_only=False, errors='strict'):
def transform(value):
from raven.utils.serializer import transform
warnings.warn('You should switch to raven.utils.serializer.transform', DeprecationWarning)
warnings.warn('You should switch to raven.utils.serializer.'
'transform', DeprecationWarning)
return transform(value)
def to_unicode(value):
try:
value = six.text_type(force_text(value))
value = text_type(force_text(value))
except (UnicodeEncodeError, UnicodeDecodeError):
value = '(Error decoding value)'
except Exception: # in some cases we get a different exception
try:
value = six.binary_type(repr(type(value)))
value = binary_type(repr(type(value)))
except Exception:
value = '(Error decoding value)'
return value
@ -89,6 +92,6 @@ def to_unicode(value):
def to_string(value):
try:
return six.binary_type(value.decode('utf-8').encode('utf-8'))
return binary_type(value.decode('utf-8').encode('utf-8'))
except:
return to_unicode(value).encode('utf-8')

View File

@ -1,11 +1,11 @@
from __future__ import absolute_import
from . import six
from raven._compat import PY2
def import_string(key):
# HACK(dcramer): Ensure a unicode key is still importable
if not six.PY3:
if PY2:
key = str(key)
if '.' not in key:

View File

@ -11,7 +11,8 @@ from __future__ import absolute_import
import itertools
import types
from raven.utils import six
from raven._compat import text_type, binary_type, string_types, iteritems, \
class_types, PY2
from raven.utils.encoding import to_unicode
from .manager import manager as serialization_manager
@ -54,13 +55,14 @@ class Serializer(object):
_depth += 1
if _depth >= max_depth:
try:
value = six.text_type(repr(value))[:string_max_length]
value = text_type(repr(value))[:string_max_length]
except Exception as e:
import traceback
traceback.print_exc()
self.manager.logger.exception(e)
return six.text_type(type(value))
return self.manager.transform(value, max_depth=max_depth, _depth=_depth, **kwargs)
return text_type(type(value))
return self.manager.transform(value, max_depth=max_depth,
_depth=_depth, **kwargs)
class IterableSerializer(Serializer):
@ -71,7 +73,8 @@ class IterableSerializer(Serializer):
return tuple(
self.recurse(o, **kwargs)
for n, o
in itertools.takewhile(lambda x: x[0] < list_max_length, enumerate(value))
in itertools.takewhile(lambda x: x[0] < list_max_length,
enumerate(value))
)
@ -79,7 +82,7 @@ class DictSerializer(Serializer):
types = (dict,)
def make_key(self, key):
if not isinstance(key, six.string_types):
if not isinstance(key, string_types):
return to_unicode(key)
return key
@ -88,12 +91,13 @@ class DictSerializer(Serializer):
return dict(
(self.make_key(self.recurse(k, **kwargs)), self.recurse(v, **kwargs))
for n, (k, v)
in itertools.takewhile(lambda x: x[0] < list_max_length, enumerate(six.iteritems(value)))
in itertools.takewhile(lambda x: x[0] < list_max_length, enumerate(
iteritems(value)))
)
class UnicodeSerializer(Serializer):
types = (six.text_type,)
types = (text_type,)
def serialize(self, value, **kwargs):
# try to return a reasonable string that can be decoded
@ -101,21 +105,22 @@ class UnicodeSerializer(Serializer):
# unicode character
# e.g. we want the output to be like: "u'רונית מגן'"
string_max_length = kwargs.get('string_max_length', None)
return repr(six.text_type('%s')) % (value[:string_max_length],)
return repr(text_type('%s')) % (value[:string_max_length],)
class StringSerializer(Serializer):
types = (six.binary_type,)
types = (binary_type,)
def serialize(self, value, **kwargs):
string_max_length = kwargs.get('string_max_length', None)
if six.PY3:
if not PY2:
return repr(value[:string_max_length])
try:
# Python2 madness: let's try to recover from developer's issues
# Try to process the string as if it was a unicode.
return "'" + value.decode('utf8')[:string_max_length].encode('utf8') + "'"
return "'" + value.decode('utf8')[:string_max_length] \
.encode('utf8') + "'"
except UnicodeDecodeError:
pass
@ -123,10 +128,11 @@ class StringSerializer(Serializer):
class TypeSerializer(Serializer):
types = six.class_types
types = class_types
def can(self, value):
return not super(TypeSerializer, self).can(value) and has_sentry_metadata(value)
return not super(TypeSerializer, self).can(value) \
and has_sentry_metadata(value)
def serialize(self, value, **kwargs):
return self.recurse(value.__sentry__(), **kwargs)
@ -157,10 +163,11 @@ class FunctionSerializer(Serializer):
types = (types.FunctionType,)
def serialize(self, value, **kwargs):
return '<function %s from %s at 0x%x>' % (value.__name__, value.__module__, id(value))
return '<function %s from %s at 0x%x>' % (
value.__name__, value.__module__, id(value))
if not six.PY3:
if PY2:
class LongSerializer(Serializer):
types = (long,) # noqa
@ -178,5 +185,5 @@ serialization_manager.register(BooleanSerializer)
serialization_manager.register(FloatSerializer)
serialization_manager.register(IntegerSerializer)
serialization_manager.register(FunctionSerializer)
if not six.PY3:
if PY2:
serialization_manager.register(LongSerializer)

View File

@ -9,7 +9,7 @@ from __future__ import absolute_import
import logging
from contextlib import closing
from raven.utils import six
from raven._compat import text_type
__all__ = ('register', 'transform')
@ -69,16 +69,18 @@ class Serializer(object):
return serializer.serialize(value, **kwargs)
except Exception as e:
logger.exception(e)
return six.text_type(type(value))
return text_type(type(value))
# if all else fails, lets use the repr of the object
try:
return repr(value)
except Exception as e:
logger.exception(e)
# It's common case that a model's __unicode__ definition may try to query the database
# which if it was not cleaned up correctly, would hit a transaction aborted exception
return six.text_type(type(value))
# It's common case that a model's __unicode__ definition
# may try to query the database which if it was not
# cleaned up correctly, would hit a transaction aborted
# exception
return text_type(type(value))
finally:
self.context.remove(objid)

View File

@ -1,406 +0,0 @@
"""Utilities for writing code that runs on Python 2 and 3"""
# flake8: noqa
# Copyright (c) 2010-2013 Benjamin Peterson
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import absolute_import
import operator
import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.3.0"
# True if we are running on Python 3.
PY3 = sys.version_info[0] == 3
if PY3:
string_types = str,
integer_types = int,
class_types = type,
text_type = str
binary_type = bytes
MAXSIZE = sys.maxsize
else:
string_types = basestring,
integer_types = (int, long)
class_types = (type, types.ClassType)
text_type = unicode
binary_type = str
if sys.platform.startswith("java"):
# Jython always uses 32 bits.
MAXSIZE = int((1 << 31) - 1)
else:
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
class X(object):
def __len__(self):
return 1 << 31
try:
len(X())
except OverflowError:
# 32-bit
MAXSIZE = int((1 << 31) - 1)
else:
# 64-bit
MAXSIZE = int((1 << 63) - 1)
del X
def _add_doc(func, doc):
"""Add documentation to a function."""
func.__doc__ = doc
def _import_module(name):
"""Import module, returning the module after the last dot."""
__import__(name)
return sys.modules[name]
class _LazyDescr(object):
def __init__(self, name):
self.name = name
def __get__(self, obj, tp):
result = self._resolve()
setattr(obj, self.name, result)
# This is a bit ugly, but it avoids running this again.
delattr(tp, self.name)
return result
class MovedModule(_LazyDescr):
def __init__(self, name, old, new=None):
super(MovedModule, self).__init__(name)
if PY3:
if new is None:
new = name
self.mod = new
else:
self.mod = old
def _resolve(self):
return _import_module(self.mod)
class MovedAttribute(_LazyDescr):
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
super(MovedAttribute, self).__init__(name)
if PY3:
if new_mod is None:
new_mod = name
self.mod = new_mod
if new_attr is None:
if old_attr is None:
new_attr = name
else:
new_attr = old_attr
self.attr = new_attr
else:
self.mod = old_mod
if old_attr is None:
old_attr = name
self.attr = old_attr
def _resolve(self):
module = _import_module(self.mod)
return getattr(module, self.attr)
class _MovedItems(types.ModuleType):
"""Lazy loading of moved objects"""
_moved_attributes = [
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
MovedAttribute("reduce", "__builtin__", "functools"),
MovedAttribute("StringIO", "StringIO", "io"),
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
MovedModule("builtins", "__builtin__"),
MovedModule("configparser", "ConfigParser"),
MovedModule("copyreg", "copy_reg"),
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
MovedModule("http_cookies", "Cookie", "http.cookies"),
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
MovedModule("html_parser", "HTMLParser", "html.parser"),
MovedModule("http_client", "httplib", "http.client"),
MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
MovedModule("cPickle", "cPickle", "pickle"),
MovedModule("queue", "Queue"),
MovedModule("reprlib", "repr"),
MovedModule("socketserver", "SocketServer"),
MovedModule("tkinter", "Tkinter"),
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
MovedModule("tkinter_colorchooser", "tkColorChooser",
"tkinter.colorchooser"),
MovedModule("tkinter_commondialog", "tkCommonDialog",
"tkinter.commondialog"),
MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
MovedModule("tkinter_font", "tkFont", "tkinter.font"),
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
"tkinter.simpledialog"),
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
MovedModule("winreg", "_winreg"),
]
for attr in _moved_attributes:
setattr(_MovedItems, attr.name, attr)
del attr
moves = sys.modules[__name__ + ".moves"] = _MovedItems("moves")
def add_move(move):
"""Add an item to six.moves."""
setattr(_MovedItems, move.name, move)
def remove_move(name):
"""Remove item from six.moves."""
try:
delattr(_MovedItems, name)
except AttributeError:
try:
del moves.__dict__[name]
except KeyError:
raise AttributeError("no such move, %r" % (name,))
if PY3:
_meth_func = "__func__"
_meth_self = "__self__"
_func_closure = "__closure__"
_func_code = "__code__"
_func_defaults = "__defaults__"
_func_globals = "__globals__"
_iterkeys = "keys"
_itervalues = "values"
_iteritems = "items"
_iterlists = "lists"
else:
_meth_func = "im_func"
_meth_self = "im_self"
_func_closure = "func_closure"
_func_code = "func_code"
_func_defaults = "func_defaults"
_func_globals = "func_globals"
_iterkeys = "iterkeys"
_itervalues = "itervalues"
_iteritems = "iteritems"
_iterlists = "iterlists"
try:
advance_iterator = next
except NameError:
def advance_iterator(it):
return it.next()
next = advance_iterator
try:
callable = callable
except NameError:
def callable(obj):
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
if PY3:
def get_unbound_function(unbound):
return unbound
Iterator = object
else:
def get_unbound_function(unbound): # NOQA
return unbound.im_func
class Iterator(object):
def next(self):
return type(self).__next__(self)
callable = callable
_add_doc(get_unbound_function,
"""Get the function out of a possibly unbound function""")
get_method_function = operator.attrgetter(_meth_func)
get_method_self = operator.attrgetter(_meth_self)
get_function_closure = operator.attrgetter(_func_closure)
get_function_code = operator.attrgetter(_func_code)
get_function_defaults = operator.attrgetter(_func_defaults)
get_function_globals = operator.attrgetter(_func_globals)
def iterkeys(d, **kw):
"""Return an iterator over the keys of a dictionary."""
return iter(getattr(d, _iterkeys)(**kw))
def itervalues(d, **kw):
"""Return an iterator over the values of a dictionary."""
return iter(getattr(d, _itervalues)(**kw))
def iteritems(d, **kw):
"""Return an iterator over the (key, value) pairs of a dictionary."""
return iter(getattr(d, _iteritems)(**kw))
def iterlists(d, **kw):
"""Return an iterator over the (key, [values]) pairs of a dictionary."""
return iter(getattr(d, _iterlists)(**kw))
if PY3:
def b(s):
return s.encode("latin-1")
def u(s):
return s
if sys.version_info[1] <= 1:
def int2byte(i):
return bytes((i,))
else:
# This is about 2x faster than the implementation above on 3.2+
int2byte = operator.methodcaller("to_bytes", 1, "big")
import io
StringIO = io.StringIO
BytesIO = io.BytesIO
else:
def b(s): # NOQA
return s
def u(s): # NOQA
return unicode(s, "unicode_escape")
int2byte = chr
import StringIO
StringIO = BytesIO = StringIO.StringIO
_add_doc(b, """Byte literal""")
_add_doc(u, """Text literal""")
if PY3:
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
print_ = getattr(builtins, "print")
del builtins
else:
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
""")
def print_(*args, **kwargs):
"""The new-style print function."""
fp = kwargs.pop("file", sys.stdout)
if fp is None:
return
def write(data):
if not isinstance(data, basestring):
data = str(data)
fp.write(data)
want_unicode = False
sep = kwargs.pop("sep", None)
if sep is not None:
if isinstance(sep, unicode):
want_unicode = True
elif not isinstance(sep, str):
raise TypeError("sep must be None or a string")
end = kwargs.pop("end", None)
if end is not None:
if isinstance(end, unicode):
want_unicode = True
elif not isinstance(end, str):
raise TypeError("end must be None or a string")
if kwargs:
raise TypeError("invalid keyword arguments to print()")
if not want_unicode:
for arg in args:
if isinstance(arg, unicode):
want_unicode = True
break
if want_unicode:
newline = unicode("\n")
space = unicode(" ")
else:
newline = "\n"
space = " "
if sep is None:
sep = space
if end is None:
end = newline
for i, arg in enumerate(args):
if i:
write(sep)
write(arg)
write(end)
_add_doc(reraise, """Reraise an exception.""")
def with_metaclass(meta, base=object):
"""Create a base class with a metaclass."""
return meta("NewBase", (base,), {})

View File

@ -14,13 +14,14 @@ import sys
import warnings
from raven.utils.serializer import transform
from raven.utils import six
from raven._compat import iteritems
_coding_re = re.compile(r'coding[:=]\s*([-\w.]+)')
def get_lines_from_file(filename, lineno, context_lines, loader=None, module_name=None):
def get_lines_from_file(filename, lineno, context_lines,
loader=None, module_name=None):
"""
Returns context_lines before and after lineno from file.
Returns (pre_context_lineno, pre_context, context_line, post_context).
@ -63,7 +64,8 @@ def get_lines_from_file(filename, lineno, context_lines, loader=None, module_nam
try:
pre_context = [line.strip('\r\n') for line in source[lower_bound:lineno]]
context_line = source[lineno].strip('\r\n')
post_context = [line.strip('\r\n') for line in source[(lineno + 1):upper_bound]]
post_context = [line.strip('\r\n') for line in
source[(lineno + 1):upper_bound]]
except IndexError:
# the file may have changed since it was loaded into memory
return None, None, None
@ -178,7 +180,7 @@ def get_frame_locals(frame, transformer=transform, max_var_size=4096):
f_vars = {}
f_size = 0
for k, v in six.iteritems(f_locals):
for k, v in iteritems(f_locals):
v = transformer(v)
v_size = len(repr(v))
if v_size + f_size < 4096:
@ -253,7 +255,8 @@ def get_stack_info(frames, transformer=transform, capture_locals=True,
lineno -= 1
if lineno is not None and abs_path:
pre_context, context_line, post_context = get_lines_from_file(abs_path, lineno, 5, loader, module_name)
pre_context, context_line, post_context = \
get_lines_from_file(abs_path, lineno, 5, loader, module_name)
else:
pre_context, context_line, post_context = None, None, None
@ -261,7 +264,8 @@ def get_stack_info(frames, transformer=transform, capture_locals=True,
# This changes /foo/site-packages/baz/bar.py into baz/bar.py
try:
base_filename = sys.modules[module_name.split('.', 1)[0]].__file__
filename = abs_path.split(base_filename.rsplit('/', 2)[0], 1)[-1].lstrip("/")
filename = abs_path.split(
base_filename.rsplit('/', 2)[0], 1)[-1].lstrip("/")
except:
filename = abs_path

View File

@ -6,7 +6,7 @@ This module implements WSGI related helpers adapted from ``werkzeug.wsgi``
"""
from __future__ import absolute_import
from raven.utils import six
from raven._compat import iteritems
from raven.utils.compat import urllib_quote
@ -15,7 +15,7 @@ def get_headers(environ):
"""
Returns only proper HTTP headers.
"""
for key, value in six.iteritems(environ):
for key, value in iteritems(environ):
key = str(key)
if key.startswith('HTTP_') and key not in \
('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'):

View File

@ -8,8 +8,7 @@ except ImportError:
# pkg_resource is not available on Google App Engine
pkg_resources = None
from raven.utils import six
from raven._compat import text_type
from .exceptions import InvalidGitRepository
__all__ = ('fetch_git_sha', 'fetch_package_version')
@ -22,10 +21,11 @@ def fetch_git_sha(path, head=None):
if not head:
head_path = os.path.join(path, '.git', 'HEAD')
if not os.path.exists(head_path):
raise InvalidGitRepository('Cannot identify HEAD for git repository at %s' % (path,))
raise InvalidGitRepository(
'Cannot identify HEAD for git repository at %s' % (path,))
with open(head_path, 'r') as fp:
head = six.text_type(fp.read()).strip()
head = text_type(fp.read()).strip()
if head.startswith('ref: '):
revision_file = os.path.join(
@ -38,12 +38,14 @@ def fetch_git_sha(path, head=None):
if not os.path.exists(revision_file):
if not os.path.exists(os.path.join(path, '.git')):
raise InvalidGitRepository('%s does not seem to be the root of a git repository' % (path,))
raise InvalidGitRepository('Unable to find ref to head "%s" in repository' % (head,))
raise InvalidGitRepository(
'%s does not seem to be the root of a git repository' % (path,))
raise InvalidGitRepository(
'Unable to find ref to head "%s" in repository' % (head,))
fh = open(revision_file, 'r')
try:
return six.text_type(fh.read()).strip()
return text_type(fh.read()).strip()
finally:
fh.close()
@ -53,6 +55,7 @@ def fetch_package_version(dist_name):
>>> fetch_package_version('sentry')
"""
if pkg_resources is None:
raise NotImplementedError('pkg_resources is not available on this Python install')
raise NotImplementedError('pkg_resources is not available '
'on this Python install')
dist = pkg_resources.get_distribution(dist_name)
return dist.version

View File

@ -53,6 +53,7 @@ if sys.version_info[0] == 3:
install_requires.remove('contextlib2')
tests_require = [
'six',
'bottle',
'celery>=2.5',
'Django>=1.4',

View File

@ -5,13 +5,13 @@ import inspect
import mock
import raven
import time
import six
from raven.base import Client, ClientState
from raven.exceptions import RateLimited
from raven.transport import AsyncTransport
from raven.transport.http import HTTPTransport
from raven.utils.stacks import iter_stack_frames
from raven.utils import six
from raven.utils.testutils import TestCase

View File

@ -9,8 +9,10 @@ import logging
import mock
import pytest
import re
import six
import sys # NOQA
from exam import fixture
from six import StringIO
from django.conf import settings
from django.contrib.auth.models import User
@ -33,8 +35,6 @@ from raven.contrib.django.templatetags.raven import sentry_public_dsn
from raven.contrib.django.views import is_valid_origin
from raven.transport import HTTPTransport
from raven.utils.serializer import transform
from raven.utils import six
from raven.utils.six import StringIO
from django.test.client import Client as TestClient, ClientHandler as TestClientHandler
from .models import TestModel

View File

@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import six
from mock import patch
from tornado import web, gen, testing
from tornado.concurrent import Future
from tornado.httpclient import HTTPError
from raven.contrib.tornado import SentryMixin, AsyncSentryClient
from raven.utils import six
class AnErrorProneHandler(SentryMixin, web.RequestHandler):

View File

@ -1,9 +1,9 @@
from __future__ import with_statement
from __future__ import unicode_literals
import six
import logbook
from raven.utils.testutils import TestCase
from raven.utils import six
from raven.base import Client
from raven.handlers.logbook import SentryHandler

View File

@ -3,10 +3,10 @@ from __future__ import unicode_literals
import logging
import sys
import mock
import six
from raven.base import Client
from raven.handlers.logging import SentryHandler
from raven.utils import six
from raven.utils.stacks import iter_stack_frames
from raven.utils.testutils import TestCase

View File

@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
import pytest
import uuid
import six
from raven.utils import six, json
from raven.utils import json
from raven.utils.testutils import TestCase
from raven.utils.serializer import transform

View File

@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import six
from mock import Mock
from raven.utils.testutils import TestCase
from raven.utils import six
from raven.utils.stacks import get_culprit, get_stack_info, get_lines_from_file

View File

@ -1,8 +1,9 @@
from __future__ import absolute_import
import six
import raven
from raven.utils import six
from raven.utils.imports import import_string

View File

@ -3,11 +3,11 @@ from __future__ import absolute_import
import os.path
import pytest
import subprocess
import six
from django.conf import settings
from raven.versioning import fetch_git_sha, fetch_package_version
from raven.utils import six
def has_git_requirements():