Skip to content

Commit b6dd7b2

Browse files
committed
Remove code to support Python versions before 3.7
1 parent b9f6a64 commit b6dd7b2

File tree

2 files changed

+32
-170
lines changed

2 files changed

+32
-170
lines changed

parameterized/parameterized.py

Lines changed: 29 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -2,83 +2,10 @@
22
import sys
33
import inspect
44
import warnings
5-
from typing import Iterable
5+
from collections.abc import Iterable
66
from functools import wraps
7-
from types import MethodType as MethodType
87
from collections import namedtuple
9-
10-
try:
11-
from unittest import mock
12-
except ImportError:
13-
try:
14-
import mock
15-
except ImportError:
16-
mock = None
17-
18-
try:
19-
from collections import OrderedDict as MaybeOrderedDict
20-
except ImportError:
21-
MaybeOrderedDict = dict
22-
23-
from unittest import TestCase
24-
25-
try:
26-
from unittest import SkipTest
27-
except ImportError:
28-
class SkipTest(Exception):
29-
pass
30-
31-
# NOTE: even though Python 2 support has been dropped, these checks have been
32-
# left in place to avoid merge conflicts. They can be removed in the future, and
33-
# future code can be written to assume Python 3.
34-
PY3 = sys.version_info[0] == 3
35-
PY2 = sys.version_info[0] == 2
36-
37-
38-
if PY3:
39-
# Python 3 doesn't have an InstanceType, so just use a dummy type.
40-
class InstanceType():
41-
pass
42-
lzip = lambda *a: list(zip(*a))
43-
text_type = str
44-
string_types = str,
45-
bytes_type = bytes
46-
def make_method(func, instance, type):
47-
if instance is None:
48-
return func
49-
return MethodType(func, instance)
50-
else:
51-
from types import InstanceType
52-
lzip = zip
53-
text_type = unicode
54-
bytes_type = str
55-
string_types = basestring,
56-
def make_method(func, instance, type):
57-
return MethodType(func, instance, type)
58-
59-
def to_text(x):
60-
if isinstance(x, text_type):
61-
return x
62-
try:
63-
return text_type(x, "utf-8")
64-
except UnicodeDecodeError:
65-
return text_type(x, "latin1")
66-
67-
CompatArgSpec = namedtuple("CompatArgSpec", "args varargs keywords defaults")
68-
69-
70-
def getargspec(func):
71-
if PY2:
72-
return CompatArgSpec(*inspect.getargspec(func))
73-
args = inspect.getfullargspec(func)
74-
if args.kwonlyargs:
75-
raise TypeError((
76-
"parameterized does not (yet) support functions with keyword "
77-
"only arguments, but %r has keyword only arguments. "
78-
"Please open an issue with your usecase if this affects you: "
79-
"https://github.com/wolever/parameterized/issues/new"
80-
) %(func, ))
81-
return CompatArgSpec(*args[:4])
8+
from unittest import SkipTest, TestCase, mock
829

8310

8411
def skip_on_empty_helper(*a, **kw):
@@ -142,10 +69,7 @@ class DummyPatchTarget(object):
14269

14370
@staticmethod
14471
def create_dummy_patch():
145-
if mock is not None:
146-
return mock.patch.object(DummyPatchTarget(), "dummy_attribute", new=None)
147-
else:
148-
raise ImportError("Missing mock package")
72+
return mock.patch.object(DummyPatchTarget(), "dummy_attribute", new=None)
14973

15074

15175
def delete_patches_if_need(func):
@@ -158,6 +82,7 @@ def delete_patches_if_need(func):
15882

15983
_param = namedtuple("param", "args kwargs")
16084

85+
16186
class param(_param):
16287
""" Represents a single parameter to a test case.
16388
@@ -180,7 +105,7 @@ def test_stuff(foo, bar=16):
180105
pass
181106
"""
182107

183-
def __new__(cls, *args , **kwargs):
108+
def __new__(cls, *args, **kwargs):
184109
return _param.__new__(cls, args, kwargs)
185110

