diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 6c9e25c29..e081f9fcd 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -71,7 +71,7 @@ The official tag is `python-attrs` and helping out in support frees us up to imp - To run the test suite, all you need is a recent [*tox*]. It will ensure the test suite runs with all dependencies against all Python versions just as it will in our [CI]. - If you lack some Python versions, you can can always limit the environments like `tox -e py27,py38`, or make it a non-failure using `tox --skip-missing-interpreters`. + If you lack some Python versions, you can can always limit the environments like `tox -e py38,py39`, or make it a non-failure using `tox --skip-missing-interpreters`. In that case you should look into [*asdf*](https://asdf-vm.com) or [*pyenv*](https://github.com/pyenv/pyenv), which make it very easy to install many different Python versions in parallel. - Write [good test docstrings](https://jml.io/pages/test-docstrings.html). diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a68af6b00..f62e0a605 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,7 +23,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "pypy-2.7", "pypy-3.7", "pypy-3.8"] + python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "pypy-3.7", "pypy-3.8"] steps: - uses: actions/checkout@v3 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b5c650904..688bdf0c4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,12 +8,20 @@ repos: hooks: - id: black + - repo: https://github.com/asottile/pyupgrade + rev: v2.31.1 + hooks: + - id: pyupgrade + args: [--py3-plus, --keep-percent-format] + exclude: "tests/test_slots.py" + - repo: https://github.com/PyCQA/isort rev: 5.10.1 hooks: - id: isort additional_dependencies: [toml] files: \.py$ + language_version: python3.10 # needed for match - repo: https://github.com/PyCQA/flake8 rev: 4.0.1 diff --git a/README.rst b/README.rst index c2e5cf309..cfb7a92fb 100644 --- a/README.rst +++ b/README.rst @@ -117,7 +117,8 @@ Project Information its documentation lives at `Read the Docs `_, the code on `GitHub `_, and the latest release on `PyPI `_. -It’s rigorously tested on Python 2.7, 3.5+, and PyPy. +It’s rigorously tested on Python 3.5+ and PyPy. +The last version with Python 2.7 support is `21.4.0 `_. We collect information on **third-party extensions** in our `wiki `_. Feel free to browse and add your own! diff --git a/changelog.d/936.breaking.rst b/changelog.d/936.breaking.rst new file mode 100644 index 000000000..1a30d126f --- /dev/null +++ b/changelog.d/936.breaking.rst @@ -0,0 +1,6 @@ +Python 2.7 is not supported anymore. + +Dealing with Python 2.7 tooling has become too difficult for a volunteer-run project. + +We have supported Python 2 more than 2 years after it was officially discontinued and feel that we have paid our dues. +All version up to 21.4.0 from December 2021 remain fully functional, of course. diff --git a/conftest.py b/conftest.py index 0d539a115..33cc6a6cb 100644 --- a/conftest.py +++ b/conftest.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function from hypothesis import HealthCheck, settings diff --git a/docs/index.rst b/docs/index.rst index ff65a6738..de82a2d55 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -89,7 +89,6 @@ Full Table of Contents :maxdepth: 1 license - python-2 changelog diff --git a/docs/python-2.rst b/docs/python-2.rst deleted file mode 100644 index 863c6b034..000000000 --- a/docs/python-2.rst +++ /dev/null @@ -1,25 +0,0 @@ -Python 2 Statement -================== - -While ``attrs`` has always been a Python 3-first package, we the maintainers are aware that Python 2 has not magically disappeared in 2020. -We are also aware that ``attrs`` is an important building block in many people's systems and livelihoods. - -As such, we do **not** have any immediate plans to drop Python 2 support in ``attrs``. -We intend to support is as long as it will be technically feasible for us. - -Feasibility in this case means: - -1. Possibility to run the tests on our development computers, -2. and **free** CI options. - -This can mean that we will have to run our tests on PyPy, whose maintainers have unequivocally declared that they do not intend to stop the development and maintenance of their Python 2-compatible line at all. -And this can mean that at some point, a sponsor will have to step up and pay for bespoke CI setups. - -**However**: there is no promise of new features coming to ``attrs`` running under Python 2. -It is up to our discretion alone, to decide whether the introduced complexity or awkwardness are worth it, or whether we choose to make a feature available on modern platforms only. - - -Summary -------- - -We will do our best to support existing users, but nobody is entitled to the latest and greatest features on a platform that is officially end of life. diff --git a/docs/why.rst b/docs/why.rst index 8489959f1..db6282da6 100644 --- a/docs/why.rst +++ b/docs/why.rst @@ -20,7 +20,7 @@ Whether they're relevant to *you* depends on your circumstances: There is a long list of features that were sacrificed for the sake of simplicity and while the most obvious ones are validators, converters, :ref:`equality customization `, or :doc:`extensibility ` in general, it permeates throughout all APIs. On the other hand, Data Classes currently do not offer any significant feature that ``attrs`` doesn't already have. -- ``attrs`` supports all mainstream Python versions, including CPython 2.7 and PyPy. +- ``attrs`` supports all mainstream Python versions including PyPy. - ``attrs`` doesn't force type annotations on you if you don't like them. - But since it **also** supports typing, it's the best way to embrace type hints *gradually*, too. - While Data Classes are implementing features from ``attrs`` every now and then, their presence is dependent on the Python version, not the package version. diff --git a/setup.py b/setup.py index 0b8caffb6..59ebc6080 100644 --- a/setup.py +++ b/setup.py @@ -32,8 +32,6 @@ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", @@ -56,7 +54,6 @@ "hypothesis", "pympler", "pytest>=4.3.0", # 4.3.0 dropped last use of `convert` - "six", ], } if ( @@ -143,7 +140,7 @@ def find_meta(meta): long_description_content_type="text/x-rst", packages=PACKAGES, package_dir={"": "src"}, - python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", + python_requires=">=3.5", zip_safe=False, classifiers=CLASSIFIERS, install_requires=INSTALL_REQUIRES, diff --git a/src/attr/__init__.py b/src/attr/__init__.py index 65c94cde9..b47591319 100644 --- a/src/attr/__init__.py +++ b/src/attr/__init__.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function import sys diff --git a/src/attr/_cmp.py b/src/attr/_cmp.py index 6cffa4dba..0060222a7 100644 --- a/src/attr/_cmp.py +++ b/src/attr/_cmp.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function import functools diff --git a/src/attr/_compat.py b/src/attr/_compat.py index 7f11733a3..b0d690858 100644 --- a/src/attr/_compat.py +++ b/src/attr/_compat.py @@ -1,15 +1,16 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function +import inspect import platform import sys import threading import types import warnings +from collections.abc import Mapping, Sequence # noqa + -PY2 = sys.version_info[0] == 2 PYPY = platform.python_implementation() == "PyPy" PY36 = sys.version_info[:2] >= (3, 6) HAS_F_STRINGS = PY36 @@ -24,180 +25,76 @@ ordered_dict = OrderedDict -if PY2: - from collections import Mapping, Sequence - - from UserDict import IterableUserDict - - # We 'bundle' isclass instead of using inspect as importing inspect is - # fairly expensive (order of 10-15 ms for a modern machine in 2016) - def isclass(klass): - return isinstance(klass, (type, types.ClassType)) - - def new_class(name, bases, kwds, exec_body): - """ - A minimal stub of types.new_class that we need for make_class. - """ - ns = {} - exec_body(ns) +def just_warn(*args, **kw): + """ + We only warn on Python 3 because we are not aware of any concrete + consequences of not setting the cell on Python 2. + """ + warnings.warn( + "Running interpreter doesn't sufficiently support code object " + "introspection. Some features like bare super() or accessing " + "__class__ will not work with slotted classes.", + RuntimeWarning, + stacklevel=2, + ) - return type(name, bases, ns) - # TYPE is used in exceptions, repr(int) is different on Python 2 and 3. - TYPE = "type" +def isclass(klass): + return isinstance(klass, type) - def iteritems(d): - return d.iteritems() - # Python 2 is bereft of a read-only dict proxy, so we make one! - class ReadOnlyDict(IterableUserDict): - """ - Best-effort read-only dict wrapper. - """ +TYPE = "class" - def __setitem__(self, key, val): - # We gently pretend we're a Python 3 mappingproxy. - raise TypeError( - "'mappingproxy' object does not support item assignment" - ) - def update(self, _): - # We gently pretend we're a Python 3 mappingproxy. - raise AttributeError( - "'mappingproxy' object has no attribute 'update'" - ) +def iteritems(d): + return d.items() - def __delitem__(self, _): - # We gently pretend we're a Python 3 mappingproxy. - raise TypeError( - "'mappingproxy' object does not support item deletion" - ) - def clear(self): - # We gently pretend we're a Python 3 mappingproxy. - raise AttributeError( - "'mappingproxy' object has no attribute 'clear'" - ) +new_class = types.new_class - def pop(self, key, default=None): - # We gently pretend we're a Python 3 mappingproxy. - raise AttributeError( - "'mappingproxy' object has no attribute 'pop'" - ) - def popitem(self): - # We gently pretend we're a Python 3 mappingproxy. - raise AttributeError( - "'mappingproxy' object has no attribute 'popitem'" - ) +def metadata_proxy(d): + return types.MappingProxyType(dict(d)) - def setdefault(self, key, default=None): - # We gently pretend we're a Python 3 mappingproxy. - raise AttributeError( - "'mappingproxy' object has no attribute 'setdefault'" - ) - def __repr__(self): - # Override to be identical to the Python 3 version. - return "mappingproxy(" + repr(self.data) + ")" +class _AnnotationExtractor: + """ + Extract type annotations from a callable, returning None whenever there + is none. + """ - def metadata_proxy(d): - res = ReadOnlyDict() - res.data.update(d) # We blocked update, so we have to do it like this. - return res + __slots__ = ["sig"] - def just_warn(*args, **kw): # pragma: no cover - """ - We only warn on Python 3 because we are not aware of any concrete - consequences of not setting the cell on Python 2. - """ + def __init__(self, callable): + try: + self.sig = inspect.signature(callable) + except (ValueError, TypeError): # inspect failed + self.sig = None - class _AnnotationExtractor: + def get_first_param_type(self): """ - Always return None, allows to keep ``if PY2``s from code. + Return the type annotation of the first argument if it's not empty. """ - - __slots__ = ["sig"] - sig = None - - def __init__(self, callable): - pass - - def get_first_param_type(self): - return None - - def get_return_type(self): + if not self.sig: return None -else: # Python 3 and later. - import inspect - - from collections.abc import Mapping, Sequence # noqa - - def just_warn(*args, **kw): - """ - We only warn on Python 3 because we are not aware of any concrete - consequences of not setting the cell on Python 2. - """ - warnings.warn( - "Running interpreter doesn't sufficiently support code object " - "introspection. Some features like bare super() or accessing " - "__class__ will not work with slotted classes.", - RuntimeWarning, - stacklevel=2, - ) - - def isclass(klass): - return isinstance(klass, type) - - TYPE = "class" - - def iteritems(d): - return d.items() + params = list(self.sig.parameters.values()) + if params and params[0].annotation is not inspect.Parameter.empty: + return params[0].annotation - new_class = types.new_class + return None - def metadata_proxy(d): - return types.MappingProxyType(dict(d)) - - class _AnnotationExtractor: + def get_return_type(self): """ - Extract type annotations from a callable, returning None whenever there - is none. + Return the return type if it's not empty. """ + if ( + self.sig + and self.sig.return_annotation is not inspect.Signature.empty + ): + return self.sig.return_annotation - __slots__ = ["sig"] - - def __init__(self, callable): - try: - self.sig = inspect.signature(callable) - except (ValueError, TypeError): # inspect failed - self.sig = None - - def get_first_param_type(self): - """ - Return the type annotation of the first argument if it's not empty. - """ - if not self.sig: - return None - - params = list(self.sig.parameters.values()) - if params and params[0].annotation is not inspect.Parameter.empty: - return params[0].annotation - - return None - - def get_return_type(self): - """ - Return the return type if it's not empty. - """ - if ( - self.sig - and self.sig.return_annotation is not inspect.Signature.empty - ): - return self.sig.return_annotation - - return None + return None def make_set_closure_cell(): @@ -229,10 +126,7 @@ def force_x_to_be_a_cell(): # pragma: no cover try: # Extract the code object and make sure our assumptions about # the closure behavior are correct. - if PY2: - co = set_first_cellvar_to.func_code - else: - co = set_first_cellvar_to.__code__ + co = set_first_cellvar_to.__code__ if co.co_cellvars != ("x",) or co.co_freevars != (): raise AssertionError # pragma: no cover @@ -247,8 +141,7 @@ def force_x_to_be_a_cell(): # pragma: no cover ) else: args = [co.co_argcount] - if not PY2: - args.append(co.co_kwonlyargcount) + args.append(co.co_kwonlyargcount) args.extend( [ co.co_nlocals, @@ -288,10 +181,7 @@ def func(): return func - if PY2: - cell = make_func_with_cell().func_closure[0] - else: - cell = make_func_with_cell().__closure__[0] + cell = make_func_with_cell().__closure__[0] set_closure_cell(cell, 100) if cell.cell_contents != 100: raise AssertionError # pragma: no cover diff --git a/src/attr/_config.py b/src/attr/_config.py index fc9be29d0..96d420077 100644 --- a/src/attr/_config.py +++ b/src/attr/_config.py @@ -1,7 +1,5 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function - __all__ = ["set_run_validators", "get_run_validators"] diff --git a/src/attr/_funcs.py b/src/attr/_funcs.py index 4c90085a4..69a3cf6d8 100644 --- a/src/attr/_funcs.py +++ b/src/attr/_funcs.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function import copy diff --git a/src/attr/_make.py b/src/attr/_make.py index c1b6f9949..0b974f30b 100644 --- a/src/attr/_make.py +++ b/src/attr/_make.py @@ -1,10 +1,10 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function import copy import linecache import sys +import typing import warnings from operator import itemgetter @@ -14,7 +14,6 @@ from . import _compat, _config, setters from ._compat import ( HAS_F_STRINGS, - PY2, PY310, PYPY, _AnnotationExtractor, @@ -29,15 +28,10 @@ DefaultAlreadySetError, FrozenInstanceError, NotAnAttrsClassError, - PythonTooOldError, UnannotatedAttributeError, ) -if not PY2: - import typing - - # This is used at least twice, so cache it here. _obj_setattr = object.__setattr__ _init_converter_pat = "__attr_converter_%s" @@ -64,7 +58,7 @@ _ng_default_on_setattr = setters.pipe(setters.convert, setters.validate) -class _Nothing(object): +class _Nothing: """ Sentinel class to indicate the lack of a value when ``None`` is ambiguous. @@ -77,7 +71,7 @@ class _Nothing(object): def __new__(cls): if _Nothing._singleton is None: - _Nothing._singleton = super(_Nothing, cls).__new__(cls) + _Nothing._singleton = super().__new__(cls) return _Nothing._singleton def __repr__(self): @@ -86,9 +80,6 @@ def __repr__(self): def __bool__(self): return False - def __len__(self): - return 0 # __bool__ for Python 2 - NOTHING = _Nothing() """ @@ -108,17 +99,8 @@ class _CacheHashWrapper(int): See GH #613 for more details. """ - if PY2: - # For some reason `type(None)` isn't callable in Python 2, but we don't - # actually need a constructor for None objects, we just need any - # available function that returns None. - def __reduce__(self, _none_constructor=getattr, _args=(0, "", None)): - return _none_constructor, _args - - else: - - def __reduce__(self, _none_constructor=type(None), _args=()): - return _none_constructor, _args + def __reduce__(self, _none_constructor=type(None), _args=()): + return _none_constructor, _args def attrib( @@ -647,7 +629,7 @@ def _frozen_delattrs(self, name): raise FrozenInstanceError() -class _ClassBuilder(object): +class _ClassBuilder: """ Iteratively build *one* class. """ @@ -701,7 +683,7 @@ def __init__( self._cls = cls self._cls_dict = dict(cls.__dict__) if slots else {} self._attrs = attrs - self._base_names = set(a.name for a in base_attrs) + self._base_names = {a.name for a in base_attrs} self._base_attr_map = base_map self._attr_names = tuple(a.name for a in attrs) self._slots = slots @@ -879,9 +861,7 @@ def _create_slots_class(self): slot_names.append(_hash_cache_field) cd["__slots__"] = tuple(slot_names) - qualname = getattr(self._cls, "__qualname__", None) - if qualname is not None: - cd["__qualname__"] = qualname + cd["__qualname__"] = self._cls.__qualname__ # Create new class based on old class and our methods. cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd) @@ -1197,8 +1177,6 @@ def _determine_whether_to_implement( whose presence signal that the user has implemented it themselves. Return *default* if no reason for either for or against is found. - - auto_detect must be False on Python 2. """ if flag is True or flag is False: return flag @@ -1495,11 +1473,6 @@ def attrs( .. versionchanged:: 21.1.0 *cmp* undeprecated .. versionadded:: 21.3.0 *match_args* """ - if auto_detect and PY2: - raise PythonTooOldError( - "auto_detect only works on Python 3 and later." - ) - eq_, order_ = _determine_attrs_eq_order(cmp, eq, order, None) hash_ = hash # work around the lack of nonlocal @@ -1507,10 +1480,6 @@ def attrs( on_setattr = setters.pipe(*on_setattr) def wrap(cls): - - if getattr(cls, "__class__", None) is None: - raise TypeError("attrs only works with new-style classes.") - is_frozen = frozen or _has_frozen_base_class(cls) is_exc = auto_exc is True and issubclass(cls, BaseException) has_own_setattr = auto_detect and _has_own_attribute( @@ -1634,34 +1603,19 @@ def wrap(cls): """ -if PY2: - - def _has_frozen_base_class(cls): - """ - Check whether *cls* has a frozen ancestor by looking at its - __setattr__. - """ - return ( - getattr(cls.__setattr__, "__module__", None) - == _frozen_setattrs.__module__ - and cls.__setattr__.__name__ == _frozen_setattrs.__name__ - ) - -else: - - def _has_frozen_base_class(cls): - """ - Check whether *cls* has a frozen ancestor by looking at its - __setattr__. - """ - return cls.__setattr__ == _frozen_setattrs +def _has_frozen_base_class(cls): + """ + Check whether *cls* has a frozen ancestor by looking at its + __setattr__. + """ + return cls.__setattr__ is _frozen_setattrs def _generate_unique_filename(cls, func_name): """ Create a "filename" suitable for a function being generated. """ - unique_filename = "".format( + unique_filename = "".format( func_name, cls.__module__, getattr(cls, "__qualname__", cls.__name__), @@ -1687,8 +1641,7 @@ def _make_hash(cls, attrs, frozen, cache_hash): if not cache_hash: hash_def += "):" else: - if not PY2: - hash_def += ", *" + hash_def += ", *" hash_def += ( ", _cache_wrapper=" @@ -1987,15 +1940,7 @@ def __repr__(self): return "..." real_cls = self.__class__ if ns is None: - qualname = getattr(real_cls, "__qualname__", None) - if qualname is not None: # pragma: no cover - # This case only happens on Python 3.5 and 3.6. We exclude - # it from coverage, because we don't want to slow down our - # test suite by running them under coverage too for this - # one line. - class_name = qualname.rsplit(">.", 1)[-1] - else: - class_name = real_cls.__name__ + class_name = real_cls.__qualname__.rsplit(">.", 1)[-1] else: class_name = ns + "." + real_cls.__name__ @@ -2086,7 +2031,7 @@ def fields_dict(cls): raise NotAnAttrsClassError( "{cls!r} is not an attrs-decorated class.".format(cls=cls) ) - return ordered_dict(((a.name, a) for a in attrs)) + return ordered_dict((a.name, a) for a in attrs) def validate(inst): @@ -2236,63 +2181,6 @@ def _assign_with_converter(attr_name, value_var, has_on_setattr): ) -if PY2: - - def _unpack_kw_only_py2(attr_name, default=None): - """ - Unpack *attr_name* from _kw_only dict. - """ - if default is not None: - arg_default = ", %s" % default - else: - arg_default = "" - return "%s = _kw_only.pop('%s'%s)" % ( - attr_name, - attr_name, - arg_default, - ) - - def _unpack_kw_only_lines_py2(kw_only_args): - """ - Unpack all *kw_only_args* from _kw_only dict and handle errors. - - Given a list of strings "{attr_name}" and "{attr_name}={default}" - generates list of lines of code that pop attrs from _kw_only dict and - raise TypeError similar to builtin if required attr is missing or - extra key is passed. - - >>> print("\n".join(_unpack_kw_only_lines_py2(["a", "b=42"]))) - try: - a = _kw_only.pop('a') - b = _kw_only.pop('b', 42) - except KeyError as _key_error: - raise TypeError( - ... - if _kw_only: - raise TypeError( - ... - """ - lines = ["try:"] - lines.extend( - " " + _unpack_kw_only_py2(*arg.split("=")) - for arg in kw_only_args - ) - lines += """\ -except KeyError as _key_error: - raise TypeError( - '__init__() missing required keyword-only argument: %s' % _key_error - ) -if _kw_only: - raise TypeError( - '__init__() got an unexpected keyword argument %r' - % next(iter(_kw_only)) - ) -""".split( - "\n" - ) - return lines - - def _attrs_to_init_script( attrs, frozen, @@ -2548,15 +2436,10 @@ def fmt_setter_with_converter( args = ", ".join(args) if kw_only_args: - if PY2: - lines = _unpack_kw_only_lines_py2(kw_only_args) + lines - - args += "%s**_kw_only" % (", " if args else "",) # leading comma - else: - args += "%s*, %s" % ( - ", " if args else "", # leading comma - ", ".join(kw_only_args), # kw_only args - ) + args += "%s*, %s" % ( + ", " if args else "", # leading comma + ", ".join(kw_only_args), # kw_only args + ) return ( """\ def {init_name}(self, {args}): @@ -2571,7 +2454,7 @@ def {init_name}(self, {args}): ) -class Attribute(object): +class Attribute: """ *Read-only* representation of an attribute. @@ -2793,7 +2676,7 @@ def _setattrs(self, name_values_pairs): ) -class _CountingAttr(object): +class _CountingAttr: """ Intermediate representation of attributes that uses a counter to preserve the order in which the attributes have been defined. @@ -2936,7 +2819,7 @@ def default(self, meth): _CountingAttr = _add_eq(_add_repr(_CountingAttr)) -class Factory(object): +class Factory: """ Stores a factory callable. @@ -3022,7 +2905,7 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments): if isinstance(attrs, dict): cls_dict = attrs elif isinstance(attrs, (list, tuple)): - cls_dict = dict((a, attrib()) for a in attrs) + cls_dict = {a: attrib() for a in attrs} else: raise TypeError("attrs argument must be a dict or a list.") @@ -3071,7 +2954,7 @@ def make_class(name, attrs, bases=(object,), **attributes_arguments): @attrs(slots=True, hash=True) -class _AndValidator(object): +class _AndValidator: """ Compose many validators to a single one. """ @@ -3126,10 +3009,9 @@ def pipe_converter(val): return val if not converters: - if not PY2: - # If the converter list is empty, pipe_converter is the identity. - A = typing.TypeVar("A") - pipe_converter.__annotations__ = {"val": A, "return": A} + # If the converter list is empty, pipe_converter is the identity. + A = typing.TypeVar("A") + pipe_converter.__annotations__ = {"val": A, "return": A} else: # Get parameter type from first converter. t = _AnnotationExtractor(converters[0]).get_first_param_type() diff --git a/src/attr/_version_info.py b/src/attr/_version_info.py index cdaeec37a..51a1312f9 100644 --- a/src/attr/_version_info.py +++ b/src/attr/_version_info.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function from functools import total_ordering @@ -10,7 +9,7 @@ @total_ordering @attrs(eq=False, order=False, slots=True, frozen=True) -class VersionInfo(object): +class VersionInfo: """ A version object that can be compared to tuple of length 1--4: diff --git a/src/attr/converters.py b/src/attr/converters.py index b59153988..a73626c26 100644 --- a/src/attr/converters.py +++ b/src/attr/converters.py @@ -4,14 +4,11 @@ Commonly useful converters. """ -from __future__ import absolute_import, division, print_function - -from ._compat import PY2, _AnnotationExtractor -from ._make import NOTHING, Factory, pipe +import typing -if not PY2: - import typing +from ._compat import _AnnotationExtractor +from ._make import NOTHING, Factory, pipe __all__ = [ diff --git a/src/attr/exceptions.py b/src/attr/exceptions.py index b2f1edc32..5dc51e0a8 100644 --- a/src/attr/exceptions.py +++ b/src/attr/exceptions.py @@ -1,7 +1,5 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function - class FrozenError(AttributeError): """ diff --git a/src/attr/filters.py b/src/attr/filters.py index a1978a877..e7432e4a8 100644 --- a/src/attr/filters.py +++ b/src/attr/filters.py @@ -4,7 +4,6 @@ Commonly useful filters for `attr.asdict`. """ -from __future__ import absolute_import, division, print_function from ._compat import isclass from ._make import Attribute diff --git a/src/attr/setters.py b/src/attr/setters.py index b9f42a410..12ed6750d 100644 --- a/src/attr/setters.py +++ b/src/attr/setters.py @@ -4,7 +4,6 @@ Commonly used hooks for on_setattr. """ -from __future__ import absolute_import, division, print_function from . import _config from .exceptions import FrozenAttributeError diff --git a/src/attr/validators.py b/src/attr/validators.py index 5f850cc8f..e1c01b4f7 100644 --- a/src/attr/validators.py +++ b/src/attr/validators.py @@ -4,7 +4,6 @@ Commonly useful validators. """ -from __future__ import absolute_import, division, print_function import operator import re @@ -93,7 +92,7 @@ def disabled(): @attrs(repr=False, slots=True, hash=True) -class _InstanceOfValidator(object): +class _InstanceOfValidator: type = attrib() def __call__(self, inst, attr, value): @@ -137,7 +136,7 @@ def instance_of(type): @attrs(repr=False, frozen=True, slots=True) -class _MatchesReValidator(object): +class _MatchesReValidator: pattern = attrib() match_func = attrib() @@ -179,8 +178,7 @@ def matches_re(regex, flags=0, func=None): .. versionadded:: 19.2.0 .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern. """ - fullmatch = getattr(re, "fullmatch", None) - valid_funcs = (fullmatch, None, re.search, re.match) + valid_funcs = (re.fullmatch, None, re.search, re.match) if func not in valid_funcs: raise ValueError( "'func' must be one of {}.".format( @@ -206,19 +204,14 @@ def matches_re(regex, flags=0, func=None): match_func = pattern.match elif func is re.search: match_func = pattern.search - elif fullmatch: + else: match_func = pattern.fullmatch - else: # Python 2 fullmatch emulation (https://bugs.python.org/issue16203) - pattern = re.compile( - r"(?:{})\Z".format(pattern.pattern), pattern.flags - ) - match_func = pattern.match return _MatchesReValidator(pattern, match_func) @attrs(repr=False, slots=True, hash=True) -class _ProvidesValidator(object): +class _ProvidesValidator: interface = attrib() def __call__(self, inst, attr, value): @@ -260,7 +253,7 @@ def provides(interface): @attrs(repr=False, slots=True, hash=True) -class _OptionalValidator(object): +class _OptionalValidator: validator = attrib() def __call__(self, inst, attr, value): @@ -294,7 +287,7 @@ def optional(validator): @attrs(repr=False, slots=True, hash=True) -class _InValidator(object): +class _InValidator: options = attrib() def __call__(self, inst, attr, value): @@ -335,7 +328,7 @@ def in_(options): @attrs(repr=False, slots=False, hash=True) -class _IsCallableValidator(object): +class _IsCallableValidator: def __call__(self, inst, attr, value): """ We use a callable class to be able to change the ``__repr__``. @@ -372,7 +365,7 @@ def is_callable(): @attrs(repr=False, slots=True, hash=True) -class _DeepIterable(object): +class _DeepIterable: member_validator = attrib(validator=is_callable()) iterable_validator = attrib( default=None, validator=optional(is_callable()) @@ -421,7 +414,7 @@ def deep_iterable(member_validator, iterable_validator=None): @attrs(repr=False, slots=True, hash=True) -class _DeepMapping(object): +class _DeepMapping: key_validator = attrib(validator=is_callable()) value_validator = attrib(validator=is_callable()) mapping_validator = attrib(default=None, validator=optional(is_callable())) @@ -460,7 +453,7 @@ def deep_mapping(key_validator, value_validator, mapping_validator=None): @attrs(repr=False, frozen=True, slots=True) -class _NumberValidator(object): +class _NumberValidator: bound = attrib() compare_op = attrib() compare_func = attrib() @@ -534,7 +527,7 @@ def gt(val): @attrs(repr=False, frozen=True, slots=True) -class _MaxLengthValidator(object): +class _MaxLengthValidator: max_length = attrib() def __call__(self, inst, attr, value): @@ -565,7 +558,7 @@ def max_len(length): @attrs(repr=False, frozen=True, slots=True) -class _MinLengthValidator(object): +class _MinLengthValidator: min_length = attrib() def __call__(self, inst, attr, value): diff --git a/tests/attr_import_star.py b/tests/attr_import_star.py index eaec321ba..636545268 100644 --- a/tests/attr_import_star.py +++ b/tests/attr_import_star.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import from attr import * # noqa: F401,F403 diff --git a/tests/strategies.py b/tests/strategies.py index 99f9f4853..f630b228a 100644 --- a/tests/strategies.py +++ b/tests/strategies.py @@ -28,8 +28,7 @@ def gen_attr_names(): Some short strings (such as 'as') are keywords, so we skip them. """ lc = string.ascii_lowercase - for c in lc: - yield c + yield from lc for outer in lc: for inner in lc: res = outer + inner diff --git a/tests/test_3rd_party.py b/tests/test_3rd_party.py index 8866d7f6e..0707b2cd2 100644 --- a/tests/test_3rd_party.py +++ b/tests/test_3rd_party.py @@ -14,7 +14,7 @@ cloudpickle = pytest.importorskip("cloudpickle") -class TestCloudpickleCompat(object): +class TestCloudpickleCompat: """ Tests for compatibility with ``cloudpickle``. """ diff --git a/tests/test_annotations.py b/tests/test_annotations.py index a201ebf7f..49c9b0d7b 100644 --- a/tests/test_annotations.py +++ b/tests/test_annotations.py @@ -113,7 +113,7 @@ class C: i = C(42) assert "C(a=42, x=[], y=2, z=3, foo=None)" == repr(i) - attr_names = set(a.name for a in C.__attrs_attrs__) + attr_names = {a.name for a in C.__attrs_attrs__} assert "a" in attr_names # just double check that the set works assert "cls_var" not in attr_names diff --git a/tests/test_cmp.py b/tests/test_cmp.py index ec2c68748..b9383871e 100644 --- a/tests/test_cmp.py +++ b/tests/test_cmp.py @@ -4,12 +4,10 @@ Tests for methods from `attrib._cmp`. """ -from __future__ import absolute_import, division, print_function import pytest from attr._cmp import cmp_using -from attr._compat import PY2 # Test parameters. @@ -57,7 +55,7 @@ cmp_ids = eq_ids + order_ids -class TestEqOrder(object): +class TestEqOrder: """ Tests for eq and order related methods. """ @@ -92,7 +90,6 @@ def test_equal_different_type(self, cls, requires_same_type): ######### # lt ######### - @pytest.mark.skipif(PY2, reason="PY2 does not raise TypeError") @pytest.mark.parametrize("cls, requires_same_type", eq_data, ids=eq_ids) def test_lt_unorderable(self, cls, requires_same_type): """ @@ -131,9 +128,8 @@ def test_lt_different_type(self, cls, requires_same_type): if requires_same_type: # Unlike __eq__, NotImplemented will cause an exception to be # raised from __lt__. - if not PY2: - with pytest.raises(TypeError): - cls(1) < cls(2.0) + with pytest.raises(TypeError): + cls(1) < cls(2.0) else: assert cls(1) < cls(2.0) assert not (cls(2) < cls(1.0)) @@ -141,7 +137,6 @@ def test_lt_different_type(self, cls, requires_same_type): ######### # le ######### - @pytest.mark.skipif(PY2, reason="PY2 does not raise TypeError") @pytest.mark.parametrize("cls, requires_same_type", eq_data, ids=eq_ids) def test_le_unorderable(self, cls, requires_same_type): """ @@ -182,9 +177,8 @@ def test_le_different_type(self, cls, requires_same_type): if requires_same_type: # Unlike __eq__, NotImplemented will cause an exception to be # raised from __le__. - if not PY2: - with pytest.raises(TypeError): - cls(1) <= cls(2.0) + with pytest.raises(TypeError): + cls(1) <= cls(2.0) else: assert cls(1) <= cls(2.0) assert cls(1) <= cls(1.0) @@ -193,7 +187,6 @@ def test_le_different_type(self, cls, requires_same_type): ######### # gt ######### - @pytest.mark.skipif(PY2, reason="PY2 does not raise TypeError") @pytest.mark.parametrize("cls, requires_same_type", eq_data, ids=eq_ids) def test_gt_unorderable(self, cls, requires_same_type): """ @@ -232,9 +225,8 @@ def test_gt_different_type(self, cls, requires_same_type): if requires_same_type: # Unlike __eq__, NotImplemented will cause an exception to be # raised from __gt__. - if not PY2: - with pytest.raises(TypeError): - cls(2) > cls(1.0) + with pytest.raises(TypeError): + cls(2) > cls(1.0) else: assert cls(2) > cls(1.0) assert not (cls(1) > cls(2.0)) @@ -242,7 +234,6 @@ def test_gt_different_type(self, cls, requires_same_type): ######### # ge ######### - @pytest.mark.skipif(PY2, reason="PY2 does not raise TypeError") @pytest.mark.parametrize("cls, requires_same_type", eq_data, ids=eq_ids) def test_ge_unorderable(self, cls, requires_same_type): """ @@ -283,16 +274,15 @@ def test_ge_different_type(self, cls, requires_same_type): if requires_same_type: # Unlike __eq__, NotImplemented will cause an exception to be # raised from __ge__. - if not PY2: - with pytest.raises(TypeError): - cls(2) >= cls(1.0) + with pytest.raises(TypeError): + cls(2) >= cls(1.0) else: assert cls(2) >= cls(2.0) assert cls(2) >= cls(1.0) assert not (cls(1) >= cls(2.0)) -class TestDundersUnnamedClass(object): +class TestDundersUnnamedClass: """ Tests for dunder attributes of unnamed classes. """ @@ -304,8 +294,7 @@ def test_class(self): Class name and qualified name should be well behaved. """ assert self.cls.__name__ == "Comparable" - if not PY2: - assert self.cls.__qualname__ == "Comparable" + assert self.cls.__qualname__ == "Comparable" def test_eq(self): """ @@ -327,7 +316,7 @@ def test_ne(self): assert method.__name__ == "__ne__" -class TestTotalOrderingException(object): +class TestTotalOrderingException: """ Test for exceptions related to total ordering. """ @@ -345,7 +334,7 @@ def test_eq_must_specified(self): ) -class TestNotImplementedIsPropagated(object): +class TestNotImplementedIsPropagated: """ Test related to functions that return NotImplemented. """ @@ -361,7 +350,7 @@ def test_not_implemented_is_propagated(self): assert C(1) != C(1) -class TestDundersPartialOrdering(object): +class TestDundersPartialOrdering: """ Tests for dunder attributes of classes with partial ordering. """ @@ -373,8 +362,7 @@ def test_class(self): Class name and qualified name should be well behaved. """ assert self.cls.__name__ == "PartialOrderCSameType" - if not PY2: - assert self.cls.__qualname__ == "PartialOrderCSameType" + assert self.cls.__qualname__ == "PartialOrderCSameType" def test_eq(self): """ @@ -408,12 +396,9 @@ def test_le(self): __le__ docstring and qualified name should be well behaved. """ method = self.cls.__le__ - if PY2: - assert method.__doc__ == "x.__le__(y) <==> x<=y" - else: - assert method.__doc__.strip().startswith( - "Return a <= b. Computed by @total_ordering from" - ) + assert method.__doc__.strip().startswith( + "Return a <= b. Computed by @total_ordering from" + ) assert method.__name__ == "__le__" def test_gt(self): @@ -421,12 +406,9 @@ def test_gt(self): __gt__ docstring and qualified name should be well behaved. """ method = self.cls.__gt__ - if PY2: - assert method.__doc__ == "x.__gt__(y) <==> x>y" - else: - assert method.__doc__.strip().startswith( - "Return a > b. Computed by @total_ordering from" - ) + assert method.__doc__.strip().startswith( + "Return a > b. Computed by @total_ordering from" + ) assert method.__name__ == "__gt__" def test_ge(self): @@ -434,16 +416,13 @@ def test_ge(self): __ge__ docstring and qualified name should be well behaved. """ method = self.cls.__ge__ - if PY2: - assert method.__doc__ == "x.__ge__(y) <==> x>=y" - else: - assert method.__doc__.strip().startswith( - "Return a >= b. Computed by @total_ordering from" - ) + assert method.__doc__.strip().startswith( + "Return a >= b. Computed by @total_ordering from" + ) assert method.__name__ == "__ge__" -class TestDundersFullOrdering(object): +class TestDundersFullOrdering: """ Tests for dunder attributes of classes with full ordering. """ @@ -455,8 +434,7 @@ def test_class(self): Class name and qualified name should be well behaved. """ assert self.cls.__name__ == "FullOrderCSameType" - if not PY2: - assert self.cls.__qualname__ == "FullOrderCSameType" + assert self.cls.__qualname__ == "FullOrderCSameType" def test_eq(self): """ diff --git a/tests/test_config.py b/tests/test_config.py index bbf675640..6c78fd295 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -4,14 +4,13 @@ Tests for `attr._config`. """ -from __future__ import absolute_import, division, print_function import pytest from attr import _config -class TestConfig(object): +class TestConfig: def test_default(self): """ Run validators by default. diff --git a/tests/test_converters.py b/tests/test_converters.py index 3213e3ab7..7607e5550 100644 --- a/tests/test_converters.py +++ b/tests/test_converters.py @@ -4,7 +4,6 @@ Tests for `attr.converters`. """ -from __future__ import absolute_import import pytest @@ -14,7 +13,7 @@ from attr.converters import default_if_none, optional, pipe, to_bool -class TestOptional(object): +class TestOptional: """ Tests for `optional`. """ @@ -45,7 +44,7 @@ def test_fail(self): c("not_an_int") -class TestDefaultIfNone(object): +class TestDefaultIfNone: def test_missing_default(self): """ Raises TypeError if neither default nor factory have been passed. @@ -101,7 +100,7 @@ def test_none_factory(self): assert [] == c(None) -class TestPipe(object): +class TestPipe: def test_success(self): """ Succeeds if all wrapped converters succeed. @@ -130,7 +129,7 @@ def test_sugar(self): """ @attr.s - class C(object): + class C: a1 = attrib(default="True", converter=pipe(str, to_bool, bool)) a2 = attrib(default=True, converter=[str, to_bool, bool]) @@ -146,7 +145,7 @@ def test_empty(self): assert o is pipe()(o) -class TestToBool(object): +class TestToBool: def test_unhashable(self): """ Fails if value is unhashable. diff --git a/tests/test_dunders.py b/tests/test_dunders.py index 186762eb0..03644f8b2 100644 --- a/tests/test_dunders.py +++ b/tests/test_dunders.py @@ -4,7 +4,6 @@ Tests for dunder methods from `attrib._make`. """ -from __future__ import absolute_import, division, print_function import copy import pickle @@ -40,25 +39,25 @@ @attr.s(eq=True) -class EqCallableC(object): +class EqCallableC: a = attr.ib(eq=str.lower, order=False) b = attr.ib(eq=True) @attr.s(eq=True, slots=True) -class EqCallableCSlots(object): +class EqCallableCSlots: a = attr.ib(eq=str.lower, order=False) b = attr.ib(eq=True) @attr.s(order=True) -class OrderCallableC(object): +class OrderCallableC: a = attr.ib(eq=True, order=str.lower) b = attr.ib(order=True) @attr.s(order=True, slots=True) -class OrderCallableCSlots(object): +class OrderCallableCSlots: a = attr.ib(eq=True, order=str.lower) b = attr.ib(order=True) @@ -102,14 +101,14 @@ def _add_init(cls, frozen): return cls -class InitC(object): +class InitC: __attrs_attrs__ = [simple_attr("a"), simple_attr("b")] InitC = _add_init(InitC, False) -class TestEqOrder(object): +class TestEqOrder: """ Tests for eq and order related methods. """ @@ -168,7 +167,7 @@ def test_unequal_different_class(self, cls): match. """ - class NotEqC(object): + class NotEqC: a = 1 b = 2 @@ -316,7 +315,7 @@ def test_ge_unordable(self, cls): assert NotImplemented == (cls(1, 2).__ge__(42)) -class TestAddRepr(object): +class TestAddRepr: """ Tests for `_add_repr`. """ @@ -349,7 +348,7 @@ def custom_repr(value): return "foo:" + str(value) @attr.s - class C(object): + class C: a = attr.ib(repr=custom_repr) assert "C(a=foo:1)" == repr(C(1)) @@ -361,7 +360,7 @@ def test_infinite_recursion(self): """ @attr.s - class Cycle(object): + class Cycle: value = attr.ib(default=7) cycle = attr.ib(default=None) @@ -376,7 +375,7 @@ def test_infinite_recursion_long_cycle(self): """ @attr.s - class LongCycle(object): + class LongCycle: value = attr.ib(default=14) cycle = attr.ib(default=None) @@ -391,7 +390,7 @@ def test_underscores(self): repr does not strip underscores. """ - class C(object): + class C: __attrs_attrs__ = [simple_attr("_x")] C = _add_repr(C) @@ -440,21 +439,21 @@ def test_str_no_repr(self): # these are for use in TestAddHash.test_cache_hash_serialization # they need to be out here so they can be un-pickled @attr.attrs(hash=True, cache_hash=False) -class HashCacheSerializationTestUncached(object): +class HashCacheSerializationTestUncached: foo_value = attr.ib() @attr.attrs(hash=True, cache_hash=True) -class HashCacheSerializationTestCached(object): +class HashCacheSerializationTestCached: foo_value = attr.ib() @attr.attrs(slots=True, hash=True, cache_hash=True) -class HashCacheSerializationTestCachedSlots(object): +class HashCacheSerializationTestCachedSlots: foo_value = attr.ib() -class IncrementingHasher(object): +class IncrementingHasher: def __init__(self): self.hash_value = 100 @@ -464,7 +463,7 @@ def __hash__(self): return rv -class TestAddHash(object): +class TestAddHash: """ Tests for `_add_hash`. """ @@ -661,7 +660,7 @@ def test_copy_hash_cleared(self, cache_hash, frozen, slots): kwargs["hash"] = True @attr.s(**kwargs) - class C(object): + class C: x = attr.ib() a = C(IncrementingHasher()) @@ -711,7 +710,7 @@ def test_copy_two_arg_reduce(self, frozen): """ @attr.s(frozen=frozen, cache_hash=True, hash=True) - class C(object): + class C: x = attr.ib() def __getstate__(self): @@ -729,7 +728,7 @@ def _roundtrip_pickle(self, obj): return pickle.loads(pickle_str) -class TestAddInit(object): +class TestAddInit: """ Tests for `_add_init`. """ @@ -802,7 +801,7 @@ def test_default(self): If a default value is present, it's used as fallback. """ - class C(object): + class C: __attrs_attrs__ = [ simple_attr(name="a", default=2), simple_attr(name="b", default="hallo"), @@ -820,10 +819,10 @@ def test_factory(self): If a default factory is present, it's used as fallback. """ - class D(object): + class D: pass - class C(object): + class C: __attrs_attrs__ = [ simple_attr(name="a", default=Factory(list)), simple_attr(name="b", default=Factory(D)), @@ -898,7 +897,7 @@ def test_underscores(self): underscores. """ - class C(object): + class C: __attrs_attrs__ = [simple_attr("_private")] C = _add_init(C, False) @@ -906,7 +905,7 @@ class C(object): assert 42 == i._private -class TestNothing(object): +class TestNothing: """ Tests for `_Nothing`. """ @@ -942,7 +941,7 @@ def test_false(self): @attr.s(hash=True, order=True) -class C(object): +class C: pass @@ -951,7 +950,7 @@ class C(object): @attr.s(hash=True, order=True) -class C(object): +class C: pass @@ -959,13 +958,13 @@ class C(object): @attr.s(hash=True, order=True) -class C(object): +class C: """A different class, to generate different methods.""" a = attr.ib() -class TestFilenames(object): +class TestFilenames: def test_filenames(self): """ The created dunder methods have a "consistent" filename. diff --git a/tests/test_filters.py b/tests/test_filters.py index d1ec24dc6..6945bd2d5 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -4,7 +4,6 @@ Tests for `attr.filters`. """ -from __future__ import absolute_import, division, print_function import pytest @@ -15,12 +14,12 @@ @attr.s -class C(object): +class C: a = attr.ib() b = attr.ib() -class TestSplitWhat(object): +class TestSplitWhat: """ Tests for `_split_what`. """ @@ -35,7 +34,7 @@ def test_splits(self): ) == _split_what((str, fields(C).a, int)) -class TestInclude(object): +class TestInclude: """ Tests for `include`. """ @@ -73,7 +72,7 @@ def test_drop_class(self, incl, value): assert i(fields(C).a, value) is False -class TestExclude(object): +class TestExclude: """ Tests for `exclude`. """ diff --git a/tests/test_funcs.py b/tests/test_funcs.py index 4490ed815..40c548756 100644 --- a/tests/test_funcs.py +++ b/tests/test_funcs.py @@ -4,7 +4,6 @@ Tests for `attr._funcs`. """ -from __future__ import absolute_import, division, print_function from collections import OrderedDict @@ -35,14 +34,14 @@ def _C(): import attr @attr.s - class C(object): + class C: x = attr.ib() y = attr.ib() return C -class TestAsDict(object): +class TestAsDict: """ Tests for `asdict`. """ @@ -207,7 +206,7 @@ def test_retain_keys_are_tuples(self): """ @attr.s - class A(object): + class A: a = attr.ib() instance = A({(1,): 1}) @@ -225,7 +224,7 @@ def test_tuple_keys(self): """ @attr.s - class A(object): + class A: a = attr.ib() instance = A({(1,): 1}) @@ -233,7 +232,7 @@ class A(object): assert {"a": {(1,): 1}} == attr.asdict(instance) -class TestAsTuple(object): +class TestAsTuple: """ Tests for `astuple`. """ @@ -391,7 +390,7 @@ def test_sets_no_retain(self, C, set_type): assert (1, [1, 2, 3]) == d -class TestHas(object): +class TestHas: """ Tests for `has`. """ @@ -408,7 +407,7 @@ def test_positive_empty(self): """ @attr.s - class D(object): + class D: pass assert has(D) @@ -420,7 +419,7 @@ def test_negative(self): assert not has(object) -class TestAssoc(object): +class TestAssoc: """ Tests for `assoc`. """ @@ -432,7 +431,7 @@ def test_empty(self, slots, frozen): """ @attr.s(slots=slots, frozen=frozen) - class C(object): + class C: pass i1 = C() @@ -494,7 +493,7 @@ def test_frozen(self): """ @attr.s(frozen=True) - class C(object): + class C: x = attr.ib() y = attr.ib() @@ -507,7 +506,7 @@ def test_warning(self): """ @attr.s - class C(object): + class C: x = attr.ib() with pytest.warns(DeprecationWarning) as wi: @@ -516,7 +515,7 @@ class C(object): assert __file__ == wi.list[0].filename -class TestEvolve(object): +class TestEvolve: """ Tests for `evolve`. """ @@ -528,7 +527,7 @@ def test_empty(self, slots, frozen): """ @attr.s(slots=slots, frozen=frozen) - class C(object): + class C: pass i1 = C() @@ -593,7 +592,7 @@ def test_validator_failure(self): """ @attr.s - class C(object): + class C: a = attr.ib(validator=instance_of(int)) with pytest.raises(TypeError) as e: @@ -608,7 +607,7 @@ def test_private(self): """ @attr.s - class C(object): + class C: _a = attr.ib() assert evolve(C(1), a=2)._a == 2 @@ -625,7 +624,7 @@ def test_non_init_attrs(self): """ @attr.s - class C(object): + class C: a = attr.ib() b = attr.ib(init=False, default=0) @@ -639,11 +638,11 @@ def test_regression_attrs_classes(self): """ @attr.s - class Cls1(object): + class Cls1: param1 = attr.ib() @attr.s - class Cls2(object): + class Cls2: param2 = attr.ib() obj2a = Cls2(param2="a") @@ -663,11 +662,11 @@ def test_dicts(self): """ @attr.s - class Cls1(object): + class Cls1: param1 = attr.ib() @attr.s - class Cls2(object): + class Cls2: param2 = attr.ib() obj2a = Cls2(param2="a") diff --git a/tests/test_functional.py b/tests/test_functional.py index 6bb989f66..c6a808438 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -4,7 +4,6 @@ End-to-end tests. """ -from __future__ import absolute_import, division, print_function import inspect import pickle @@ -12,14 +11,13 @@ from copy import deepcopy import pytest -import six from hypothesis import assume, given from hypothesis.strategies import booleans import attr -from attr._compat import PY2, PY36, TYPE +from attr._compat import PY36, TYPE from attr._make import NOTHING, Attribute from attr.exceptions import FrozenInstanceError @@ -27,13 +25,13 @@ @attr.s -class C1(object): +class C1: x = attr.ib(validator=attr.validators.instance_of(int)) y = attr.ib() @attr.s(slots=True) -class C1Slots(object): +class C1Slots: x = attr.ib(validator=attr.validators.instance_of(int)) y = attr.ib() @@ -42,19 +40,19 @@ class C1Slots(object): @attr.s() -class C2(object): +class C2: x = attr.ib(default=foo) y = attr.ib(default=attr.Factory(list)) @attr.s(slots=True) -class C2Slots(object): +class C2Slots: x = attr.ib(default=foo) y = attr.ib(default=attr.Factory(list)) @attr.s -class Base(object): +class Base: x = attr.ib() def meth(self): @@ -62,7 +60,7 @@ def meth(self): @attr.s(slots=True) -class BaseSlots(object): +class BaseSlots: x = attr.ib() def meth(self): @@ -80,7 +78,7 @@ class SubSlots(BaseSlots): @attr.s(frozen=True, slots=True) -class Frozen(object): +class Frozen: x = attr.ib() @@ -90,7 +88,7 @@ class SubFrozen(Frozen): @attr.s(frozen=True, slots=False) -class FrozenNoSlots(object): +class FrozenNoSlots: x = attr.ib() @@ -99,21 +97,19 @@ class Meta(type): @attr.s -@six.add_metaclass(Meta) -class WithMeta(object): +class WithMeta(metaclass=Meta): pass @attr.s(slots=True) -@six.add_metaclass(Meta) -class WithMetaSlots(object): +class WithMetaSlots(metaclass=Meta): pass FromMakeClass = attr.make_class("FromMakeClass", ["x"]) -class TestFunctional(object): +class TestFunctional: """ Functional tests. """ @@ -181,7 +177,7 @@ def test_renaming(self, slots): """ @attr.s(slots=slots) - class C3(object): + class C3: _x = attr.ib() assert "C3(_x=1)" == repr(C3(x=1)) @@ -351,7 +347,7 @@ def test_default_decorator(self): """ @attr.s - class C(object): + class C: x = attr.ib(default=1) y = attr.ib() @@ -380,7 +376,7 @@ def test_dict_patch_class(self): dict-classes are never replaced. """ - class C(object): + class C: x = attr.ib() C_new = attr.s(C) @@ -395,7 +391,7 @@ def test_hash_by_id(self): """ @attr.s(hash=False) - class HashByIDBackwardCompat(object): + class HashByIDBackwardCompat: x = attr.ib() assert hash(HashByIDBackwardCompat(1)) != hash( @@ -403,13 +399,13 @@ class HashByIDBackwardCompat(object): ) @attr.s(hash=False, eq=False) - class HashByID(object): + class HashByID: x = attr.ib() assert hash(HashByID(1)) != hash(HashByID(1)) @attr.s(hash=True) - class HashByValues(object): + class HashByValues: x = attr.ib() assert hash(HashByValues(1)) == hash(HashByValues(1)) @@ -420,11 +416,11 @@ def test_handles_different_defaults(self): """ @attr.s - class Unhashable(object): + class Unhashable: pass @attr.s - class C(object): + class C: x = attr.ib(default=Unhashable()) @attr.s @@ -438,7 +434,7 @@ def test_hash_false_eq_false(self, slots): """ @attr.s(hash=False, eq=False, slots=slots) - class C(object): + class C: pass assert hash(C()) != hash(C()) @@ -450,7 +446,7 @@ def test_eq_false(self, slots): """ @attr.s(eq=False, slots=slots) - class C(object): + class C: pass # Ensure both objects live long enough such that their ids/hashes @@ -468,7 +464,7 @@ def test_overwrite_base(self): """ @attr.s - class C(object): + class C: c = attr.ib(default=100) x = attr.ib(default=1) b = attr.ib(default=23) @@ -515,7 +511,7 @@ def test_frozen_slots_combo( slots=base_slots, weakref_slot=base_weakref_slot, ) - class Base(object): + class Base: a = attr.ib(converter=int if base_converter else None) @attr.s( @@ -542,7 +538,7 @@ def test_tuple_class_aliasing(self): """ @attr.s - class C(object): + class C: property = attr.ib() itemgetter = attr.ib() x = attr.ib() @@ -633,24 +629,19 @@ def test_eq_only(self, slots, frozen): """ @attr.s(eq=True, order=False, slots=slots, frozen=frozen) - class C(object): + class C: x = attr.ib() - if not PY2: - possible_errors = ( - "unorderable types: C() < C()", - "'<' not supported between instances of 'C' and 'C'", - "unorderable types: C < C", # old PyPy 3 - ) + possible_errors = ( + "unorderable types: C() < C()", + "'<' not supported between instances of 'C' and 'C'", + "unorderable types: C < C", # old PyPy 3 + ) - with pytest.raises(TypeError) as ei: - C(5) < C(6) + with pytest.raises(TypeError) as ei: + C(5) < C(6) - assert ei.value.args[0] in possible_errors - else: - i = C(42) - for m in ("lt", "le", "gt", "ge"): - assert None is getattr(i, "__%s__" % (m,), None) + assert ei.value.args[0] in possible_errors @given(cmp=optional_bool, eq=optional_bool, order=optional_bool) def test_cmp_deprecated_attribute(self, cmp, eq, order): @@ -678,7 +669,7 @@ def test_cmp_deprecated_attribute(self, cmp, eq, order): with pytest.deprecated_call() as dc: @attr.s - class C(object): + class C: x = attr.ib(cmp=cmp, eq=eq, order=order) assert rv == attr.fields(C).x.cmp @@ -702,7 +693,7 @@ def test_no_setattr_if_validate_without_validators(self, slots): """ @attr.s(on_setattr=attr.setters.validate) - class C(object): + class C: x = attr.ib() @attr.s(on_setattr=attr.setters.validate) @@ -724,7 +715,7 @@ def test_no_setattr_if_convert_without_converters(self, slots): """ @attr.s(on_setattr=attr.setters.convert) - class C(object): + class C: x = attr.ib() @attr.s(on_setattr=attr.setters.convert) @@ -748,7 +739,7 @@ def test_no_setattr_with_ng_defaults(self, slots): """ @attr.define - class C(object): + class C: x = attr.ib() src = inspect.getsource(C.__init__) @@ -775,7 +766,7 @@ def test_on_setattr_detect_inherited_validators(self): """ @attr.s(on_setattr=attr.setters.validate) - class C(object): + class C: x = attr.ib(validator=42) @attr.s(on_setattr=attr.setters.validate) diff --git a/tests/test_import.py b/tests/test_import.py index 423124319..9e90a5c11 100644 --- a/tests/test_import.py +++ b/tests/test_import.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: MIT -class TestImportStar(object): +class TestImportStar: def test_from_attr_import_star(self): """ import * from attr diff --git a/tests/test_make.py b/tests/test_make.py index cdca30d08..d4d8640cd 100644 --- a/tests/test_make.py +++ b/tests/test_make.py @@ -4,7 +4,6 @@ Tests for `attr._make`. """ -from __future__ import absolute_import, division, print_function import copy import functools @@ -23,7 +22,7 @@ import attr from attr import _config -from attr._compat import PY2, PY310, ordered_dict +from attr._compat import PY310, ordered_dict from attr._make import ( Attribute, Factory, @@ -41,11 +40,7 @@ make_class, validate, ) -from attr.exceptions import ( - DefaultAlreadySetError, - NotAnAttrsClassError, - PythonTooOldError, -) +from attr.exceptions import DefaultAlreadySetError, NotAnAttrsClassError from .strategies import ( gen_attr_names, @@ -62,7 +57,7 @@ attrs_st = simple_attrs.map(lambda c: Attribute.from_counting_attr("name", c)) -class TestCountingAttr(object): +class TestCountingAttr: """ Tests for `attr`. """ @@ -151,7 +146,7 @@ def f(self): def make_tc(): - class TransformC(object): + class TransformC: z = attr.ib() y = attr.ib() x = attr.ib() @@ -160,7 +155,7 @@ class TransformC(object): return TransformC -class TestTransformAttrs(object): +class TestTransformAttrs: """ Tests for `_transform_attrs`. """ @@ -189,7 +184,7 @@ def test_empty(self): """ @attr.s - class C(object): + class C: pass assert _Attributes(((), [], {})) == _transform_attrs( @@ -215,7 +210,7 @@ def test_conflicting_defaults(self): mandatory attributes. """ - class C(object): + class C: x = attr.ib(default=None) y = attr.ib() @@ -241,7 +236,7 @@ def test_kw_only(self): """ @attr.s - class B(object): + class B: b = attr.ib() for b_a in B.__attrs_attrs__: @@ -269,7 +264,7 @@ def test_these(self): If these is passed, use it and ignore body and base classes. """ - class Base(object): + class Base: z = attr.ib() class C(Base): @@ -288,7 +283,7 @@ def test_these_leave_body(self): """ @attr.s(init=False, these={"x": attr.ib()}) - class C(object): + class C: x = 5 assert 5 == C().x @@ -303,7 +298,7 @@ def test_these_ordered(self): a = attr.ib(default=1) @attr.s(these=ordered_dict([("a", a), ("b", b)])) - class C(object): + class C: pass assert "C(a=1, b=2)" == repr(C()) @@ -316,7 +311,7 @@ def test_multiple_inheritance_old(self): """ @attr.s - class A(object): + class A: a1 = attr.ib(default="a1") a2 = attr.ib(default="a2") @@ -351,7 +346,7 @@ def test_overwrite_proper_mro(self): """ @attr.s(collect_by_mro=True) - class C(object): + class C: x = attr.ib(default=1) @attr.s(collect_by_mro=True) @@ -368,7 +363,7 @@ def test_multiple_inheritance_proper_mro(self): """ @attr.s - class A(object): + class A: a1 = attr.ib(default="a1") a2 = attr.ib(default="a2") @@ -405,7 +400,7 @@ def test_mro(self): """ @attr.s(collect_by_mro=True) - class A(object): + class A: x = attr.ib(10) @@ -437,7 +432,7 @@ def test_inherited(self): """ @attr.s - class A(object): + class A: a = attr.ib() @attr.s @@ -461,31 +456,18 @@ class C(B): assert False is f(C).c.inherited -class TestAttributes(object): +class TestAttributes: """ Tests for the `attrs`/`attr.s` class decorator. """ - @pytest.mark.skipif(not PY2, reason="No old-style classes in Py3") - def test_catches_old_style(self): - """ - Raises TypeError on old-style classes. - """ - with pytest.raises(TypeError) as e: - - @attr.s - class C: - pass - - assert ("attrs only works with new-style classes.",) == e.value.args - def test_sets_attrs(self): """ Sets the `__attrs_attrs__` class attribute with a list of `Attribute`s. """ @attr.s - class C(object): + class C: x = attr.ib() assert "x" == C.__attrs_attrs__[0].name @@ -497,7 +479,7 @@ def test_empty(self): """ @attr.s - class C3(object): + class C3: pass assert "C3()" == repr(C3()) @@ -523,7 +505,7 @@ def test_adds_all_by_default(self, method_name): # overwritten afterwards. sentinel = object() - class C(object): + class C: x = attr.ib() setattr(C, method_name, sentinel) @@ -564,7 +546,7 @@ def test_respects_add_arguments(self, arg_name, method_name): if arg_name == "eq": am_args["order"] = False - class C(object): + class C: x = attr.ib() setattr(C, method_name, sentinel) @@ -580,13 +562,12 @@ def test_respects_init_attrs_init(self, init): Otherwise, it does not. """ - class C(object): + class C: x = attr.ib() C = attr.s(init=init)(C) assert hasattr(C, "__attrs_init__") != init - @pytest.mark.skipif(PY2, reason="__qualname__ is PY3-only.") @given(slots_outer=booleans(), slots_inner=booleans()) def test_repr_qualname(self, slots_outer, slots_inner): """ @@ -594,9 +575,9 @@ def test_repr_qualname(self, slots_outer, slots_inner): """ @attr.s(slots=slots_outer) - class C(object): + class C: @attr.s(slots=slots_inner) - class D(object): + class D: pass assert "C.D()" == repr(C.D()) @@ -609,14 +590,13 @@ def test_repr_fake_qualname(self, slots_outer, slots_inner): """ @attr.s(slots=slots_outer) - class C(object): + class C: @attr.s(repr_ns="C", slots=slots_inner) - class D(object): + class D: pass assert "C.D()" == repr(C.D()) - @pytest.mark.skipif(PY2, reason="__qualname__ is PY3-only.") @given(slots_outer=booleans(), slots_inner=booleans()) def test_name_not_overridden(self, slots_outer, slots_inner): """ @@ -624,9 +604,9 @@ def test_name_not_overridden(self, slots_outer, slots_inner): """ @attr.s(slots=slots_outer) - class C(object): + class C: @attr.s(slots=slots_inner) - class D(object): + class D: pass assert C.D.__name__ == "D" @@ -640,7 +620,7 @@ def test_pre_init(self, with_validation, monkeypatch): monkeypatch.setattr(_config, "_run_validators", with_validation) @attr.s - class C(object): + class C: def __attrs_pre_init__(self2): self2.z = 30 @@ -656,7 +636,7 @@ def test_post_init(self, with_validation, monkeypatch): monkeypatch.setattr(_config, "_run_validators", with_validation) @attr.s - class C(object): + class C: x = attr.ib() y = attr.ib() @@ -675,7 +655,7 @@ def test_pre_post_init_order(self, with_validation, monkeypatch): monkeypatch.setattr(_config, "_run_validators", with_validation) @attr.s - class C(object): + class C: x = attr.ib() def __attrs_pre_init__(self2): @@ -694,7 +674,7 @@ def test_types(self): """ @attr.s - class C(object): + class C: x = attr.ib(type=int) y = attr.ib(type=str) z = attr.ib() @@ -710,7 +690,7 @@ def test_clean_class(self, slots): """ @attr.s(slots=slots) - class C(object): + class C: x = attr.ib() x = getattr(C, "x", None) @@ -723,7 +703,7 @@ def test_factory_sugar(self): """ @attr.s - class C(object): + class C: x = attr.ib(factory=list) assert Factory(list) == attr.fields(C).x.default @@ -735,7 +715,7 @@ def test_sugar_factory_mutex(self): with pytest.raises(ValueError, match="mutually exclusive"): @attr.s - class C(object): + class C: x = attr.ib(factory=list, default=Factory(list)) def test_sugar_callable(self): @@ -746,7 +726,7 @@ def test_sugar_callable(self): with pytest.raises(ValueError, match="must be a callable"): @attr.s - class C(object): + class C: x = attr.ib(factory=Factory(list)) def test_inherited_does_not_affect_hashing_and_equality(self): @@ -756,7 +736,7 @@ def test_inherited_does_not_affect_hashing_and_equality(self): """ @attr.s - class BaseClass(object): + class BaseClass: x = attr.ib() @attr.s @@ -770,7 +750,7 @@ class SubClass(BaseClass): assert hash(ba) == hash(sa) -class TestKeywordOnlyAttributes(object): +class TestKeywordOnlyAttributes: """ Tests for keyword-only attributes. """ @@ -781,7 +761,7 @@ def test_adds_keyword_only_arguments(self): """ @attr.s - class C(object): + class C: a = attr.ib() b = attr.ib(default=2, kw_only=True) c = attr.ib(kw_only=True) @@ -800,7 +780,7 @@ def test_ignores_kw_only_when_init_is_false(self): """ @attr.s - class C(object): + class C: x = attr.ib(init=False, default=0, kw_only=True) y = attr.ib() @@ -816,20 +796,15 @@ def test_keyword_only_attributes_presence(self): """ @attr.s - class C(object): + class C: x = attr.ib(kw_only=True) with pytest.raises(TypeError) as e: C() - if PY2: - assert ( - "missing required keyword-only argument: 'x'" - ) in e.value.args[0] - else: - assert ( - "missing 1 required keyword-only argument: 'x'" - ) in e.value.args[0] + assert ( + "missing 1 required keyword-only argument: 'x'" + ) in e.value.args[0] def test_keyword_only_attributes_unexpected(self): """ @@ -837,7 +812,7 @@ def test_keyword_only_attributes_unexpected(self): """ @attr.s - class C(object): + class C: x = attr.ib(kw_only=True) with pytest.raises(TypeError) as e: @@ -854,7 +829,7 @@ def test_keyword_only_attributes_can_come_in_any_order(self): """ @attr.s - class C(object): + class C: a = attr.ib(kw_only=True) b = attr.ib(kw_only=True, default="b") c = attr.ib(kw_only=True) @@ -883,7 +858,7 @@ def test_keyword_only_attributes_allow_subclassing(self): """ @attr.s - class Base(object): + class Base: x = attr.ib(default=0) @attr.s @@ -902,7 +877,7 @@ def test_keyword_only_class_level(self): """ @attr.s(kw_only=True) - class C(object): + class C: x = attr.ib() y = attr.ib(kw_only=True) @@ -921,7 +896,7 @@ def test_keyword_only_class_level_subclassing(self): """ @attr.s - class Base(object): + class Base: x = attr.ib(default=0) @attr.s(kw_only=True) @@ -944,7 +919,7 @@ def test_init_false_attribute_after_keyword_attribute(self): """ @attr.s - class KwArgBeforeInitFalse(object): + class KwArgBeforeInitFalse: kwarg = attr.ib(kw_only=True) non_init_function_default = attr.ib(init=False) non_init_keyword_default = attr.ib( @@ -972,7 +947,7 @@ def test_init_false_attribute_after_keyword_attribute_with_inheritance( """ @attr.s - class KwArgBeforeInitFalseParent(object): + class KwArgBeforeInitFalseParent: kwarg = attr.ib(kw_only=True) @attr.s @@ -993,34 +968,14 @@ def _init_to_init(self): assert c.non_init_keyword_default == "default-by-keyword" -@pytest.mark.skipif(not PY2, reason="PY2-specific keyword-only error behavior") -class TestKeywordOnlyAttributesOnPy2(object): - """ - Tests for keyword-only attribute behavior on py2. - """ - - def test_no_init(self): - """ - Keyworld-only is a no-op, not any error, if ``init=false``. - """ - - @attr.s(kw_only=True, init=False) - class ClassLevel(object): - a = attr.ib() - - @attr.s(init=False) - class AttrLevel(object): - a = attr.ib(kw_only=True) - - @attr.s -class GC(object): +class GC: @attr.s - class D(object): + class D: pass -class TestMakeClass(object): +class TestMakeClass: """ Tests for `make_class`. """ @@ -1033,7 +988,7 @@ def test_simple(self, ls): C1 = make_class("C1", ls(["a", "b"])) @attr.s - class C2(object): + class C2: a = attr.ib() b = attr.ib() @@ -1048,7 +1003,7 @@ def test_dict(self): ) @attr.s - class C2(object): + class C2: a = attr.ib(default=42) b = attr.ib(default=None) @@ -1076,7 +1031,7 @@ def test_bases(self): Parameter bases default to (object,) and subclasses correctly """ - class D(object): + class D: pass cls = make_class("C", {}) @@ -1120,7 +1075,6 @@ def test_make_class_ordered(self): assert "C(a=1, b=2)" == repr(C()) - @pytest.mark.skipif(PY2, reason="Python 3-only") def test_generic_dynamic_class(self): """ make_class can create generic dynamic classes. @@ -1137,7 +1091,7 @@ def test_generic_dynamic_class(self): attr.make_class("test", {"id": attr.ib(type=str)}, (MyParent[int],)) -class TestFields(object): +class TestFields: """ Tests for `fields`. """ @@ -1179,7 +1133,7 @@ def test_fields_properties(self, C): assert getattr(fields(C), attribute.name) is attribute -class TestFieldsDict(object): +class TestFieldsDict: """ Tests for `fields_dict`. """ @@ -1217,7 +1171,7 @@ def test_fields_dict(self, C): assert [a.name for a in fields(C)] == [field_name for field_name in d] -class TestConverter(object): +class TestConverter: """ Tests for attribute conversion. """ @@ -1330,7 +1284,7 @@ def test_frozen(self): C("1") -class TestValidate(object): +class TestValidate: """ Tests for `validate`. """ @@ -1428,7 +1382,7 @@ def test_multiple_empty(self): ) -class TestMetadata(object): +class TestMetadata: """ Tests for metadata handling. """ @@ -1523,7 +1477,7 @@ def test_metadata(self): assert md is a.metadata -class TestClassBuilder(object): +class TestClassBuilder: """ Tests for `_ClassBuilder`. """ @@ -1545,7 +1499,7 @@ def test_repr(self): repr of builder itself makes sense. """ - class C(object): + class C: pass b = _ClassBuilder( @@ -1572,7 +1526,7 @@ def test_returns_self(self): All methods return the builder for chaining. """ - class C(object): + class C: x = attr.ib() b = _ClassBuilder( @@ -1627,12 +1581,12 @@ def test_attaches_meta_dunders(self, meth_name): """ @attr.s(hash=True, str=True) - class C(object): + class C: def organic(self): pass @attr.s(hash=True, str=True) - class D(object): + class D: pass meth_C = getattr(C, meth_name) @@ -1640,11 +1594,10 @@ class D(object): assert meth_name == meth_C.__name__ == meth_D.__name__ assert C.organic.__module__ == meth_C.__module__ == meth_D.__module__ - if not PY2: - # This is assertion that would fail if a single __ne__ instance - # was reused across multiple _make_eq calls. - organic_prefix = C.organic.__qualname__.rsplit(".", 1)[0] - assert organic_prefix + "." + meth_name == meth_C.__qualname__ + # This is assertion that would fail if a single __ne__ instance + # was reused across multiple _make_eq calls. + organic_prefix = C.organic.__qualname__.rsplit(".", 1)[0] + assert organic_prefix + "." + meth_name == meth_C.__qualname__ def test_handles_missing_meta_on_class(self): """ @@ -1652,7 +1605,7 @@ def test_handles_missing_meta_on_class(self): either. """ - class C(object): + class C: pass b = _ClassBuilder( @@ -1691,7 +1644,7 @@ def test_weakref_setstate(self): """ @attr.s(slots=True) - class C(object): + class C: __weakref__ = attr.ib( init=False, hash=False, repr=False, eq=False, order=False ) @@ -1705,7 +1658,7 @@ def test_no_references_to_original(self): """ @attr.s(slots=True) - class C(object): + class C: pass @attr.s(slots=True) @@ -1748,7 +1701,7 @@ def test_copy(self, kwargs): """ @attr.s(eq=True, **kwargs) - class C(object): + class C: x = attr.ib() a = C(1) @@ -1763,7 +1716,7 @@ def test_copy_custom_setstate(self, kwargs): """ @attr.s(eq=True, **kwargs) - class C(object): + class C: x = attr.ib() def __getstate__(self): @@ -1792,7 +1745,7 @@ def test_subclasses_cannot_be_compared(self): """ @attr.s - class A(object): + class A: a = attr.ib() @attr.s @@ -1815,21 +1768,20 @@ class B(A): == a.__ge__(b) ) - if not PY2: - with pytest.raises(TypeError): - a <= b + with pytest.raises(TypeError): + a <= b - with pytest.raises(TypeError): - a >= b + with pytest.raises(TypeError): + a >= b - with pytest.raises(TypeError): - a < b + with pytest.raises(TypeError): + a < b - with pytest.raises(TypeError): - a > b + with pytest.raises(TypeError): + a > b -class TestDetermineAttrsEqOrder(object): +class TestDetermineAttrsEqOrder: def test_default(self): """ If all are set to None, set both eq and order to the passed default. @@ -1865,7 +1817,7 @@ def test_mix(self, cmp, eq, order): _determine_attrs_eq_order(cmp, eq, order, True) -class TestDetermineAttribEqOrder(object): +class TestDetermineAttribEqOrder: def test_default(self): """ If all are set to None, set both eq and order to the passed default. @@ -1953,7 +1905,7 @@ def test_docs(self, meth_name): """ @attr.s - class A(object): + class A: pass if hasattr(A, "__qualname__"): @@ -1964,24 +1916,14 @@ class A(object): assert expected == method.__doc__ -@pytest.mark.skipif(not PY2, reason="Needs to be only caught on Python 2.") -def test_auto_detect_raises_on_py2(): - """ - Trying to pass auto_detect=True to attr.s raises PythonTooOldError. - """ - with pytest.raises(PythonTooOldError): - attr.s(auto_detect=True) - - -class BareC(object): +class BareC: pass -class BareSlottedC(object): +class BareSlottedC: __slots__ = () -@pytest.mark.skipif(PY2, reason="Auto-detection is Python 3-only.") class TestAutoDetect: @pytest.mark.parametrize("C", (BareC, BareSlottedC)) def test_determine_detects_non_presence_correctly(self, C): @@ -2010,7 +1952,7 @@ def test_make_all_by_default(self, slots, frozen): """ @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class C(object): + class C: x = attr.ib() i = C(1) @@ -2033,7 +1975,7 @@ def test_detect_auto_init(self, slots, frozen): """ @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class CI(object): + class CI: x = attr.ib() def __init__(self): @@ -2049,7 +1991,7 @@ def test_detect_auto_repr(self, slots, frozen): """ @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class C(object): + class C: x = attr.ib() def __repr__(self): @@ -2066,11 +2008,11 @@ def test_hash_uses_eq(self, slots, frozen): """ @attr.s(slots=slots, frozen=frozen, hash=True) - class C(object): + class C: x = attr.ib(eq=str) @attr.s(slots=slots, frozen=frozen, hash=True) - class D(object): + class D: x = attr.ib() # These hashes should be the same because 1 is turned into @@ -2086,7 +2028,7 @@ def test_detect_auto_hash(self, slots, frozen): """ @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class C(object): + class C: x = attr.ib() def __hash__(self): @@ -2102,7 +2044,7 @@ def test_detect_auto_eq(self, slots, frozen): """ @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class C(object): + class C: x = attr.ib() def __eq__(self, o): @@ -2112,7 +2054,7 @@ def __eq__(self, o): C(1) == C(1) @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class D(object): + class D: x = attr.ib() def __ne__(self, o): @@ -2148,19 +2090,19 @@ def assert_none_set(cls, ex): assert_not_set(cls, ex, "__" + m + "__") @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class LE(object): + class LE: __le__ = 42 @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class LT(object): + class LT: __lt__ = 42 @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class GE(object): + class GE: __ge__ = 42 @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class GT(object): + class GT: __gt__ = 42 assert_none_set(LE, "__le__") @@ -2176,7 +2118,7 @@ def test_override_init(self, slots, frozen): """ @attr.s(init=True, auto_detect=True, slots=slots, frozen=frozen) - class C(object): + class C: x = attr.ib() def __init__(self): @@ -2192,7 +2134,7 @@ def test_override_repr(self, slots, frozen): """ @attr.s(repr=True, auto_detect=True, slots=slots, frozen=frozen) - class C(object): + class C: x = attr.ib() def __repr__(self): @@ -2208,7 +2150,7 @@ def test_override_hash(self, slots, frozen): """ @attr.s(hash=True, auto_detect=True, slots=slots, frozen=frozen) - class C(object): + class C: x = attr.ib() def __hash__(self): @@ -2224,7 +2166,7 @@ def test_override_eq(self, slots, frozen): """ @attr.s(eq=True, auto_detect=True, slots=slots, frozen=frozen) - class C(object): + class C: x = attr.ib() def __eq__(self, o): @@ -2264,7 +2206,7 @@ def meth(self, o): slots=slots, frozen=frozen, ) - class C(object): + class C: x = attr.ib() __le__ = __lt__ = __gt__ = __ge__ = meth @@ -2283,7 +2225,7 @@ def test_total_ordering(self, slots, first): Ensure the order doesn't matter. """ - class C(object): + class C: x = attr.ib() own_eq_called = attr.ib(default=False) own_le_called = attr.ib(default=False) @@ -2329,14 +2271,14 @@ def test_detects_setstate_getstate(self, slots): """ @attr.s(slots=slots, auto_detect=True) - class C(object): + class C: def __getstate__(self): return ("hi",) assert None is getattr(C(), "__setstate__", None) @attr.s(slots=slots, auto_detect=True) - class C(object): + class C: called = attr.ib(False) def __setstate__(self, state): @@ -2358,14 +2300,14 @@ def test_match_args_pre_310(self): """ @attr.s - class C(object): + class C: a = attr.ib() assert None is getattr(C, "__match_args__", None) @pytest.mark.skipif(not PY310, reason="Structural pattern matching is 3.10+") -class TestMatchArgs(object): +class TestMatchArgs: """ Tests for match_args and __match_args__ generation. """ diff --git a/tests/test_mypy.yml b/tests/test_mypy.yml index ca17b0a66..f77984d78 100644 --- a/tests/test_mypy.yml +++ b/tests/test_mypy.yml @@ -983,29 +983,6 @@ a: int = attr.ib() # E: Name "a" already defined on line 16 reveal_type(C) # N: Revealed type is "def (a: builtins.int, b: builtins.int) -> main.C" -- case: testAttrsNewStyleClassPy2 - mypy_config: - python_version = 2.7 - main: | - import attr - @attr.s - class Good(object): - pass - @attr.s - class Bad: # E: attrs only works with new-style classes - pass - skip: True # https://github.com/typeddjango/pytest-mypy-plugins/issues/47 - -- case: testAttrsAutoAttribsPy2 - mypy_config: | - python_version = 2.7 - main: | - import attr - @attr.s(auto_attribs=True) # E: auto_attribs is not supported in Python 2 - class A(object): - x = attr.ib() - skip: True # https://github.com/typeddjango/pytest-mypy-plugins/issues/47 - - case: testAttrsFrozenSubclass main: | import attr @@ -1218,19 +1195,6 @@ a = attr.ib(kw_only=True) b = attr.ib(15) -- case: testAttrsKwOnlyPy2 - mypy_config: - python_version=2.7 - main: | - import attr - @attr.s(kw_only=True) # E: kw_only is not supported in Python 2 - class A(object): - x = attr.ib() - @attr.s - class B(object): - x = attr.ib(kw_only=True) # E: kw_only is not supported in Python 2 - skip: True # https://github.com/typeddjango/pytest-mypy-plugins/issues/47 - - case: testAttrsDisallowUntypedWorksForward main: | # flags: --disallow-untyped-defs diff --git a/tests/test_pattern_matching.py b/tests/test_pattern_matching.py index 590804a8a..3855d6a37 100644 --- a/tests/test_pattern_matching.py +++ b/tests/test_pattern_matching.py @@ -19,7 +19,7 @@ def test_simple_match_case(self, dec): """ @dec - class C(object): + class C: a = attr.ib() assert ("a",) == C.__match_args__ diff --git a/tests/test_pyright.py b/tests/test_pyright.py index c30dcc5cb..e055ebb8c 100644 --- a/tests/test_pyright.py +++ b/tests/test_pyright.py @@ -18,7 +18,7 @@ @attr.s(frozen=True) -class PyrightDiagnostic(object): +class PyrightDiagnostic: severity = attr.ib() message = attr.ib() @@ -36,10 +36,10 @@ def test_pyright_baseline(): ) pyright_result = json.loads(pyright.stdout) - diagnostics = set( + diagnostics = { PyrightDiagnostic(d["severity"], d["message"]) for d in pyright_result["generalDiagnostics"] - ) + } # Expected diagnostics as per pyright 1.1.135 expected_diagnostics = { diff --git a/tests/test_setattr.py b/tests/test_setattr.py index aaedde574..38fcf347d 100644 --- a/tests/test_setattr.py +++ b/tests/test_setattr.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function import pickle @@ -9,22 +8,21 @@ import attr from attr import setters -from attr._compat import PY2 from attr.exceptions import FrozenAttributeError from attr.validators import instance_of, matches_re @attr.s(frozen=True) -class Frozen(object): +class Frozen: x = attr.ib() @attr.s -class WithOnSetAttrHook(object): +class WithOnSetAttrHook: x = attr.ib(on_setattr=lambda *args: None) -class TestSetAttr(object): +class TestSetAttr: def test_change(self): """ The return value of a hook overwrites the value. But they are not run @@ -35,7 +33,7 @@ def hook(*a, **kw): return "hooked!" @attr.s - class Hooked(object): + class Hooked: x = attr.ib(on_setattr=hook) y = attr.ib() @@ -56,7 +54,7 @@ def test_frozen_attribute(self): """ @attr.s - class PartiallyFrozen(object): + class PartiallyFrozen: x = attr.ib(on_setattr=setters.frozen) y = attr.ib() @@ -81,7 +79,7 @@ def test_validator(self, on_setattr): """ @attr.s(on_setattr=on_setattr) - class ValidatedAttribute(object): + class ValidatedAttribute: x = attr.ib() y = attr.ib(validator=[instance_of(str), matches_re("foo.*qux")]) @@ -115,7 +113,7 @@ def test_pipe(self): s = [setters.convert, lambda _, __, nv: nv + 1] @attr.s - class Piped(object): + class Piped: x1 = attr.ib(converter=int, on_setattr=setters.pipe(*s)) x2 = attr.ib(converter=int, on_setattr=s) @@ -147,7 +145,7 @@ def test_no_validator_no_converter(self): """ @attr.s(on_setattr=[setters.convert, setters.validate]) - class C(object): + class C: x = attr.ib() c = C(1) @@ -162,7 +160,7 @@ def test_validate_respects_run_validators_config(self): """ @attr.s(on_setattr=setters.validate) - class C(object): + class C: x = attr.ib(validator=attr.validators.instance_of(int)) c = C(1) @@ -187,7 +185,7 @@ def test_frozen_on_setattr_class_is_caught(self): with pytest.raises(ValueError) as ei: @attr.s(frozen=True, on_setattr=setters.validate) - class C(object): + class C: x = attr.ib() assert "Frozen classes can't use on_setattr." == ei.value.args[0] @@ -200,7 +198,7 @@ def test_frozen_on_setattr_attribute_is_caught(self): with pytest.raises(ValueError) as ei: @attr.s(frozen=True) - class C(object): + class C: x = attr.ib(on_setattr=setters.validate) assert "Frozen classes can't use on_setattr." == ei.value.args[0] @@ -218,16 +216,14 @@ def boom(*args): pytest.fail("Must not be called.") @attr.s - class Hooked(object): + class Hooked: x = attr.ib(on_setattr=boom) @attr.s(slots=slots) class NoHook(WithOnSetAttrHook): x = attr.ib() - if not PY2: - assert NoHook.__setattr__ == object.__setattr__ - + assert NoHook.__setattr__ == object.__setattr__ assert 1 == NoHook(1).x assert Hooked.__attrs_own_setattr__ assert not NoHook.__attrs_own_setattr__ @@ -240,7 +236,7 @@ def test_setattr_inherited_do_not_reset(self, slots): not reset it unless necessary. """ - class A(object): + class A: """ Not an attrs class on purpose to prevent accidental resets that would render the asserts meaningless. @@ -288,7 +284,7 @@ def test_slotted_class_can_have_custom_setattr(self): """ @attr.s(slots=True) - class A(object): + class A: def __setattr__(self, key, value): raise SystemError @@ -306,7 +302,7 @@ def test_slotted_confused(self): """ @attr.s(slots=True) - class A(object): + class A: x = attr.ib(on_setattr=setters.frozen) class B(A): @@ -318,13 +314,6 @@ class C(B): C(1).x = 2 - -@pytest.mark.skipif(PY2, reason="Python 3-only.") -class TestSetAttrNoPy2(object): - """ - __setattr__ tests for Py3+ to avoid the skip repetition. - """ - @pytest.mark.parametrize("slots", [True, False]) def test_setattr_auto_detect_if_no_custom_setattr(self, slots): """ @@ -385,7 +374,7 @@ def test_setattr_auto_detect_on_setattr(self, slots): ): @attr.s(auto_detect=True, slots=slots) - class HookAndCustomSetAttr(object): + class HookAndCustomSetAttr: x = attr.ib(on_setattr=lambda *args: None) def __setattr__(self, _, __): @@ -406,7 +395,7 @@ def test_setattr_inherited_do_not_reset_intermediate( """ @attr.s(slots=a_slots) - class A(object): + class A: x = attr.ib(on_setattr=setters.frozen) @attr.s(slots=b_slots, auto_detect=True) diff --git a/tests/test_slots.py b/tests/test_slots.py index 91697a741..89e7e93f2 100644 --- a/tests/test_slots.py +++ b/tests/test_slots.py @@ -13,7 +13,7 @@ import attr -from attr._compat import PY2, PYPY, just_warn, make_set_closure_cell +from attr._compat import PYPY, just_warn, make_set_closure_cell # Pympler doesn't work on PyPy. @@ -26,7 +26,7 @@ @attr.s -class C1(object): +class C1: x = attr.ib(validator=attr.validators.instance_of(int)) y = attr.ib() @@ -41,18 +41,16 @@ def classmethod(cls): def staticmethod(): return "staticmethod" - if not PY2: + def my_class(self): + return __class__ - def my_class(self): - return __class__ - - def my_super(self): - """Just to test out the no-arg super.""" - return super().__repr__() + def my_super(self): + """Just to test out the no-arg super.""" + return super().__repr__() @attr.s(slots=True, hash=True) -class C1Slots(object): +class C1Slots: x = attr.ib(validator=attr.validators.instance_of(int)) y = attr.ib() @@ -67,14 +65,12 @@ def classmethod(cls): def staticmethod(): return "staticmethod" - if not PY2: - - def my_class(self): - return __class__ + def my_class(self): + return __class__ - def my_super(self): - """Just to test out the no-arg super.""" - return super().__repr__() + def my_super(self): + """Just to test out the no-arg super.""" + return super().__repr__() def test_slots_being_used(): @@ -90,7 +86,7 @@ def test_slots_being_used(): assert "__dict__" in dir(non_slot_instance) assert "__slots__" not in dir(non_slot_instance) - assert set(["__weakref__", "x", "y"]) == set(slot_instance.__slots__) + assert {"__weakref__", "x", "y"} == set(slot_instance.__slots__) if has_pympler: assert asizeof(slot_instance) < asizeof(non_slot_instance) @@ -154,7 +150,7 @@ class C2Slots(C1): assert "clsmethod" == c2.classmethod() assert "staticmethod" == c2.staticmethod() - assert set(["z"]) == set(C2Slots.__slots__) + assert {"z"} == set(C2Slots.__slots__) c3 = C2Slots(x=1, y=3, z="test") @@ -178,7 +174,7 @@ def test_nonslots_these(): This will actually *replace* the class with another one, using slots. """ - class SimpleOrdinaryClass(object): + class SimpleOrdinaryClass: def __init__(self, x, y, z): self.x = x self.y = y @@ -213,7 +209,7 @@ def staticmethod(): assert "clsmethod" == c2.classmethod() assert "staticmethod" == c2.staticmethod() - assert set(["__weakref__", "x", "y", "z"]) == set(C2Slots.__slots__) + assert {"__weakref__", "x", "y", "z"} == set(C2Slots.__slots__) c3 = C2Slots(x=1, y=3, z="test") assert c3 > c2 @@ -245,7 +241,7 @@ class C2(C1): assert 2 == c2.y assert "test" == c2.z - assert set(["z"]) == set(C2Slots.__slots__) + assert {"z"} == set(C2Slots.__slots__) assert 1 == c2.method() assert "clsmethod" == c2.classmethod() @@ -275,7 +271,7 @@ def test_inheritance_from_slots_with_attribute_override(): Inheriting from a slotted class doesn't re-create existing slots """ - class HasXSlot(object): + class HasXSlot: __slots__ = ("x",) @attr.s(slots=True, hash=True) @@ -311,7 +307,7 @@ def test_inherited_slot_reuses_slot_descriptor(): We reuse slot descriptor for an attr.ib defined in a slotted attr.s """ - class HasXSlot(object): + class HasXSlot: __slots__ = ("x",) class OverridesX(HasXSlot): @@ -342,7 +338,7 @@ def test_bare_inheritance_from_slots(): @attr.s( init=False, eq=False, order=False, hash=False, repr=False, slots=True ) - class C1BareSlots(object): + class C1BareSlots: x = attr.ib(validator=attr.validators.instance_of(int)) y = attr.ib() @@ -358,7 +354,7 @@ def staticmethod(): return "staticmethod" @attr.s(init=False, eq=False, order=False, hash=False, repr=False) - class C1Bare(object): + class C1Bare: x = attr.ib(validator=attr.validators.instance_of(int)) y = attr.ib() @@ -409,8 +405,7 @@ class C2(C1Bare): assert {"x": 1, "y": 2, "z": "test"} == attr.asdict(c2) -@pytest.mark.skipif(PY2, reason="closure cell rewriting is PY3-only.") -class TestClosureCellRewriting(object): +class TestClosureCellRewriting: def test_closure_cell_rewriting(self): """ Slotted classes support proper closure cell rewriting. @@ -520,7 +515,7 @@ def test_not_weakrefable(): """ @attr.s(slots=True, weakref_slot=False) - class C(object): + class C: pass c = C() @@ -538,7 +533,7 @@ def test_implicitly_weakrefable(): """ @attr.s(slots=True, weakref_slot=False) - class C(object): + class C: pass c = C() @@ -553,7 +548,7 @@ def test_weakrefable(): """ @attr.s(slots=True, weakref_slot=True) - class C(object): + class C: pass c = C() @@ -568,7 +563,7 @@ def test_weakref_does_not_add_a_field(): """ @attr.s(slots=True, weakref_slot=True) - class C(object): + class C: field = attr.ib() assert [f.name for f in attr.fields(C)] == ["field"] @@ -581,7 +576,7 @@ def tests_weakref_does_not_add_when_inheriting_with_weakref(): """ @attr.s(slots=True, weakref_slot=True) - class C(object): + class C: pass @attr.s(slots=True, weakref_slot=True) @@ -601,7 +596,7 @@ def tests_weakref_does_not_add_with_weakref_attribute(): """ @attr.s(slots=True, weakref_slot=True) - class C(object): + class C: __weakref__ = attr.ib( init=False, hash=False, repr=False, eq=False, order=False ) @@ -628,7 +623,7 @@ def test_slots_empty_cell(): """ @attr.s(slots=True) - class C(object): + class C: field = attr.ib() def f(self, a): @@ -638,16 +633,16 @@ def f(self, a): @attr.s(getstate_setstate=True) -class C2(object): +class C2: x = attr.ib() @attr.s(slots=True, getstate_setstate=True) -class C2Slots(object): +class C2Slots: x = attr.ib() -class TestPickle(object): +class TestPickle: @pytest.mark.parametrize("protocol", range(pickle.HIGHEST_PROTOCOL)) def test_pickleable_by_default(self, protocol): """ @@ -676,7 +671,7 @@ def test_no_getstate_setstate_if_option_false(self): """ @attr.s(slots=True, getstate_setstate=False) - class C(object): + class C: x = attr.ib() i = C(42) @@ -699,7 +694,7 @@ def test_slots_super_property_get(): """ @attr.s(slots=True) - class A(object): + class A: x = attr.ib() @property @@ -710,20 +705,19 @@ def f(self): class B(A): @property def f(self): - return super(B, self).f ** 2 + return super().f ** 2 assert B(11).f == 121 assert B(17).f == 289 -@pytest.mark.skipif(PY2, reason="shortcut super() is PY3-only.") def test_slots_super_property_get_shortcut(): """ On Python 3, the `super()` shortcut is allowed. """ @attr.s(slots=True) - class A(object): + class A: x = attr.ib() @property diff --git a/tests/test_validators.py b/tests/test_validators.py index fce774b10..633f23541 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -4,7 +4,6 @@ Tests for `attr.validators`. """ -from __future__ import absolute_import, division, print_function import re @@ -14,7 +13,7 @@ from attr import _config, fields, has from attr import validators as validator_module -from attr._compat import PY2, TYPE +from attr._compat import TYPE from attr.validators import ( and_, deep_iterable, @@ -49,7 +48,7 @@ def zope_interface(): return zope.interface -class TestDisableValidators(object): +class TestDisableValidators: @pytest.fixture(autouse=True) def reset_default(self): """ @@ -109,7 +108,7 @@ def test_disabled_ctx_with_errors(self): assert _config._run_validators is True -class TestInstanceOf(object): +class TestInstanceOf: """ Tests for `instance_of`. """ @@ -161,7 +160,7 @@ def test_repr(self): ) == repr(v) -class TestMatchesRe(object): +class TestMatchesRe: """ Tests for `matches_re`. """ @@ -178,7 +177,7 @@ def test_match(self): """ @attr.s - class ReTester(object): + class ReTester: str_match = attr.ib(validator=matches_re("a|ab")) ReTester("ab") # shouldn't raise exceptions @@ -195,7 +194,7 @@ def test_flags(self): """ @attr.s - class MatchTester(object): + class MatchTester: val = attr.ib(validator=matches_re("a", re.IGNORECASE, re.match)) MatchTester("A1") # test flags and using re.match @@ -207,7 +206,7 @@ def test_precompiled_pattern(self): pattern = re.compile("a") @attr.s - class RePatternTester(object): + class RePatternTester: val = attr.ib(validator=matches_re(pattern)) RePatternTester("a") @@ -229,7 +228,7 @@ def test_different_func(self): """ @attr.s - class SearchTester(object): + class SearchTester: val = attr.ib(validator=matches_re("a", 0, re.search)) SearchTester("bab") # re.search will match @@ -241,16 +240,10 @@ def test_catches_invalid_func(self): with pytest.raises(ValueError) as ei: matches_re("a", 0, lambda: None) - if not PY2: - assert ( - "'func' must be one of None, fullmatch, match, search." - == ei.value.args[0] - ) - else: - assert ( - "'func' must be one of None, match, search." - == ei.value.args[0] - ) + assert ( + "'func' must be one of None, fullmatch, match, search." + == ei.value.args[0] + ) @pytest.mark.parametrize( "func", [None, getattr(re, "fullmatch", None), re.match, re.search] @@ -283,7 +276,7 @@ def always_fail(_, __, ___): 0 / 0 -class TestAnd(object): +class TestAnd: def test_in_all(self): """ Verify that this validator is in ``__all__``. @@ -313,7 +306,7 @@ def test_sugar(self): """ @attr.s - class C(object): + class C: a1 = attr.ib("a1", validator=and_(instance_of(int))) a2 = attr.ib("a2", validator=[instance_of(int)]) @@ -337,7 +330,7 @@ def f(): return IFoo -class TestProvides(object): +class TestProvides: """ Tests for `provides`. """ @@ -354,7 +347,7 @@ def test_success(self, zope_interface, ifoo): """ @zope_interface.implementer(ifoo) - class C(object): + class C: def f(self): pass @@ -395,7 +388,7 @@ def test_repr(self, ifoo): @pytest.mark.parametrize( "validator", [instance_of(int), [always_pass, instance_of(int)]] ) -class TestOptional(object): +class TestOptional: """ Tests for `optional`. """ @@ -456,7 +449,7 @@ def test_repr(self, validator): assert repr_s == repr(v) -class TestIn_(object): +class TestIn_: """ Tests for `in_`. """ @@ -501,7 +494,7 @@ def test_repr(self): Returned validator has a useful `__repr__`. """ v = in_([3, 4, 5]) - assert (("")) == repr(v) + assert ("") == repr(v) @pytest.fixture( @@ -520,7 +513,7 @@ def _member_validator(request): return request.param -class TestDeepIterable(object): +class TestDeepIterable: """ Tests for `deep_iterable`. """ @@ -685,7 +678,7 @@ def test_repr_sequence_member_and_iterable(self): assert expected_repr == repr(v) -class TestDeepMapping(object): +class TestDeepMapping: """ Tests for `deep_mapping`. """ @@ -789,7 +782,7 @@ def test_repr(self): assert expected_repr == repr(v) -class TestIsCallable(object): +class TestIsCallable: """ Tests for `is_callable`. """ @@ -879,7 +872,7 @@ def test_retrieve_bound(self, v): """ @attr.s - class Tester(object): + class Tester: value = attr.ib(validator=v(self.BOUND)) assert fields(Tester).value.validator.bound == self.BOUND @@ -899,7 +892,7 @@ def test_check_valid(self, v, value): """Silent if value {op} bound.""" @attr.s - class Tester(object): + class Tester: value = attr.ib(validator=v(self.BOUND)) Tester(value) # shouldn't raise exceptions @@ -917,7 +910,7 @@ def test_check_invalid(self, v, value): """Raise ValueError if value {op} bound.""" @attr.s - class Tester(object): + class Tester: value = attr.ib(validator=v(self.BOUND)) with pytest.raises(ValueError): @@ -953,7 +946,7 @@ def test_retrieve_max_len(self): """ @attr.s - class Tester(object): + class Tester: value = attr.ib(validator=max_len(self.MAX_LENGTH)) assert fields(Tester).value.validator.max_length == self.MAX_LENGTH @@ -976,7 +969,7 @@ def test_check_valid(self, value): """ @attr.s - class Tester(object): + class Tester: value = attr.ib(validator=max_len(self.MAX_LENGTH)) Tester(value) # shouldn't raise exceptions @@ -994,7 +987,7 @@ def test_check_invalid(self, value): """ @attr.s - class Tester(object): + class Tester: value = attr.ib(validator=max_len(self.MAX_LENGTH)) with pytest.raises(ValueError): @@ -1026,7 +1019,7 @@ def test_retrieve_min_len(self): """ @attr.s - class Tester(object): + class Tester: value = attr.ib(validator=min_len(self.MIN_LENGTH)) assert fields(Tester).value.validator.min_length == self.MIN_LENGTH @@ -1047,7 +1040,7 @@ def test_check_valid(self, value): """ @attr.s - class Tester(object): + class Tester: value = attr.ib(validator=min_len(self.MIN_LENGTH)) Tester(value) # shouldn't raise exceptions @@ -1065,7 +1058,7 @@ def test_check_invalid(self, value): """ @attr.s - class Tester(object): + class Tester: value = attr.ib(validator=min_len(self.MIN_LENGTH)) with pytest.raises(ValueError): diff --git a/tests/test_version_info.py b/tests/test_version_info.py index 41f75f47a..5bd101bcc 100644 --- a/tests/test_version_info.py +++ b/tests/test_version_info.py @@ -1,11 +1,9 @@ # SPDX-License-Identifier: MIT -from __future__ import absolute_import, division, print_function import pytest from attr import VersionInfo -from attr._compat import PY2 @pytest.fixture(name="vi") @@ -29,9 +27,6 @@ def test_suffix_is_preserved(self): == VersionInfo._from_version_string("19.2.0.dev0").releaselevel ) - @pytest.mark.skipif( - PY2, reason="Python 2 is too YOLO to care about comparability." - ) @pytest.mark.parametrize("other", [(), (19, 2, 0, "final", "garbage")]) def test_wrong_len(self, vi, other): """ diff --git a/tests/utils.py b/tests/utils.py index a2fefbd60..3d10621da 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,7 +4,6 @@ Common helper functions for tests. """ -from __future__ import absolute_import, division, print_function from attr import Attribute from attr._make import NOTHING, make_class @@ -68,7 +67,7 @@ def simple_attr( ) -class TestSimpleClass(object): +class TestSimpleClass: """ Tests for the testing helper function `make_class`. """ diff --git a/tox.ini b/tox.ini index 48e969b47..c5dc2ff5e 100644 --- a/tox.ini +++ b/tox.ini @@ -10,19 +10,17 @@ filterwarnings = # Keep docs in sync with docs env and .readthedocs.yml. [gh-actions] python = - 2.7: py27 3.5: py35 3.6: py36 3.7: py37 3.8: py38, changelog 3.9: py39, pyright 3.10: py310, manifest, typing, docs - pypy-2: pypy pypy-3: pypy3 [tox] -envlist = typing,pre-commit,py27,py35,py36,py37,py38,py39,py310,pypy,pypy3,pyright,manifest,docs,pypi-description,changelog,coverage-report +envlist = typing,pre-commit,py35,py36,py37,py38,py39,py310,pypy3,pyright,manifest,docs,pypi-description,changelog,coverage-report isolated_build = True @@ -41,7 +39,7 @@ extras = tests commands = python -m pytest {posargs} -[testenv:py27] +[testenv:py35] extras = tests commands = coverage run -m pytest {posargs} @@ -64,7 +62,7 @@ commands = coverage run -m pytest {posargs} [testenv:coverage-report] basepython = python3.10 -depends = py27,py37,py310 +depends = py35,py37,py310 skip_install = true deps = coverage[toml]>=5.4 commands =