summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2017-01-16 07:58:53 -0500
committerNed Batchelder <ned@nedbatchelder.com>2017-01-16 07:58:53 -0500
commit5f5510fba829e12d57adb949667ce8e3c5ff09d9 (patch)
tree9bdf075c6e0b02abf2865f21a56b655a07bb4c52
parent8175a927c0189ff06e9f961bd780c9a8b7280abf (diff)
downloadpython-coveragepy-git-5f5510fba829e12d57adb949667ce8e3c5ff09d9.tar.gz
A one_of decorator for checking function arguments.
-rw-r--r--coverage/misc.py17
-rw-r--r--pylintrc27
-rw-r--r--tests/test_misc.py23
3 files changed, 49 insertions, 18 deletions
diff --git a/coverage/misc.py b/coverage/misc.py
index 240a2587..e78a1537 100644
--- a/coverage/misc.py
+++ b/coverage/misc.py
@@ -58,6 +58,17 @@ if env.TESTING:
new_contract('bytes', lambda v: isinstance(v, bytes))
if env.PY3:
new_contract('unicode', lambda v: isinstance(v, unicode_class))
+
+ def one_of(argnames):
+ """Ensure that only one of the argnames is non-None."""
+ def _decorator(func):
+ argnameset = set(name.strip() for name in argnames.split(","))
+ def _wrapped(*args, **kwargs):
+ vals = set(kwargs.get(name) for name in argnameset)
+ assert sum(val is not None for val in vals) == 1
+ return func(*args, **kwargs)
+ return _wrapped
+ return _decorator
else: # pragma: not covered
# We aren't using real PyContracts, so just define a no-op decorator as a
# stunt double.
@@ -69,6 +80,12 @@ else: # pragma: not covered
"""Dummy no-op implementation of `new_contract`."""
pass
+ def one_of(argnames_unused):
+ """Dummy no-op implementation of `one_of`."""
+ def _decorator(func):
+ return func
+ return _decorator
+
def nice_pair(pair):
"""Make a nice string representation of a pair of numbers.
diff --git a/pylintrc b/pylintrc
index b3921667..c9f1dc3d 100644
--- a/pylintrc
+++ b/pylintrc
@@ -2,13 +2,13 @@
# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
# lint Python modules using external checkers.
-#
+#
# This is the main checker controlling the other ones and the reports
# generation. It is itself both a raw checker and an astng checker in order
# to:
# * handle message activation / deactivation at the module level
# * handle some basic but necessary stats'data (number of classes, methods...)
-#
+#
[MASTER]
# Specify a configuration file.
@@ -54,7 +54,7 @@ enable=
useless-suppression
# Disable the message(s) with the given id(s).
-disable=
+disable=
spelling,
# Messages that are just silly:
locally-disabled,
@@ -118,7 +118,7 @@ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / stateme
# * dangerous default values as arguments
# * redefinition of function / method / class
# * uses of the global statement
-#
+#
[BASIC]
# Regular expression which should only match functions or classes name which do
@@ -126,8 +126,9 @@ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / stateme
# Special methods don't: __foo__
# Test methods don't: testXXXX
# TestCase overrides don't: setUp, tearDown
+# Nested decorator implementations: _decorator, _wrapped
# Dispatched methods don't: _xxx__Xxxx
-no-docstring-rgx=__.*__|test[A-Z_].*|setUp|tearDown|_.*__.*
+no-docstring-rgx=__.*__|test[A-Z_].*|setUp|tearDown|_decorator|_wrapped|_.*__.*
# Regular expression which should only match correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
@@ -168,7 +169,7 @@ bad-functions=
# try to find bugs in the code using type inference
-#
+#
[TYPECHECK]
# Tells wether missing members accessed in mixin class should be ignored. A
@@ -189,7 +190,7 @@ acquired-members=REQUEST,acl_users,aq_parent
# * undefined variables
# * redefinition of variable from builtins or from an outer scope
# * use of variable before assigment
-#
+#
[VARIABLES]
# Tells wether we should check for unused import in __init__ files.
@@ -210,7 +211,7 @@ additional-builtins=
# * attributes not defined in the __init__ method
# * supported interfaces implementation
# * unreachable code
-#
+#
[CLASSES]
# List of method names used to declare (i.e. assign) instance attributes.
@@ -220,7 +221,7 @@ defining-attr-methods=__init__,__new__,setUp,reset
# checks for sign of poor/misdesign:
# * number of methods, attributes, local variables...
# * size, complexity of functions, methods
-#
+#
[DESIGN]
# Maximum number of arguments for function / method
@@ -256,7 +257,7 @@ max-public-methods=500
# * relative / wildcard imports
# * cyclic imports
# * uses of deprecated modules
-#
+#
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
@@ -280,7 +281,7 @@ int-import-graph=
# * strict indentation
# * line length
# * use of <> instead of !=
-#
+#
[FORMAT]
# Maximum number of characters on a single line.
@@ -297,7 +298,7 @@ indent-string=' '
# checks for:
# * warning notes in the code like FIXME, XXX
# * PEP 263: source code with non ascii character but no encoding declaration
-#
+#
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
@@ -307,7 +308,7 @@ notes=FIXME,XXX,TODO
# checks for similarities and duplicated code. This computation may be
# memory / CPU intensive, so you should disable it if you experiments some
# problems.
-#
+#
[SIMILARITIES]
# Minimum lines number of a similarity.
diff --git a/tests/test_misc.py b/tests/test_misc.py
index d3dede4f..0b6c9144 100644
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -9,7 +9,7 @@ import pytest
import coverage
from coverage.version import _make_url, _make_version
-from coverage.misc import contract, Hasher, file_be_gone
+from coverage.misc import contract, file_be_gone, Hasher, one_of
from tests.coveragetest import CoverageTest
@@ -78,24 +78,37 @@ class ContractTest(CoverageTest):
def test_bytes(self):
@contract(text='bytes|None')
- def need_bytes(text):
+ def need_bytes(text=None): # pylint: disable=missing-docstring
return text
assert need_bytes(b"Hey") == b"Hey"
- assert need_bytes(None) == None
+ assert need_bytes() is None
with pytest.raises(Exception):
need_bytes(u"Oops")
def test_unicode(self):
@contract(text='unicode|None')
- def need_unicode(text):
+ def need_unicode(text=None): # pylint: disable=missing-docstring
return text
assert need_unicode(u"Hey") == u"Hey"
- assert need_unicode(None) == None
+ assert need_unicode() is None
with pytest.raises(Exception):
need_unicode(b"Oops")
+ def test_one_of(self):
+ @one_of("a, b, c")
+ def give_me_one(a=None, b=None, c=None): # pylint: disable=missing-docstring
+ return (a, b, c)
+
+ assert give_me_one(a=17) == (17, None, None)
+ assert give_me_one(b=17) == (None, 17, None)
+ assert give_me_one(c=17) == (None, None, 17)
+ with pytest.raises(AssertionError):
+ give_me_one(a=17, b=23)
+ with pytest.raises(AssertionError):
+ give_me_one()
+
class VersionTest(CoverageTest):
"""Tests of version.py"""