186111
@classmethod
@@ -225,13 +150,6 @@ def __repr__(self):
225150
return "param(*%r, **%r)" %self
226151

227152

228-
class QuietOrderedDict(MaybeOrderedDict):
229-
""" When OrderedDict is available, use it to make sure that the kwargs in
230-
doc strings are consistently ordered. """
231-
__str__ = dict.__str__
232-
__repr__ = dict.__repr__
233-
234-
235153
def parameterized_argument_value_pairs(func, p):
236154
"""Return tuples of parameterized arguments and their values.
237155
@@ -261,12 +179,19 @@ def parameterized_argument_value_pairs(func, p):
261179
>>> parameterized_argument_value_pairs(func, p)
262180
[("foo", 1), ("*args", (16, ))]
263181
"""
264-
argspec = getargspec(func)
182+
argspec = inspect.getfullargspec(func)
183+
if argspec.kwonlyargs:
184+
raise TypeError((
185+
"parameterized does not (yet) support functions with keyword "
186+
"only arguments, but %r has keyword only arguments. "
187+
"Please open an issue with your usecase if this affects you: "
188+
"https://github.com/wolever/parameterized/issues/new"
189+
) %(func, ))
265190
arg_offset = 1 if argspec.args[:1] == ["self"] else 0
266191

267192
named_args = argspec.args[arg_offset:]
268193

269-
result = lzip(named_args, p.args)
194+
result = list(zip(named_args, p.args))
270195
named_args = argspec.args[len(result) + arg_offset:]
271196
varargs = p.args[len(result):]
272197

@@ -276,8 +201,8 @@ def parameterized_argument_value_pairs(func, p):
276201
in zip(named_args, argspec.defaults or [])
277202
])
278203

