* Testtools now depends on extras, a small library split out from it to contain

generally useful non-testing facilities. Since extras has been around for a
  couple of testtools releases now, we're making this into a hard dependency of
  testtools. (Robert Collins)

* Testtools now uses setuptools rather than distutils so that we can document
  the extras dependency. (Robert Collins)
This commit is contained in:
Robert Collins
2013-01-18 22:17:19 +13:00
parent aaa3bbca1a
commit b3416e44da
21 changed files with 61 additions and 285 deletions

View File

@@ -8,3 +8,4 @@ apidocs
_trial_temp
doc/_build
./.testrepository
./testtools.egg-info

11
NEWS
View File

@@ -6,6 +6,17 @@ Changes and improvements to testtools_, grouped by release.
NEXT
~~~~
Changes
-------
* Testtools now depends on extras, a small library split out from it to contain
generally useful non-testing facilities. Since extras has been around for a
couple of testtools releases now, we're making this into a hard dependency of
testtools. (Robert Collins)
* Testtools now uses setuptools rather than distutils so that we can document
the extras dependency. (Robert Collins)
0.9.24
~~~~~~

3
README
View File

@@ -36,6 +36,9 @@ Required Dependencies
If you would like to use testtools for earlier Python's, please use testtools
0.9.15.
* extras (helpers that we intend to push into Python itself in the near
future).
Optional Dependencies
---------------------

View File

@@ -1288,7 +1288,7 @@ Conditional imports
-------------------
Lots of the time we would like to conditionally import modules. testtools
needs to do this itself, and graciously extends the ability to its users.
uses the small library extras to do this. This used to be part of testtools.
Instead of::
@@ -1317,9 +1317,9 @@ You can do::
Safe attribute testing
----------------------
``hasattr`` is broken_ on many versions of Python. testtools provides
``safe_hasattr``, which can be used to safely test whether an object has a
particular attribute.
``hasattr`` is broken_ on many versions of Python. The helper ``safe_hasattr``
can be used to safely test whether an object has a particular attribute. Like
``try_import`` this used to be in testtools but is now in extras.
Nullary callables

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
"""Distutils installer for testtools."""
from distutils.core import setup
from setuptools import setup
import email
import os
@@ -82,4 +82,8 @@ setup(name='testtools',
'testtools.tests.matchers',
],
cmdclass={'test': testtools.TestCommand},
zip_safe=False)
zip_safe=False,
install_requires=[
'extras',
],
)

View File

