summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES24
-rw-r--r--CONTRIBUTORS4
-rw-r--r--LICENSE2
-rw-r--r--README6
-rw-r--r--documentation/conf.py2
-rw-r--r--documentation/index.rst75
-rw-r--r--setup.cfg16
-rw-r--r--six.py118
-rw-r--r--test_six.py120
-rw-r--r--tox.ini11
10 files changed, 347 insertions, 31 deletions
diff --git a/CHANGES b/CHANGES
index fa74e77..1aba42b 100644
--- a/CHANGES
+++ b/CHANGES
@@ -6,6 +6,27 @@ This file lists the changes in each six version.
Development version
-------------------
+- Pull request #60 and issue #108: Add `six.moves.getcwd` and
+ `six.moves.getcwdu`.
+
+- Pull request #64: Add `create_unbound_method` to create unbound methods.
+
+1.9.0
+-----
+
+- Issue #106: Support the `flush` parameter to `six.print_`.
+
+- Pull request #48 and issue #15: Add the `python_2_unicode_compatible`
+ decorator.
+
+- Pull request #57 and issue #50: Add several compatibility methods for unittest
+ assertions that were renamed between Python 2 and 3.
+
+- Issue #105 and pull request #58: Ensure `six.wraps` respects the *updated* and
+ *assigned* arguments.
+
+- Issue #102: Add `raise_from` to abstract out Python 3's raise from syntax.
+
- Issue #97: Optimize `six.iterbytes` on Python 2.
- Issue #98: Fix `six.moves` race condition in multi-threaded code.
@@ -13,6 +34,9 @@ Development version
- Pull request #51: Add `six.view(keys|values|itmes)`, which provide dictionary
views on Python 2.7+.
+- Issue #112: `six.moves.reload_module` now uses the importlib module on
+ Python 3.4+.
+
1.8.0
-----
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index 0cbd0a4..3122a57 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -6,14 +6,18 @@ Marc Abramowitz
Alexander Artemenko
Aymeric Augustin
Ned Batchelder
+Brett Cannon
Jason R. Coombs
Julien Danjou
Ben Darnell
Ben Davis
+Tim Graham
+Thomas Grainger
Joshua Harlow
Anselm Kruis
Alexander Lukanin
James Mills
+Berker Peksag
Sridhar Ratnakumar
Erik Rose
Peter Ruibal
diff --git a/LICENSE b/LICENSE
index d76e024..e558f9d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2010-2014 Benjamin Peterson
+Copyright (c) 2010-2015 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
diff --git a/README b/README
index 4de73fa..2a22717 100644
--- a/README
+++ b/README
@@ -3,14 +3,14 @@ for smoothing over the differences between the Python versions with the goal of
writing Python code that is compatible on both Python versions. See the
documentation for more information on what is provided.
-Six supports every Python version since 2.5. It is contained in only one Python
+Six supports every Python version since 2.6. It is contained in only one Python
file, so it can be easily copied into your project. (The copyright and license
notice must be retained.)
Online documentation is at http://pythonhosted.org/six/.
-Bugs can be reported to http://bitbucket.org/gutworth/six. The code can also be
-found there.
+Bugs can be reported to https://bitbucket.org/gutworth/six. The code can also
+be found there.
For questions about six or porting in general, email the python-porting mailing
list: http://mail.python.org/mailman/listinfo/python-porting
diff --git a/documentation/conf.py b/documentation/conf.py
index 7e54287..0215bdd 100644
--- a/documentation/conf.py
+++ b/documentation/conf.py
@@ -33,7 +33,7 @@ master_doc = "index"
# General information about the project.
project = u"six"
-copyright = u"2010-2014, Benjamin Peterson"
+copyright = u"2010-2015, Benjamin Peterson"
sys.path.append(os.path.abspath(os.path.join(".", "..")))
from six import __version__ as six_version
diff --git a/documentation/index.rst b/documentation/index.rst
index c7ca996..05f95e5 100644
--- a/documentation/index.rst
+++ b/documentation/index.rst
@@ -18,7 +18,7 @@ tracker and code hosting is on `BitBucket <http://bitbucket.org/gutworth/six>`_.
The name, "six", comes from the fact that 2*3 equals 6. Why not addition?
Multiplication is more powerful, and, anyway, "five" has already been snatched
-away by the Zope Five project.
+away by the (admittedly now moribund) Zope Five project.
Indices and tables
@@ -232,6 +232,13 @@ functions and methods is the stdlib :mod:`py3:inspect` module.
requires the *obj*'s class to be passed.
+.. function:: create_unbound_method(func, cls)
+
+ Return an unbound method object wrapping *func*. In Python 2, this will
+ return a :func:`py2:types.MethodType` object. In Python 3, unbound methods
+ do not exist and this wrapper will simply return *func*.
+
+
.. class:: Iterator
A class for making portable iterators. The intention is that it be subclassed
@@ -243,7 +250,7 @@ functions and methods is the stdlib :mod:`py3:inspect` module.
aliased to :class:`py3:object`.)
-.. function:: wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, updated=functools.WRAPPER_UPDATES)
+.. decorator:: wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, updated=functools.WRAPPER_UPDATES)
This is exactly the :func:`py3:functools.wraps` decorator, but it sets the
``__wrapped__`` attribute on what it decorates as :func:`py3:functools.wraps`
@@ -270,10 +277,11 @@ Python 2 and 3.
:func:`exec` with them should be avoided.
-.. function:: print_(*args, *, file=sys.stdout, end="\\n", sep=" ")
+.. function:: print_(*args, *, file=sys.stdout, end="\\n", sep=" ", flush=False)
Print *args* into *file*. Each argument will be separated with *sep* and
- *end* will be written to the file after the last argument is printed.
+ *end* will be written to the file after the last argument is printed. If
+ *flush* is true, ``file.flush()`` will be called after all data is written.
.. note::
@@ -282,6 +290,13 @@ Python 2 and 3.
ok. :)
+.. function:: raise_from(exc_value, exc_value_from)
+
+ Raise an exception from a context. On Python 3, this is equivalent to
+ ``raise exc_value from exc_value_from``. On Python 2, which does not support
+ exception chaining, it is equivalent to ``raise exc_value``.
+
+
.. function:: reraise(exc_type, exc_value, exc_traceback=None)
Reraise an exception, possibly with a different traceback. In the simple
@@ -313,7 +328,7 @@ Python 2 and 3.
decorator.
-.. function:: add_metaclass(metaclass)
+.. decorator:: add_metaclass(metaclass)
Class decorator that replaces a normally-constructed class with a
metaclass-constructed one. Example usage: ::
@@ -430,6 +445,48 @@ string data in all Python versions.
:class:`py3:io.BytesIO`.
+.. decorator:: python_2_unicode_compatible
+
+ A class decorator that takes a class defining a ``__str__`` method. On
+ Python 3, the decorator does nothing. On Python 2, it aliases the
+ ``__str__`` method to ``__unicode__`` and creates a new ``__str__`` method
+ that returns the result of ``__unicode__()`` encoded with UTF-8.
+
+
+unittest assertions
+>>>>>>>>>>>>>>>>>>>
+
+Six contains compatibility shims for unittest assertions that have been renamed.
+The parameters are the same as their aliases, but you must pass the test method
+as the first argument. For example::
+
+ import six
+ import unittest
+
+ class TestAssertCountEqual(unittest.TestCase):
+ def test(self):
+ six.assertCountEqual(self, (1, 2), [2, 1])
+
+Note these functions are only available on Python 2.7 or later.
+
+.. function:: assertCountEqual()
+
+ Alias for :meth:`~py3:unittest.TestCase.assertCountEqual` on Python 3 and
+ :meth:`~py2:unittest.TestCase.assertItemsEqual` on Python 2.
+
+
+.. function:: assertRaisesRegex()
+
+ Alias for :meth:`~py3:unittest.TestCase.assertRaisesRegex` on Python 3 and
+ :meth:`~py2:unittest.TestCase.assertRaisesRegexp` on Python 2.
+
+
+.. function:: assertRegex()
+
+ Alias for :meth:`~py3:unittest.TestCase.assertRegex` on Python 3 and
+ :meth:`~py2:unittest.TestCase.assertRegexpMatches` on Python 2.
+
+
Renamed modules and attributes compatibility
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
@@ -520,6 +577,10 @@ Supported renames:
+------------------------------+-------------------------------------+-------------------------------------+
| ``filterfalse`` | :func:`py2:itertools.ifilterfalse` | :func:`py3:itertools.filterfalse` |
+------------------------------+-------------------------------------+-------------------------------------+
+| ``getcwd`` | :func:`py2:os.getcwdu` | :func:`py3:os.getcwd` |
++------------------------------+-------------------------------------+-------------------------------------+
+| ``getcwdb`` | :func:`py2:os.getcwd` | :func:`py3:os.getcwdb` |
++------------------------------+-------------------------------------+-------------------------------------+
| ``http_cookiejar`` | :mod:`py2:cookielib` | :mod:`py3:http.cookiejar` |
+------------------------------+-------------------------------------+-------------------------------------+
| ``http_cookies`` | :mod:`py2:Cookie` | :mod:`py3:http.cookies` |
@@ -548,7 +609,9 @@ Supported renames:
+------------------------------+-------------------------------------+-------------------------------------+
| ``reduce`` | :func:`py2:reduce` | :func:`py3:functools.reduce` |
+------------------------------+-------------------------------------+-------------------------------------+
-| ``reload_module`` | :func:`py2:reload` | :func:`py3:imp.reload` |
+| ``reload_module`` | :func:`py2:reload` | :func:`py3:imp.reload`, |
+| | | :func:`py3:importlib.reload` |
+| | | on Python 3.4+ |
+------------------------------+-------------------------------------+-------------------------------------+
| ``reprlib`` | :mod:`py2:repr` | :mod:`py3:reprlib` |
+------------------------------+-------------------------------------+-------------------------------------+
diff --git a/setup.cfg b/setup.cfg
index 5e40900..4a5b847 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,2 +1,18 @@
[wheel]
universal = 1
+
+[flake8]
+max-line-length = 100
+ignore = F821
+
+[pytest]
+minversion=2.2.0
+pep8ignore =
+ documentation/*.py ALL
+ test_six.py ALL
+
+flakes-ignore =
+ documentation/*.py ALL
+ test_six.py ALL
+ six.py UndefinedName
+
diff --git a/six.py b/six.py
index 06c8bd7..806e3cd 100644
--- a/six.py
+++ b/six.py
@@ -1,6 +1,6 @@
"""Utilities for writing code that runs on Python 2 and 3"""
-# Copyright (c) 2010-2014 Benjamin Peterson
+# Copyright (c) 2010-2015 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
@@ -29,12 +29,13 @@ import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
-__version__ = "1.8.0"
+__version__ = "1.9.0"
# Useful for very coarse version differentiation.
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
+PY34 = sys.version_info[0:2] >= (3, 4)
if PY3:
string_types = str,
@@ -57,6 +58,7 @@ else:
else:
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
class X(object):
+
def __len__(self):
return 1 << 31
try:
@@ -88,7 +90,7 @@ class _LazyDescr(object):
def __get__(self, obj, tp):
result = self._resolve()
- setattr(obj, self.name, result) # Invokes __set__.
+ setattr(obj, self.name, result) # Invokes __set__.
try:
# This is a bit ugly, but it avoids running this again by
# removing this descriptor.
@@ -160,12 +162,14 @@ class MovedAttribute(_LazyDescr):
class _SixMetaPathImporter(object):
+
"""
A meta path importer to import six.moves and its submodules.
This class implements a PEP302 finder and loader. It should be compatible
with Python 2.5 and all existing versions of Python3
"""
+
def __init__(self, six_module_name):
self.name = six_module_name
self.known_modules = {}
@@ -223,6 +227,7 @@ _importer = _SixMetaPathImporter(__name__)
class _MovedItems(_LazyModule):
+
"""Lazy loading of moved objects"""
__path__ = [] # mark as package
@@ -234,8 +239,10 @@ _moved_attributes = [
MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
MovedAttribute("intern", "__builtin__", "sys"),
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
+ MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
+ MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
- MovedAttribute("reload_module", "__builtin__", "imp", "reload"),
+ MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
MovedAttribute("reduce", "__builtin__", "functools"),
MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
MovedAttribute("StringIO", "StringIO", "io"),
@@ -245,7 +252,6 @@ _moved_attributes = [
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
-
MovedModule("builtins", "__builtin__"),
MovedModule("configparser", "ConfigParser"),
MovedModule("copyreg", "copy_reg"),
@@ -311,6 +317,7 @@ _importer._add_module(moves, "moves")
class Module_six_moves_urllib_parse(_LazyModule):
+
"""Lazy loading of moved objects in six.moves.urllib_parse"""
@@ -350,6 +357,7 @@ _importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_pa
class Module_six_moves_urllib_error(_LazyModule):
+
"""Lazy loading of moved objects in six.moves.urllib_error"""
@@ -369,6 +377,7 @@ _importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.er
class Module_six_moves_urllib_request(_LazyModule):
+
"""Lazy loading of moved objects in six.moves.urllib_request"""
@@ -418,6 +427,7 @@ _importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.
class Module_six_moves_urllib_response(_LazyModule):
+
"""Lazy loading of moved objects in six.moves.urllib_response"""
@@ -438,6 +448,7 @@ _importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib
class Module_six_moves_urllib_robotparser(_LazyModule):
+
"""Lazy loading of moved objects in six.moves.urllib_robotparser"""
@@ -455,6 +466,7 @@ _importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.url
class Module_six_moves_urllib(types.ModuleType):
+
"""Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
__path__ = [] # mark as package
parse = _importer._get_module("moves.urllib_parse")
@@ -525,6 +537,9 @@ if PY3:
create_bound_method = types.MethodType
+ def create_unbound_method(func, cls):
+ return func
+
Iterator = object
else:
def get_unbound_function(unbound):
@@ -533,6 +548,9 @@ else:
def create_bound_method(func, obj):
return types.MethodType(func, obj, obj.__class__)
+ def create_unbound_method(func, cls):
+ return types.MethodType(func, None, cls)
+
class Iterator(object):
def next(self):
@@ -571,16 +589,16 @@ if PY3:
viewitems = operator.methodcaller("items")
else:
def iterkeys(d, **kw):
- return iter(d.iterkeys(**kw))
+ return d.iterkeys(**kw)
def itervalues(d, **kw):
- return iter(d.itervalues(**kw))
+ return d.itervalues(**kw)
def iteritems(d, **kw):
- return iter(d.iteritems(**kw))
+ return d.iteritems(**kw)
def iterlists(d, **kw):
- return iter(d.iterlists(**kw))
+ return d.iterlists(**kw)
viewkeys = operator.methodcaller("viewkeys")
@@ -599,6 +617,7 @@ _add_doc(iterlists,
if PY3:
def b(s):
return s.encode("latin-1")
+
def u(s):
return s
unichr = chr
@@ -614,29 +633,53 @@ if PY3:
import io
StringIO = io.StringIO
BytesIO = io.BytesIO
+ _assertCountEqual = "assertCountEqual"
+ if sys.version_info[1] <= 1:
+ _assertRaisesRegex = "assertRaisesRegexp"
+ _assertRegex = "assertRegexpMatches"
+ else:
+ _assertRaisesRegex = "assertRaisesRegex"
+ _assertRegex = "assertRegex"
else:
def b(s):
return s
# Workaround for standalone backslash
+
def u(s):
return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
unichr = unichr
int2byte = chr
+
def byte2int(bs):
return ord(bs[0])
+
def indexbytes(buf, i):
return ord(buf[i])
iterbytes = functools.partial(itertools.imap, ord)
import StringIO
StringIO = BytesIO = StringIO.StringIO
+ _assertCountEqual = "assertItemsEqual"
+ _assertRaisesRegex = "assertRaisesRegexp"
+ _assertRegex = "assertRegexpMatches"
_add_doc(b, """Byte literal""")
_add_doc(u, """Text literal""")
+def assertCountEqual(self, *args, **kwargs):
+ return getattr(self, _assertCountEqual)(*args, **kwargs)
+
+
+def assertRaisesRegex(self, *args, **kwargs):
+ return getattr(self, _assertRaisesRegex)(*args, **kwargs)
+
+
+def assertRegex(self, *args, **kwargs):
+ return getattr(self, _assertRegex)(*args, **kwargs)
+
+
if PY3:
exec_ = getattr(moves.builtins, "exec")
-
def reraise(tp, value, tb=None):
if value is None:
value = tp()
@@ -657,12 +700,26 @@ else:
_locs_ = _globs_
exec("""exec _code_ in _globs_, _locs_""")
-
exec_("""def reraise(tp, value, tb=None):
raise tp, value, tb
""")
+if sys.version_info[:2] == (3, 2):
+ exec_("""def raise_from(value, from_value):
+ if from_value is None:
+ raise value
+ raise value from from_value
+""")
+elif sys.version_info[:2] > (3, 2):
+ exec_("""def raise_from(value, from_value):
+ raise value from from_value
+""")
+else:
+ def raise_from(value, from_value):
+ raise value
+
+
print_ = getattr(moves.builtins, "print", None)
if print_ is None:
def print_(*args, **kwargs):
@@ -670,13 +727,14 @@ if print_ is None:
fp = kwargs.pop("file", sys.stdout)
if fp is None:
return
+
def write(data):
if not isinstance(data, basestring):
data = str(data)
# If the file has an encoding, encode unicode with it.
if (isinstance(fp, file) and
- isinstance(data, unicode) and
- fp.encoding is not None):
+ isinstance(data, unicode) and
+ fp.encoding is not None):
errors = getattr(fp, "errors", None)
if errors is None:
errors = "strict"
@@ -717,6 +775,15 @@ if print_ is None:
write(sep)
write(arg)
write(end)
+if sys.version_info[:2] < (3, 3):
+ _print = print_
+
+ def print_(*args, **kwargs):
+ fp = kwargs.get("file", sys.stdout)
+ flush = kwargs.pop("flush", False)
+ _print(*args, **kwargs)
+ if flush and fp is not None:
+ fp.flush()
_add_doc(reraise, """Reraise an exception.""")
@@ -724,19 +791,21 @@ if sys.version_info[0:2] < (3, 4):
def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
updated=functools.WRAPPER_UPDATES):
def wrapper(f):
- f = functools.wraps(wrapped)(f)
+ f = functools.wraps(wrapped, assigned, updated)(f)
f.__wrapped__ = wrapped
return f
return wrapper
else:
wraps = functools.wraps
+
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
# This requires a bit of explanation: the basic idea is to make a dummy
# metaclass for one level of class instantiation that replaces itself with
# the actual metaclass.
class metaclass(meta):
+
def __new__(cls, name, this_bases, d):
return meta(name, bases, d)
return type.__new__(metaclass, 'temporary_class', (), {})
@@ -757,6 +826,25 @@ def add_metaclass(metaclass):
return metaclass(cls.__name__, cls.__bases__, orig_vars)
return wrapper
+
+def python_2_unicode_compatible(klass):
+ """
+ A decorator that defines __unicode__ and __str__ methods under Python 2.
+ Under Python 3 it does nothing.
+
+ To support Python 2 and 3 with a single code base, define a __str__ method
+ returning text and apply this decorator to the class.
+ """
+ if PY2:
+ if '__str__' not in klass.__dict__:
+ raise ValueError("@python_2_unicode_compatible cannot be applied "
+ "to %s because it doesn't define __str__()." %
+ klass.__name__)
+ klass.__unicode__ = klass.__str__
+ klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
+ return klass
+
+
# Complete the moves implementation.
# This code is at the end of this module to speed up module loading.
# Turn this module into a package.
@@ -774,7 +862,7 @@ if sys.meta_path:
# the six meta path importer, since the other six instance will have
# inserted an importer with different class.
if (type(importer).__name__ == "_SixMetaPathImporter" and
- importer.name == __name__):
+ importer.name == __name__):
del sys.meta_path[i]
break
del i, importer
diff --git a/test_six.py b/test_six.py
index 4163520..060a966 100644
--- a/test_six.py
+++ b/test_six.py
@@ -1,6 +1,7 @@
import operator
import sys
import types
+import unittest
import py
@@ -389,7 +390,7 @@ def test_dictionary_iterators(monkeypatch):
monkeypatch.undo()
-@py.test.mark.skipif(sys.version_info[:2] < (2, 7),
+@py.test.mark.skipif("sys.version_info[:2] < (2, 7)",
reason="view methods on dictionaries only available on 2.7+")
def test_dictionary_views():
def stock_method_name(viewwhat):
@@ -455,6 +456,20 @@ def test_create_bound_method():
assert b() is x
+def test_create_unbound_method():
+ class X(object):
+ pass
+
+ def f(self):
+ return self
+ u = six.create_unbound_method(f, X)
+ py.test.raises(TypeError, u)
+ if six.PY2:
+ assert isinstance(u, types.MethodType)
+ x = X()
+ assert f(x) is x
+
+
if six.PY3:
def test_b():
@@ -588,6 +603,27 @@ def test_reraise():
assert tb is get_next(tb2)
+def test_raise_from():
+ try:
+ try:
+ raise Exception("blah")
+ except Exception:
+ ctx = sys.exc_info()[1]
+ f = Exception("foo")
+ six.raise_from(f, None)
+ except Exception:
+ tp, val, tb = sys.exc_info()
+ if sys.version_info[:2] > (3, 0):
+ # We should have done a raise f from None equivalent.
+ assert val.__cause__ is None
+ assert val.__context__ is ctx
+ if sys.version_info[:2] >= (3, 3):
+ # And that should suppress the context on the exception.
+ assert val.__suppress_context__
+ # For all versions the outer exception should have raised successfully.
+ assert str(val) == "foo"
+
+
def test_print_():
save = sys.stdout
out = sys.stdout = six.moves.StringIO()
@@ -614,6 +650,17 @@ def test_print_():
out = six.StringIO()
six.print_(None, file=out)
assert out.getvalue() == "None\n"
+ class FlushableStringIO(six.StringIO):
+ def __init__(self):
+ six.StringIO.__init__(self)
+ self.flushed = False
+ def flush(self):
+ self.flushed = True
+ out = FlushableStringIO()
+ six.print_("Hello", file=out)
+ assert not out.flushed
+ six.print_("Hello", file=out, flush=True)
+ assert out.flushed
@py.test.mark.skipif("sys.version_info[:2] >= (2, 6)")
@@ -680,6 +727,18 @@ def test_wraps():
assert k is original_k
assert not hasattr(k, '__wrapped__')
+ def f(g, assign, update):
+ def w():
+ return 42
+ w.glue = {"foo" : "bar"}
+ return six.wraps(g, assign, update)(w)
+ k.glue = {"melon" : "egg"}
+ k.turnip = 43
+ k = f(k, ["turnip"], ["glue"])
+ assert k.__name__ == "w"
+ assert k.turnip == 43
+ assert k.glue == {"melon" : "egg", "foo" : "bar"}
+
def test_add_metaclass():
class Meta(type):
@@ -752,3 +811,62 @@ def test_add_metaclass():
__slots__ = "__weakref__",
MySlotsWeakref = six.add_metaclass(Meta)(MySlotsWeakref)
assert type(MySlotsWeakref) is Meta
+
+
+@py.test.mark.skipif("sys.version_info[:2] < (2, 7) or sys.version_info[:2] in ((3, 0), (3, 1))")
+def test_assertCountEqual():
+ class TestAssertCountEqual(unittest.TestCase):
+ def test(self):
+ with self.assertRaises(AssertionError):
+ six.assertCountEqual(self, (1, 2), [3, 4, 5])
+
+ six.assertCountEqual(self, (1, 2), [2, 1])
+
+ TestAssertCountEqual('test').test()
+
+
+@py.test.mark.skipif("sys.version_info[:2] < (2, 7)")
+def test_assertRegex():
+ class TestAssertRegex(unittest.TestCase):
+ def test(self):
+ with self.assertRaises(AssertionError):
+ six.assertRegex(self, 'test', r'^a')
+
+ six.assertRegex(self, 'test', r'^t')
+
+ TestAssertRegex('test').test()
+
+
+@py.test.mark.skipif("sys.version_info[:2] < (2, 7)")
+def test_assertRaisesRegex():
+ class TestAssertRaisesRegex(unittest.TestCase):
+ def test(self):
+ with six.assertRaisesRegex(self, AssertionError, '^Foo'):
+ raise AssertionError('Foo')
+
+ with self.assertRaises(AssertionError):
+ with six.assertRaisesRegex(self, AssertionError, r'^Foo'):
+ raise AssertionError('Bar')
+
+ TestAssertRaisesRegex('test').test()
+
+
+def test_python_2_unicode_compatible():
+ @six.python_2_unicode_compatible
+ class MyTest(object):
+ def __str__(self):
+ return six.u('hello')
+
+ def __bytes__(self):
+ return six.b('hello')
+
+ my_test = MyTest()
+
+ if six.PY2:
+ assert str(my_test) == six.b("hello")
+ assert unicode(my_test) == six.u("hello")
+ elif six.PY3:
+ assert bytes(my_test) == six.b("hello")
+ assert str(my_test) == six.u("hello")
+
+ assert getattr(six.moves.builtins, 'bytes', str)(my_test) == six.b("hello")
diff --git a/tox.ini b/tox.ini
index b29b31a..617b7df 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,12 +1,15 @@
[tox]
-envlist=py25,py26,py27,py31,py32,py33,py34,pypy
+envlist=py26,py27,py31,py32,py33,py34,pypy,flake8
indexserver=
default = http://pypi.python.org/simple
testrun = http://pypi.testrun.org
[testenv]
-deps=pytest
+deps= pytest
commands= py.test -rfsxX {posargs}
-[pytest]
-minversion=2.2.0
+[testenv:flake8]
+basepython=python
+deps=flake8
+commands= flake8 six.py
+