279-
seen_arg_names = set([ n for (n, _) in result ])
280-
keywords = QuietOrderedDict(sorted([
204+
seen_arg_names = {n for (n, _) in result}
205+
keywords = dict(sorted([
281206
(name, p.kwargs[name])
282207
for name in p.kwargs
283208
if name not in seen_arg_names
@@ -287,7 +212,7 @@ def parameterized_argument_value_pairs(func, p):
287212
result.append(("*%s" %(argspec.varargs, ), tuple(varargs)))
288213

289214
if keywords:
290-
result.append(("**%s" %(argspec.keywords, ), keywords))
215+
result.append(("**%s" %(argspec.varkw, ), keywords))
291216

292217
return result
293218

@@ -301,7 +226,7 @@ def short_repr(x, n=64):
301226
u"12...89"
302227
"""
303228

304-
x_repr = to_text(repr(x))
229+
x_repr = repr(x)
305230
if len(x_repr) > n:
306231
x_repr = x_repr[:n//2] + "..." + x_repr[len(x_repr) - n//2:]
307232
return x_repr
@@ -325,24 +250,21 @@ def default_doc_func(func, num, p):
325250
suffix = "."
326251
first = first[:-1]
327252
args = "%s[with %s]" %(len(first) and " " or "", ", ".join(descs))
328-
return "".join(
329-
to_text(x)
330-
for x in [first.rstrip(), args, suffix, nl, rest]
331-
)
253+
return "".join([first.rstrip(), args, suffix, nl, rest])
332254

333255

334256
def default_name_func(func, num, p):
335257
base_name = func.__name__
336258
name_suffix = "_%s" %(num, )
337259

338-
if len(p.args) > 0 and isinstance(p.args[0], string_types):
260+
if len(p.args) > 0 and isinstance(p.args[0], str):
339261
name_suffix += "_" + parameterized.to_safe_name(p.args[0])
340262
return base_name + name_suffix
341263

342264

343265
_test_runner_override = None
344266
_test_runner_guess = False
345-
_test_runners = set(["unittest", "unittest2", "nose", "nose2", "pytest"])
267+
_test_runners = {"unittest", "unittest2", "nose", "nose2", "pytest"}
346268
_test_runner_aliases = {
347269
"_pytest": "pytest",
348270
}
@@ -384,7 +306,6 @@ def detect_runner():
384306
return _test_runner_guess
385307

386308

387-
388309
class parameterized(object):
389310
""" Parameterize a test case::
390311
@@ -417,20 +338,10 @@ def __call__(self, test_func):
417338
@wraps(test_func)
418339
def wrapper(test_self=None):
419340
test_cls = test_self and type(test_self)
420-
if test_self is not None:
421-
if issubclass(test_cls, InstanceType):
422-
raise TypeError((
423-
"@parameterized can't be used with old-style classes, but "
424-
"%r has an old-style class. Consider using a new-style "
425-
"class, or '@parameterized.expand' "
426-
"(see http://stackoverflow.com/q/54867/71522 for more "
427-
"information on old-style classes)."
428-
) %(test_self, ))
429-
430341
original_doc = wrapper.__doc__
431342
for num, args in enumerate(wrapper.parameterized_input):
432343
p = param.from_decorator(args)
433-
unbound_func, nose_tuple = self.param_as_nose_tuple(test_self, test_func, num, p)
344+
nose_tuple = self.param_as_nose_tuple(test_self, test_func, num, p)
434345
try:
435346
wrapper.__doc__ = nose_tuple[0].__doc__
436347
# Nose uses `getattr(instance, test_func.__name__)` to get
@@ -439,7 +350,7 @@ def wrapper(test_self=None):
439350
# tests were being enumerated). Set a value here to make
440351
# sure nose can get the correct test method.
441352
if test_self is not None:
442-
setattr(test_cls, test_func.__name__, unbound_func)
353+
setattr(test_cls, test_func.__name__, nose_tuple[0].__func__)
443354
yield nose_tuple
444355
finally:
445356
if test_self is not None:
@@ -465,21 +376,9 @@ def wrapper(test_self=None):
465376
def param_as_nose_tuple(self, test_self, func, num, p):
466377
nose_func = wraps(func)(lambda *args: func(*args[:-1], **args[-1]))
467378
nose_func.__doc__ = self.doc_func(func, num, p)
468-
# Track the unbound function because we need to setattr the unbound
469-
# function onto the class for nose to work (see comments above), and
470-
# Python 3 doesn't let us pull the function out of a bound method.
471-
unbound_func = nose_func
472379
if test_self is not None:
473-
# Under nose on Py2 we need to return an unbound method to make
474-
# sure that the `self` in the method is properly shared with the
475-
# `self` used in `setUp` and `tearDown`. But only there. Everyone
476-
# else needs a bound method.
477-
func_self = (
478-
None if PY2 and detect_runner() == "nose" else
479-
test_self
480-
)
481-
nose_func = make_method(nose_func, func_self, type(test_self))
482-
return unbound_func, (nose_func, ) + p.args + (p.kwargs or {}, )
380+
nose_func = nose_func.__get__(test_self)
381+
return (nose_func, *p.args, p.kwargs or {})
483382

484383
def assert_not_in_testcase_subclass(self):
485384
parent_classes = self._terrible_magic_get_defining_classes()
@@ -521,7 +420,7 @@ def check_input_values(cls, input_values):
521420
# https://github.com/wolever/nose-parameterized/pull/31)
522421
if not isinstance(input_values, list):
523422
input_values = list(input_values)
524-
return [ param.from_decorator(p) for p in input_values ]
423+
return [param.from_decorator(p) for p in input_values]
525424

526425
@classmethod
527426
def expand(cls, input, name_func=None, doc_func=None, skip_on_empty=False,
@@ -666,7 +565,7 @@ class TestUserAccessLevel(TestCase):
666565
667566
"""
668567

669-
if isinstance(attrs, string_types):
568+
if isinstance(attrs, str):
670569
attrs = [attrs]
671570

672571
input_dicts = (
@@ -713,13 +612,9 @@ def get_class_name_suffix(params_dict):
713612
if "name" in params_dict:
714613
return parameterized.to_safe_name(params_dict["name"])
715614

716-
params_vals = (
717-
params_dict.values() if PY3 else
718-
(v for (_, v) in sorted(params_dict.items()))
719-
)
720615
return parameterized.to_safe_name(next((
721-
v for v in params_vals
722-
if isinstance(v, string_types)
616+
v for v in params_dict.values()
617+
if isinstance(v, str)
723618
), ""))
724619

725620

parameterized/test.py

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22

33
import inspect
44
import sys
5-
import mock
65
from functools import wraps
7-
from unittest import TestCase
6+
from unittest import TestCase, mock
87
try:
98
from nose.tools import assert_equal, assert_raises
109
except ImportError:
@@ -14,7 +13,7 @@ def assert_raises(*args, **kwds):
1413
return TestCase().assertRaises(*args, **kwds)
1514

1615
from .parameterized import (
17-
PY3, PY2, parameterized, param, parameterized_argument_value_pairs,
16+
parameterized, param, parameterized_argument_value_pairs,
1817
short_repr, detect_runner, parameterized_class, SkipTest,
1918
)
2019

@@ -35,7 +34,7 @@ def assert_raises_regexp_decorator(expected_exception, expected_regexp):
3534
def func_decorator(func):
3635
@wraps(func)
3736
def wrapper(self, *args, **kwargs):
38-
with self.assertRaisesRegexp(expected_exception, expected_regexp):
37+
with self.assertRaisesRegex(expected_exception, expected_regexp):
3938
func(self, *args, **kwargs)
4039

4140
return wrapper
@@ -51,9 +50,6 @@ def wrapper(self, *args, **kwargs):
5150
SKIP_FLAGS = {
5251
"generator": UNITTEST,
5352
"standalone": UNITTEST,
54-
# nose2 doesn't run tests on old-style classes under Py2, so don't expect
55-
# these tests to run under nose2.
56-
"py2nose2": (PY2 and NOSE2),
5753
"pytest": PYTEST,
5854
}
5955

@@ -329,7 +325,6 @@ def test_mock_patch_multiple_standalone(param, umask, getpid):
329325
)
330326

331327

332-
333328
class TestParamerizedOnTestCase(TestCase):
334329
expect([
335330
"test_on_TestCase(42, bar=None)",
@@ -374,7 +369,6 @@ def _assert_docstring(self, expected_docstring, rstrip=False):
374369
stack = inspect.stack()
375370
f_locals = stack[3][0].f_locals
376371
test_method = (
377-
f_locals.get("testMethod") or # Py27
378372
f_locals.get("function") or # Py33
379373
f_locals.get("method") or # Py38
380374
f_locals.get("testfunction") or # Py382
@@ -488,33 +482,6 @@ def tearDownModule():
488482
missing = sorted(list(missing_tests))
489483
assert_equal(missing, [])
490484

491-
def test_old_style_classes():
492-
if PY3:
493-
raise SkipTest("Py3 doesn't have old-style classes")
494-
class OldStyleClass:
495-
@parameterized(["foo"])
496-
def parameterized_method(self, param):
497-
pass
498-
try:
499-
list(OldStyleClass().parameterized_method())
500-
except TypeError as e:
501-
assert_contains(str(e), "new-style")
502-
assert_contains(str(e), "parameterized.expand")
503-
assert_contains(str(e), "OldStyleClass")
504-
else:
505-
raise AssertionError("expected TypeError not raised by old-style class")
506-
507-
508-
class TestOldStyleClass:
509-
expect("py2nose2 generator", [
510-
"test_on_old_style_class('foo')",
511-
"test_on_old_style_class('bar')",
512-
])
513-
514-
@parameterized.expand(["foo", "bar"])
515-
def test_old_style_classes(self, param):
516-
missing_tests.remove("test_on_old_style_class(%r)" %(param, ))
517-
518485

519486
@parameterized([
520487
("", param(), []),

0 commit comments

Comments
 (0)