@@ -30,10 +30,12 @@ __all__ = [
'try_imports',
]
from testtools.helpers import (
# Compat - removal announced in 0.9.25.
from extras import (
try_import,
try_imports,
)
from testtools.matchers._impl import (
Matcher,
)

View File

@@ -27,7 +27,7 @@ import sys
import traceback
import unicodedata
from testtools.helpers import try_imports
from extras import try_imports
BytesIO = try_imports(['StringIO.StringIO', 'io.BytesIO'])
StringIO = try_imports(['StringIO.StringIO', 'io.StringIO'])

View File

@@ -17,7 +17,8 @@ import os
import sys
import traceback
from testtools import try_import
from extras import try_import
from testtools.compat import _b, _format_exc_info, str_is_unicode, _u
from testtools.content_type import ContentType, JSON, UTF8_TEXT

View File

@@ -8,83 +8,12 @@ __all__ = [
import sys
def try_import(name, alternative=None, error_callback=None):
"""Attempt to import ``name``. If it fails, return ``alternative``.
When supporting multiple versions of Python or optional dependencies, it
is useful to be able to try to import a module.
:param name: The name of the object to import, e.g. ``os.path`` or
``os.path.join``.
:param alternative: The value to return if no module can be imported.
Defaults to None.
:param error_callback: If non-None, a callable that is passed the ImportError
when the module cannot be loaded.
"""
module_segments = name.split('.')
last_error = None
while module_segments:
module_name = '.'.join(module_segments)
try:
module = __import__(module_name)
except ImportError:
last_error = sys.exc_info()[1]
module_segments.pop()
continue
else:
break
else:
if last_error is not None and error_callback is not None:
error_callback(last_error)
return alternative
nonexistent = object()
for segment in name.split('.')[1:]:
module = getattr(module, segment, nonexistent)
if module is nonexistent:
if last_error is not None and error_callback is not None:
error_callback(last_error)
return alternative
return module
_RAISE_EXCEPTION = object()
def try_imports(module_names, alternative=_RAISE_EXCEPTION, error_callback=None):
"""Attempt to import modules.
Tries to import the first module in ``module_names``. If it can be
imported, we return it. If not, we go on to the second module and try
that. The process continues until we run out of modules to try. If none
of the modules can be imported, either raise an exception or return the
provided ``alternative`` value.
:param module_names: A sequence of module names to try to import.
:param alternative: The value to return if no module can be imported.
If unspecified, we raise an ImportError.
:param error_callback: If None, called with the ImportError for *each*
module that fails to load.
:raises ImportError: If none of the modules can be imported and no
alternative value was specified.
"""
module_names = list(module_names)
for module_name in module_names:
module = try_import(module_name, error_callback=error_callback)
if module:
return module
if alternative is _RAISE_EXCEPTION:
raise ImportError(
"Could not import any of: %s" % ', '.join(module_names))
return alternative
def safe_hasattr(obj, attr, _marker=object()):
"""Does 'obj' have an attribute 'attr'?
Use this rather than built-in hasattr, as the built-in swallows exceptions
in some versions of Python and behaves unpredictably with respect to
properties.
"""
return getattr(obj, attr, _marker) is not _marker
# Compat - removal announced in 0.9.25.
from extras import (
safe_hasattr,
try_import,
try_imports,
)
def map_values(function, dictionary):

View File

@@ -20,9 +20,10 @@ import sys
import types
import unittest
from extras import try_import
from testtools import (
content,
try_import,
)
from testtools.compat import (
advance_iterator,

View File

@@ -16,12 +16,13 @@ import datetime
import sys
import unittest
from extras import safe_hasattr
from testtools.compat import all, str_is_unicode, _u
from testtools.content import (
text_content,
TracebackContent,
)
from testtools.helpers import safe_hasattr
from testtools.tags import TagContext
# From http://docs.python.org/library/datetime.html

View File

@@ -8,10 +8,9 @@ __all__ = [
import sys
from extras import safe_hasattr
from testtools import TestResult
from testtools.helpers import (
safe_hasattr,
)
from testtools.content import TracebackContent
from testtools import runtest

View File

@@ -5,6 +5,8 @@
import os
import signal
from extras import try_import
from testtools import (
skipIf,
TestCase,
@@ -13,7 +15,6 @@ from testtools import (
from testtools.content import (
text_content,
)
from testtools.helpers import try_import
from testtools.matchers import (
Equals,
KeysEqual,

View File

@@ -4,12 +4,13 @@
from distutils.dist import Distribution
from extras import try_import
from testtools.compat import (
_b,
_u,
BytesIO,
)
from testtools.helpers import try_import
fixtures = try_import('fixtures')
import testtools

View File

@@ -2,13 +2,14 @@
import unittest
from extras import try_import
from testtools import (
TestCase,
content,
content_type,
)
from testtools.compat import _b, _u
from testtools.helpers import try_import
from testtools.testresult.doubles import (
ExtendedTestResult,
)

View File

@@ -1,196 +1,13 @@
# Copyright (c) 2010-2012 testtools developers. See LICENSE for details.
from testtools import TestCase
from testtools.helpers import (
try_import,
try_imports,
)
from testtools.matchers import (
Equals,
Is,
Not,
)
from testtools.tests.helpers import (
FullStackRunTest,
hide_testtools_stack,
is_stack_hidden,
safe_hasattr,
)
def check_error_callback(test, function, arg, expected_error_count,
expect_result):
"""General test template for error_callback argument.
:param test: Test case instance.
:param function: Either try_import or try_imports.
:param arg: Name or names to import.
:param expected_error_count: Expected number of calls to the callback.
:param expect_result: Boolean for whether a module should
ultimately be returned or not.
"""
cb_calls = []
def cb(e):
test.assertIsInstance(e, ImportError)
cb_calls.append(e)
try:
result = function(arg, error_callback=cb)
except ImportError:
test.assertFalse(expect_result)
else:
if expect_result:
test.assertThat(result, Not(Is(None)))
else:
test.assertThat(result, Is(None))
test.assertEquals(len(cb_calls), expected_error_count)
class TestSafeHasattr(TestCase):
def test_attribute_not_there(self):
class Foo(object):
pass
self.assertEqual(False, safe_hasattr(Foo(), 'anything'))
def test_attribute_there(self):
class Foo(object):
pass
foo = Foo()
foo.attribute = None
self.assertEqual(True, safe_hasattr(foo, 'attribute'))
def test_property_there(self):
class Foo(object):
@property
def attribute(self):
return None
foo = Foo()
self.assertEqual(True, safe_hasattr(foo, 'attribute'))
def test_property_raises(self):
class Foo(object):
@property
def attribute(self):
1/0
foo = Foo()
self.assertRaises(ZeroDivisionError, safe_hasattr, foo, 'attribute')
class TestTryImport(TestCase):
def test_doesnt_exist(self):
# try_import('thing', foo) returns foo if 'thing' doesn't exist.
marker = object()
result = try_import('doesntexist', marker)
self.assertThat(result, Is(marker))
def test_None_is_default_alternative(self):
# try_import('thing') returns None if 'thing' doesn't exist.
result = try_import('doesntexist')
self.assertThat(result, Is(None))
def test_existing_module(self):
# try_import('thing', foo) imports 'thing' and returns it if it's a
# module that exists.
result = try_import('os', object())
import os
self.assertThat(result, Is(os))
def test_existing_submodule(self):
# try_import('thing.another', foo) imports 'thing' and returns it if
# it's a module that exists.
result = try_import('os.path', object())
import os
self.assertThat(result, Is(os.path))
def test_nonexistent_submodule(self):
# try_import('thing.another', foo) imports 'thing' and returns foo if
# 'another' doesn't exist.
marker = object()
result = try_import('os.doesntexist', marker)
self.assertThat(result, Is(marker))
def test_object_from_module(self):
# try_import('thing.object') imports 'thing' and returns
# 'thing.object' if 'thing' is a module and 'object' is not.
result = try_import('os.path.join')
import os
self.assertThat(result, Is(os.path.join))
def test_error_callback(self):
# the error callback is called on failures.
check_error_callback(self, try_import, 'doesntexist', 1, False)
def test_error_callback_missing_module_member(self):
# the error callback is called on failures to find an object
# inside an existing module.
check_error_callback(self, try_import, 'os.nonexistent', 1, False)
def test_error_callback_not_on_success(self):
# the error callback is not called on success.
check_error_callback(self, try_import, 'os.path', 0, True)
class TestTryImports(TestCase):
def test_doesnt_exist(self):
# try_imports('thing', foo) returns foo if 'thing' doesn't exist.
marker = object()
result = try_imports(['doesntexist'], marker)
self.assertThat(result, Is(marker))
def test_fallback(self):
result = try_imports(['doesntexist', 'os'])
import os
self.assertThat(result, Is(os))
def test_None_is_default_alternative(self):
# try_imports('thing') returns None if 'thing' doesn't exist.
e = self.assertRaises(
ImportError, try_imports, ['doesntexist', 'noreally'])
self.assertThat(
str(e),
Equals("Could not import any of: doesntexist, noreally"))
def test_existing_module(self):
# try_imports('thing', foo) imports 'thing' and returns it if it's a
# module that exists.
result = try_imports(['os'], object())
import os
self.assertThat(result, Is(os))
def test_existing_submodule(self):
# try_imports('thing.another', foo) imports 'thing' and returns it if
# it's a module that exists.
result = try_imports(['os.path'], object())
import os
self.assertThat(result, Is(os.path))
def test_nonexistent_submodule(self):
# try_imports('thing.another', foo) imports 'thing' and returns foo if
# 'another' doesn't exist.
marker = object()
result = try_imports(['os.doesntexist'], marker)
self.assertThat(result, Is(marker))
def test_fallback_submodule(self):
result = try_imports(['os.doesntexist', 'os.path'])
import os
self.assertThat(result, Is(os.path))
def test_error_callback(self):
# One error for every class that doesn't exist.
check_error_callback(self, try_imports,
['os.doesntexist', 'os.notthiseither'],
2, False)
check_error_callback(self, try_imports,
['os.doesntexist', 'os.notthiseither', 'os'],
2, True)
check_error_callback(self, try_imports,
['os.path'],
0, True)
class TestStackHiding(TestCase):
run_tests_with = FullStackRunTest

View File

@@ -4,11 +4,12 @@
from unittest import TestSuite
from extras import try_import
from testtools.compat import (
_b,
StringIO,
)
from testtools.helpers import try_import
fixtures = try_import('fixtures')
import testtools

View File

@@ -5,11 +5,12 @@
import os
import signal
from extras import try_import
from testtools import (
skipIf,
TestCase,
)
from testtools.helpers import try_import
from testtools.matchers import (
Equals,
Is,

View File

@@ -15,6 +15,8 @@ import threading
from unittest import TestSuite
import warnings
from extras import safe_hasattr
from testtools import (
ExtendedToOriginalDecorator,
MultiTestResult,
@@ -44,7 +46,6 @@ from testtools.content import (
TracebackContent,
)
from testtools.content_type import ContentType, UTF8_TEXT
from testtools.helpers import safe_hasattr
from testtools.matchers import (
Contains,
DocTestMatches,

View File

@@ -6,13 +6,14 @@ __metaclass__ = type
import unittest
from extras import try_import
from testtools import (
ConcurrentTestSuite,
iterate_tests,
PlaceHolder,
TestCase,
)
from testtools.helpers import try_import
from testtools.testsuite import FixtureSuite, iterate_tests, sorted_tests
from testtools.tests.helpers import LoggingResult

View File

@@ -9,13 +9,13 @@ __all__ = [
'sorted_tests',
]
from testtools.helpers import safe_hasattr, try_imports
Queue = try_imports(['Queue.Queue', 'queue.Queue'])
import threading
import unittest
from extras import safe_hasattr, try_imports
Queue = try_imports(['Queue.Queue', 'queue.Queue'])
import testtools