From 980c8b04f59d93c429fc66d901d2a24c70a8d777 Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Mon, 21 Mar 2022 08:47:47 +0100 Subject: [PATCH] Drop Python 2.7 (#936) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Drop Python 2.7 The tooling situation around Python 2 has deteriorate to such a degree that upholding compatibility is not tenable anymore for a volunteer-run project. Signed-off-by: Hynek Schlawack * Add newsfragment * Run Python 3.5 under coverage to make up for Python 2.7 * Wait for py35 in parallel * Remove fullmatch kludge * Remove Python 2-specific code * Revert empty slot test Also disable pyupgrade on that file. Signed-off-by: Hynek Schlawack * We DO run under 3.5 * Remove __qualname__ workarounds * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update changelog.d/936.breaking.rst Co-authored-by: Tin Tvrtković * Compare methods using is Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tin Tvrtković --- .github/CONTRIBUTING.md | 2 +- .github/workflows/main.yml | 2 +- .pre-commit-config.yaml | 8 + README.rst | 3 +- changelog.d/936.breaking.rst | 6 + conftest.py | 1 - docs/index.rst | 1 - docs/python-2.rst | 25 --- docs/why.rst | 2 +- setup.py | 5 +- src/attr/__init__.py | 1 - src/attr/_cmp.py | 1 - src/attr/_compat.py | 238 ++++++++-------------------- src/attr/_config.py | 2 - src/attr/_funcs.py | 1 - src/attr/_make.py | 178 ++++----------------- src/attr/_version_info.py | 3 +- src/attr/converters.py | 9 +- src/attr/exceptions.py | 2 - src/attr/filters.py | 1 - src/attr/setters.py | 1 - src/attr/validators.py | 33 ++-- tests/attr_import_star.py | 1 - tests/strategies.py | 3 +- tests/test_3rd_party.py | 2 +- tests/test_annotations.py | 2 +- tests/test_cmp.py | 74 +++------ tests/test_config.py | 3 +- tests/test_converters.py | 11 +- tests/test_dunders.py | 59 ++++--- tests/test_filters.py | 9 +- tests/test_funcs.py | 41 +++-- tests/test_functional.py | 87 +++++----- tests/test_import.py | 2 +- tests/test_make.py | 282 +++++++++++++-------------------- tests/test_mypy.yml | 36 ----- tests/test_pattern_matching.py | 2 +- tests/test_pyright.py | 6 +- tests/test_setattr.py | 47 +++--- tests/test_slots.py | 122 +++++++------- tests/test_validators.py | 69 ++++---- tests/test_version_info.py | 5 - tests/utils.py | 3 +- tox.ini | 8 +- 44 files changed, 486 insertions(+), 913 deletions(-) create mode 100644 changelog.d/936.breaking.rst delete mode 100644 docs/python-2.rst diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 6c9e25c2..e081f9fc 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 a68af6b0..f62e0a60 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 b5c65090..688bdf0c 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 c2e5cf30..cfb7a92f 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 00000000..1a30d126 --- /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 0d539a11..33cc6a6c 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 ff65a673..de82a2d5 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 863c6b03..00000000 --- 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 8489959f..db6282da 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 0b8caffb..59ebc608 100644 --- a/setup.py +++ b/setup.py @@ -32,8 +32,6 @@ CLASSIFIERS = [ "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 @@ EXTRAS_REQUIRE = { "hypothesis", "pympler", "pytest>=4.3.0", # 4.3.0 dropped last use of `convert` - "six", ], } if ( @@ -143,7 +140,7 @@ if __name__ == "__main__": 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 65c94cde..b4759131 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 6cffa4db..0060222a 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 7f11733a..b0d69085 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 @@ else: ordered_dict = OrderedDict -if PY2: - from collections import Mapping, Sequence +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, + ) - 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 isclass(klass): + return isinstance(klass, type) - def new_class(name, bases, kwds, exec_body): + +TYPE = "class" + + +def iteritems(d): + return d.items() + + +new_class = types.new_class + + +def metadata_proxy(d): + return types.MappingProxyType(dict(d)) + + +class _AnnotationExtractor: + """ + Extract type annotations from a callable, returning None whenever there + is none. + """ + + __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): """ - A minimal stub of types.new_class that we need for make_class. + Return the type annotation of the first argument if it's not empty. """ - ns = {} - exec_body(ns) - - return type(name, bases, ns) - - # TYPE is used in exceptions, repr(int) is different on Python 2 and 3. - TYPE = "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. - """ - - 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 __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'" - ) - - 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 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) + ")" - - def metadata_proxy(d): - res = ReadOnlyDict() - res.data.update(d) # We blocked update, so we have to do it like this. - return res - - 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. - """ - - class _AnnotationExtractor: - """ - Always return None, allows to keep ``if PY2``s from code. - """ - - __slots__ = ["sig"] - sig = None - - def __init__(self, callable): - pass - - def get_first_param_type(self): + if not self.sig: return None - def get_return_type(self): - return None + params = list(self.sig.parameters.values()) + if params and params[0].annotation is not inspect.Parameter.empty: + return params[0].annotation -else: # Python 3 and later. - import inspect + return None - from collections.abc import Mapping, Sequence # noqa - - def just_warn(*args, **kw): + def get_return_type(self): """ - We only warn on Python 3 because we are not aware of any concrete - consequences of not setting the cell on Python 2. + Return the return type if it's not empty. """ - 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, - ) + if ( + self.sig + and self.sig.return_annotation is not inspect.Signature.empty + ): + return self.sig.return_annotation - def isclass(klass): - return isinstance(klass, type) - - TYPE = "class" - - def iteritems(d): - return d.items() - - new_class = types.new_class - - def metadata_proxy(d): - return types.MappingProxyType(dict(d)) - - class _AnnotationExtractor: - """ - Extract type annotations from a callable, returning None whenever there - is none. - """ - - __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 make_set_closure_cell(): 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 make_set_closure_cell(): ) 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 make_set_closure_cell(): 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 fc9be29d..96d42007 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 4c90085a..69a3cf6d 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 c1b6f994..0b974f30 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 operator import itemgetter from . import _compat, _config, setters from ._compat import ( HAS_F_STRINGS, - PY2, PY310, PYPY, _AnnotationExtractor, @@ -29,15 +28,10 @@ from .exceptions import ( 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 @@ _sentinel = object() _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 @@ class _Nothing(object): 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 @@ class _ClassBuilder(object): 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 @@ class _ClassBuilder(object): 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 @@ Internal alias so we can use it in functions that take an argument called """ -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 @@ else: 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 _attrs_to_init_script( 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 @@ Attribute = _add_hash( ) -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 @@ class _CountingAttr(object): _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(*converters): 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 cdaeec37..51a1312f 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 @@ from ._make import attrib, attrs @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 b5915398..a73626c2 100644 --- a/src/attr/converters.py +++ b/src/attr/converters.py @@ -4,16 +4,13 @@ Commonly useful converters. """ -from __future__ import absolute_import, division, print_function -from ._compat import PY2, _AnnotationExtractor +import typing + +from ._compat import _AnnotationExtractor from ._make import NOTHING, Factory, pipe -if not PY2: - import typing - - __all__ = [ "default_if_none", "optional", diff --git a/src/attr/exceptions.py b/src/attr/exceptions.py index b2f1edc3..5dc51e0a 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 a1978a87..e7432e4a 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 b9f42a41..12ed6750 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 5f850cc8..e1c01b4f 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 eaec321b..63654526 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 99f9f485..f630b228 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 8866d7f6..0707b2cd 100644 --- a/tests/test_3rd_party.py +++ b/tests/test_3rd_party.py @@ -14,7 +14,7 @@ from .strategies import simple_classes 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 a201ebf7..49c9b0d7 100644 --- a/tests/test_annotations.py +++ b/tests/test_annotations.py @@ -113,7 +113,7 @@ class TestAnnotations: 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 ec2c6874..b9383871 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_data = eq_data + order_data cmp_ids = eq_ids + order_ids -class TestEqOrder(object): +class TestEqOrder: """ Tests for eq and order related methods. """ @@ -92,7 +90,6 @@ class TestEqOrder(object): ######### # 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 @@ class TestEqOrder(object): 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 @@ class TestEqOrder(object): ######### # 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 @@ class TestEqOrder(object): 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 @@ class TestEqOrder(object): ######### # 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 @@ class TestEqOrder(object): 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 @@ class TestEqOrder(object): ######### # 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 @@ class TestEqOrder(object): 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 @@ class TestDundersUnnamedClass(object): 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 @@ class TestDundersUnnamedClass(object): assert method.__name__ == "__ne__" -class TestTotalOrderingException(object): +class TestTotalOrderingException: """ Test for exceptions related to total ordering. """ @@ -345,7 +334,7 @@ class TestTotalOrderingException(object): ) -class TestNotImplementedIsPropagated(object): +class TestNotImplementedIsPropagated: """ Test related to functions that return NotImplemented. """ @@ -361,7 +350,7 @@ class TestNotImplementedIsPropagated(object): assert C(1) != C(1) -class TestDundersPartialOrdering(object): +class TestDundersPartialOrdering: """ Tests for dunder attributes of classes with partial ordering. """ @@ -373,8 +362,7 @@ class TestDundersPartialOrdering(object): 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 @@ class TestDundersPartialOrdering(object): __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 @@ class TestDundersPartialOrdering(object): __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 @@ class TestDundersPartialOrdering(object): __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 @@ class TestDundersFullOrdering(object): 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 bbf67564..6c78fd29 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 3213e3ab..7607e555 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 import Factory, attrib from attr.converters import default_if_none, optional, pipe, to_bool -class TestOptional(object): +class TestOptional: """ Tests for `optional`. """ @@ -45,7 +44,7 @@ class TestOptional(object): 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 @@ class TestDefaultIfNone(object): assert [] == c(None) -class TestPipe(object): +class TestPipe: def test_success(self): """ Succeeds if all wrapped converters succeed. @@ -130,7 +129,7 @@ class TestPipe(object): """ @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 @@ class TestPipe(object): 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 186762eb..03644f8b 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 @@ ReprCSlots = simple_class(repr=True, slots=True) @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 @@ class TestEqOrder(object): match. """ - class NotEqC(object): + class NotEqC: a = 1 b = 2 @@ -316,7 +315,7 @@ class TestEqOrder(object): assert NotImplemented == (cls(1, 2).__ge__(42)) -class TestAddRepr(object): +class TestAddRepr: """ Tests for `_add_repr`. """ @@ -349,7 +348,7 @@ class TestAddRepr(object): 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 @@ class TestAddRepr(object): """ @attr.s - class Cycle(object): + class Cycle: value = attr.ib(default=7) cycle = attr.ib(default=None) @@ -376,7 +375,7 @@ class TestAddRepr(object): """ @attr.s - class LongCycle(object): + class LongCycle: value = attr.ib(default=14) cycle = attr.ib(default=None) @@ -391,7 +390,7 @@ class TestAddRepr(object): repr does not strip underscores. """ - class C(object): + class C: __attrs_attrs__ = [simple_attr("_x")] C = _add_repr(C) @@ -440,21 +439,21 @@ class TestAddRepr(object): # 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 @@ class IncrementingHasher(object): return rv -class TestAddHash(object): +class TestAddHash: """ Tests for `_add_hash`. """ @@ -661,7 +660,7 @@ class TestAddHash(object): kwargs["hash"] = True @attr.s(**kwargs) - class C(object): + class C: x = attr.ib() a = C(IncrementingHasher()) @@ -711,7 +710,7 @@ class TestAddHash(object): """ @attr.s(frozen=frozen, cache_hash=True, hash=True) - class C(object): + class C: x = attr.ib() def __getstate__(self): @@ -729,7 +728,7 @@ class TestAddHash(object): return pickle.loads(pickle_str) -class TestAddInit(object): +class TestAddInit: """ Tests for `_add_init`. """ @@ -802,7 +801,7 @@ class TestAddInit(object): 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 @@ class TestAddInit(object): 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 @@ class TestAddInit(object): underscores. """ - class C(object): + class C: __attrs_attrs__ = [simple_attr("_private")] C = _add_init(C, False) @@ -906,7 +905,7 @@ class TestAddInit(object): assert 42 == i._private -class TestNothing(object): +class TestNothing: """ Tests for `_Nothing`. """ @@ -942,7 +941,7 @@ class TestNothing(object): @attr.s(hash=True, order=True) -class C(object): +class C: pass @@ -951,7 +950,7 @@ OriginalC = C @attr.s(hash=True, order=True) -class C(object): +class C: pass @@ -959,13 +958,13 @@ CopyC = C @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 d1ec24dc..6945bd2d 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 @@ from attr.filters import _split_what, exclude, include @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 @@ class TestSplitWhat(object): ) == _split_what((str, fields(C).a, int)) -class TestInclude(object): +class TestInclude: """ Tests for `include`. """ @@ -73,7 +72,7 @@ class TestInclude(object): 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 4490ed81..40c54875 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 @@ class TestAsDict(object): """ @attr.s - class A(object): + class A: a = attr.ib() instance = A({(1,): 1}) @@ -225,7 +224,7 @@ class TestAsDict(object): """ @attr.s - class A(object): + class A: a = attr.ib() instance = A({(1,): 1}) @@ -233,7 +232,7 @@ class TestAsDict(object): assert {"a": {(1,): 1}} == attr.asdict(instance) -class TestAsTuple(object): +class TestAsTuple: """ Tests for `astuple`. """ @@ -391,7 +390,7 @@ class TestAsTuple(object): assert (1, [1, 2, 3]) == d -class TestHas(object): +class TestHas: """ Tests for `has`. """ @@ -408,7 +407,7 @@ class TestHas(object): """ @attr.s - class D(object): + class D: pass assert has(D) @@ -420,7 +419,7 @@ class TestHas(object): assert not has(object) -class TestAssoc(object): +class TestAssoc: """ Tests for `assoc`. """ @@ -432,7 +431,7 @@ class TestAssoc(object): """ @attr.s(slots=slots, frozen=frozen) - class C(object): + class C: pass i1 = C() @@ -494,7 +493,7 @@ class TestAssoc(object): """ @attr.s(frozen=True) - class C(object): + class C: x = attr.ib() y = attr.ib() @@ -507,7 +506,7 @@ class TestAssoc(object): """ @attr.s - class C(object): + class C: x = attr.ib() with pytest.warns(DeprecationWarning) as wi: @@ -516,7 +515,7 @@ class TestAssoc(object): assert __file__ == wi.list[0].filename -class TestEvolve(object): +class TestEvolve: """ Tests for `evolve`. """ @@ -528,7 +527,7 @@ class TestEvolve(object): """ @attr.s(slots=slots, frozen=frozen) - class C(object): + class C: pass i1 = C() @@ -593,7 +592,7 @@ class TestEvolve(object): """ @attr.s - class C(object): + class C: a = attr.ib(validator=instance_of(int)) with pytest.raises(TypeError) as e: @@ -608,7 +607,7 @@ class TestEvolve(object): """ @attr.s - class C(object): + class C: _a = attr.ib() assert evolve(C(1), a=2)._a == 2 @@ -625,7 +624,7 @@ class TestEvolve(object): """ @attr.s - class C(object): + class C: a = attr.ib() b = attr.ib(init=False, default=0) @@ -639,11 +638,11 @@ class TestEvolve(object): """ @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 @@ class TestEvolve(object): """ @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 6bb989f6..c6a80843 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 @@ import pickle 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 @@ from .strategies import optional_bool @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 @@ foo = None @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 @@ class Base(object): @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 @@ class TestFunctional(object): """ @attr.s(slots=slots) - class C3(object): + class C3: _x = attr.ib() assert "C3(_x=1)" == repr(C3(x=1)) @@ -351,7 +347,7 @@ class TestFunctional(object): """ @attr.s - class C(object): + class C: x = attr.ib(default=1) y = attr.ib() @@ -380,7 +376,7 @@ class TestFunctional(object): dict-classes are never replaced. """ - class C(object): + class C: x = attr.ib() C_new = attr.s(C) @@ -395,7 +391,7 @@ class TestFunctional(object): """ @attr.s(hash=False) - class HashByIDBackwardCompat(object): + class HashByIDBackwardCompat: x = attr.ib() assert hash(HashByIDBackwardCompat(1)) != hash( @@ -403,13 +399,13 @@ class TestFunctional(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 @@ class TestFunctional(object): """ @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 @@ class TestFunctional(object): """ @attr.s(hash=False, eq=False, slots=slots) - class C(object): + class C: pass assert hash(C()) != hash(C()) @@ -450,7 +446,7 @@ class TestFunctional(object): """ @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 @@ class TestFunctional(object): """ @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 @@ class TestFunctional(object): 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 @@ class TestFunctional(object): """ @attr.s - class C(object): + class C: property = attr.ib() itemgetter = attr.ib() x = attr.ib() @@ -633,24 +629,19 @@ class TestFunctional(object): """ @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 @@ class TestFunctional(object): 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 @@ class TestFunctional(object): """ @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 @@ class TestFunctional(object): """ @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 @@ class TestFunctional(object): """ @attr.define - class C(object): + class C: x = attr.ib() src = inspect.getsource(C.__init__) @@ -775,7 +766,7 @@ class TestFunctional(object): """ @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 42312431..9e90a5c1 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 cdca30d0..d4d8640c 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 @@ from hypothesis.strategies import booleans, integers, lists, sampled_from, text 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 @@ from attr._make import ( 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 @@ from .utils import simple_attr 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 @@ class TestCountingAttr(object): def make_tc(): - class TransformC(object): + class TransformC: z = attr.ib() y = attr.ib() x = attr.ib() @@ -160,7 +155,7 @@ def make_tc(): return TransformC -class TestTransformAttrs(object): +class TestTransformAttrs: """ Tests for `_transform_attrs`. """ @@ -189,7 +184,7 @@ class TestTransformAttrs(object): """ @attr.s - class C(object): + class C: pass assert _Attributes(((), [], {})) == _transform_attrs( @@ -215,7 +210,7 @@ class TestTransformAttrs(object): mandatory attributes. """ - class C(object): + class C: x = attr.ib(default=None) y = attr.ib() @@ -241,7 +236,7 @@ class TestTransformAttrs(object): """ @attr.s - class B(object): + class B: b = attr.ib() for b_a in B.__attrs_attrs__: @@ -269,7 +264,7 @@ class TestTransformAttrs(object): 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 @@ class TestTransformAttrs(object): """ @attr.s(init=False, these={"x": attr.ib()}) - class C(object): + class C: x = 5 assert 5 == C().x @@ -303,7 +298,7 @@ class TestTransformAttrs(object): 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 @@ class TestTransformAttrs(object): """ @attr.s - class A(object): + class A: a1 = attr.ib(default="a1") a2 = attr.ib(default="a2") @@ -351,7 +346,7 @@ class TestTransformAttrs(object): """ @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 @@ class TestTransformAttrs(object): """ @attr.s - class A(object): + class A: a1 = attr.ib(default="a1") a2 = attr.ib(default="a2") @@ -405,7 +400,7 @@ class TestTransformAttrs(object): """ @attr.s(collect_by_mro=True) - class A(object): + class A: x = attr.ib(10) @@ -437,7 +432,7 @@ class TestTransformAttrs(object): """ @attr.s - class A(object): + class A: a = attr.ib() @attr.s @@ -461,31 +456,18 @@ class TestTransformAttrs(object): 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 @@ class TestAttributes(object): """ @attr.s - class C3(object): + class C3: pass assert "C3()" == repr(C3()) @@ -523,7 +505,7 @@ class TestAttributes(object): # overwritten afterwards. sentinel = object() - class C(object): + class C: x = attr.ib() setattr(C, method_name, sentinel) @@ -564,7 +546,7 @@ class TestAttributes(object): 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 @@ class TestAttributes(object): 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 @@ class TestAttributes(object): """ @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 @@ class TestAttributes(object): """ @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 @@ class TestAttributes(object): """ @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 @@ class TestAttributes(object): 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 @@ class TestAttributes(object): monkeypatch.setattr(_config, "_run_validators", with_validation) @attr.s - class C(object): + class C: x = attr.ib() y = attr.ib() @@ -675,7 +655,7 @@ class TestAttributes(object): 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 @@ class TestAttributes(object): """ @attr.s - class C(object): + class C: x = attr.ib(type=int) y = attr.ib(type=str) z = attr.ib() @@ -710,7 +690,7 @@ class TestAttributes(object): """ @attr.s(slots=slots) - class C(object): + class C: x = attr.ib() x = getattr(C, "x", None) @@ -723,7 +703,7 @@ class TestAttributes(object): """ @attr.s - class C(object): + class C: x = attr.ib(factory=list) assert Factory(list) == attr.fields(C).x.default @@ -735,7 +715,7 @@ class TestAttributes(object): 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 @@ class TestAttributes(object): 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 @@ class TestAttributes(object): """ @attr.s - class BaseClass(object): + class BaseClass: x = attr.ib() @attr.s @@ -770,7 +750,7 @@ class TestAttributes(object): assert hash(ba) == hash(sa) -class TestKeywordOnlyAttributes(object): +class TestKeywordOnlyAttributes: """ Tests for keyword-only attributes. """ @@ -781,7 +761,7 @@ class TestKeywordOnlyAttributes(object): """ @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 @@ class TestKeywordOnlyAttributes(object): """ @attr.s - class C(object): + class C: x = attr.ib(init=False, default=0, kw_only=True) y = attr.ib() @@ -816,20 +796,15 @@ class TestKeywordOnlyAttributes(object): """ @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 @@ class TestKeywordOnlyAttributes(object): """ @attr.s - class C(object): + class C: x = attr.ib(kw_only=True) with pytest.raises(TypeError) as e: @@ -854,7 +829,7 @@ class TestKeywordOnlyAttributes(object): """ @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 @@ class TestKeywordOnlyAttributes(object): """ @attr.s - class Base(object): + class Base: x = attr.ib(default=0) @attr.s @@ -902,7 +877,7 @@ class TestKeywordOnlyAttributes(object): """ @attr.s(kw_only=True) - class C(object): + class C: x = attr.ib() y = attr.ib(kw_only=True) @@ -921,7 +896,7 @@ class TestKeywordOnlyAttributes(object): """ @attr.s - class Base(object): + class Base: x = attr.ib(default=0) @attr.s(kw_only=True) @@ -944,7 +919,7 @@ class TestKeywordOnlyAttributes(object): """ @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 @@ class TestKeywordOnlyAttributes(object): """ @attr.s - class KwArgBeforeInitFalseParent(object): + class KwArgBeforeInitFalseParent: kwarg = attr.ib(kw_only=True) @attr.s @@ -993,34 +968,14 @@ class TestKeywordOnlyAttributes(object): 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 @@ class TestMakeClass(object): C1 = make_class("C1", ls(["a", "b"])) @attr.s - class C2(object): + class C2: a = attr.ib() b = attr.ib() @@ -1048,7 +1003,7 @@ class TestMakeClass(object): ) @attr.s - class C2(object): + class C2: a = attr.ib(default=42) b = attr.ib(default=None) @@ -1076,7 +1031,7 @@ class TestMakeClass(object): Parameter bases default to (object,) and subclasses correctly """ - class D(object): + class D: pass cls = make_class("C", {}) @@ -1120,7 +1075,6 @@ class TestMakeClass(object): 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 @@ class TestMakeClass(object): attr.make_class("test", {"id": attr.ib(type=str)}, (MyParent[int],)) -class TestFields(object): +class TestFields: """ Tests for `fields`. """ @@ -1179,7 +1133,7 @@ class TestFields(object): assert getattr(fields(C), attribute.name) is attribute -class TestFieldsDict(object): +class TestFieldsDict: """ Tests for `fields_dict`. """ @@ -1217,7 +1171,7 @@ class TestFieldsDict(object): 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 @@ class TestConverter(object): C("1") -class TestValidate(object): +class TestValidate: """ Tests for `validate`. """ @@ -1428,7 +1382,7 @@ sorted_lists_of_attrs = list_of_attrs.map( ) -class TestMetadata(object): +class TestMetadata: """ Tests for metadata handling. """ @@ -1523,7 +1477,7 @@ class TestMetadata(object): assert md is a.metadata -class TestClassBuilder(object): +class TestClassBuilder: """ Tests for `_ClassBuilder`. """ @@ -1545,7 +1499,7 @@ class TestClassBuilder(object): repr of builder itself makes sense. """ - class C(object): + class C: pass b = _ClassBuilder( @@ -1572,7 +1526,7 @@ class TestClassBuilder(object): All methods return the builder for chaining. """ - class C(object): + class C: x = attr.ib() b = _ClassBuilder( @@ -1627,12 +1581,12 @@ class TestClassBuilder(object): """ @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 TestClassBuilder(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 @@ class TestClassBuilder(object): either. """ - class C(object): + class C: pass b = _ClassBuilder( @@ -1691,7 +1644,7 @@ class TestClassBuilder(object): """ @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 @@ class TestClassBuilder(object): """ @attr.s(slots=True) - class C(object): + class C: pass @attr.s(slots=True) @@ -1748,7 +1701,7 @@ class TestClassBuilder(object): """ @attr.s(eq=True, **kwargs) - class C(object): + class C: x = attr.ib() a = C(1) @@ -1763,7 +1716,7 @@ class TestClassBuilder(object): """ @attr.s(eq=True, **kwargs) - class C(object): + class C: x = attr.ib() def __getstate__(self): @@ -1792,7 +1745,7 @@ class TestMakeOrder: """ @attr.s - class A(object): + class A: a = attr.ib() @attr.s @@ -1815,21 +1768,20 @@ class TestMakeOrder: == 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 @@ class TestDetermineAttrsEqOrder(object): _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 @@ class TestDocs: """ @attr.s - class A(object): + class A: pass if hasattr(A, "__qualname__"): @@ -1964,24 +1916,14 @@ class TestDocs: 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 @@ class TestAutoDetect: """ @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class C(object): + class C: x = attr.ib() i = C(1) @@ -2033,7 +1975,7 @@ class TestAutoDetect: """ @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class CI(object): + class CI: x = attr.ib() def __init__(self): @@ -2049,7 +1991,7 @@ class TestAutoDetect: """ @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class C(object): + class C: x = attr.ib() def __repr__(self): @@ -2066,11 +2008,11 @@ class TestAutoDetect: """ @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 @@ class TestAutoDetect: """ @attr.s(auto_detect=True, slots=slots, frozen=frozen) - class C(object): + class C: x = attr.ib() def __hash__(self): @@ -2102,7 +2044,7 @@ class TestAutoDetect: """ @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 @@ class TestAutoDetect: 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 @@ class TestAutoDetect: 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 @@ class TestAutoDetect: """ @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 @@ class TestAutoDetect: """ @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 @@ class TestAutoDetect: """ @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 @@ class TestAutoDetect: """ @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 @@ class TestAutoDetect: slots=slots, frozen=frozen, ) - class C(object): + class C: x = attr.ib() __le__ = __lt__ = __gt__ = __ge__ = meth @@ -2283,7 +2225,7 @@ class TestAutoDetect: 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 @@ class TestAutoDetect: """ @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 @@ class TestAutoDetect: """ @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 ca17b0a6..f77984d7 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 590804a8..3855d6a3 100644 --- a/tests/test_pattern_matching.py +++ b/tests/test_pattern_matching.py @@ -19,7 +19,7 @@ class TestPatternMatching: """ @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 c30dcc5c..e055ebb8 100644 --- a/tests/test_pyright.py +++ b/tests/test_pyright.py @@ -18,7 +18,7 @@ else: @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 aaedde57..38fcf347 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 pytest 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 @@ class TestSetAttr(object): return "hooked!" @attr.s - class Hooked(object): + class Hooked: x = attr.ib(on_setattr=hook) y = attr.ib() @@ -56,7 +54,7 @@ class TestSetAttr(object): """ @attr.s - class PartiallyFrozen(object): + class PartiallyFrozen: x = attr.ib(on_setattr=setters.frozen) y = attr.ib() @@ -81,7 +79,7 @@ class TestSetAttr(object): """ @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 @@ class TestSetAttr(object): 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 @@ class TestSetAttr(object): """ @attr.s(on_setattr=[setters.convert, setters.validate]) - class C(object): + class C: x = attr.ib() c = C(1) @@ -162,7 +160,7 @@ class TestSetAttr(object): """ @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 @@ class TestSetAttr(object): 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 @@ class TestSetAttr(object): 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 @@ class TestSetAttr(object): 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 @@ class TestSetAttr(object): 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 @@ class TestSetAttr(object): """ @attr.s(slots=True) - class A(object): + class A: def __setattr__(self, key, value): raise SystemError @@ -306,7 +302,7 @@ class TestSetAttr(object): """ @attr.s(slots=True) - class A(object): + class A: x = attr.ib(on_setattr=setters.frozen) class B(A): @@ -318,13 +314,6 @@ class TestSetAttr(object): 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 @@ class TestSetAttrNoPy2(object): ): @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 @@ class TestSetAttrNoPy2(object): """ @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 91697a74..89e7e93f 100644 --- a/tests/test_slots.py +++ b/tests/test_slots.py @@ -13,7 +13,7 @@ import pytest 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 @@ except BaseException: # Won't be an import error. @attr.s -class C1(object): +class C1: x = attr.ib(validator=attr.validators.instance_of(int)) y = attr.ib() @@ -41,18 +41,16 @@ class C1(object): 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 @@ class C1Slots(object): 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 @@ def test_inheritance_from_nonslots(): 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 test_nonslots_these(): 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 @@ def test_inheritance_from_slots(): 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 test_bare_inheritance_from_slots(): 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 @@ def test_bare_inheritance_from_slots(): 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 test_slots_empty_cell(): @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 @@ class TestPickle(object): """ @attr.s(slots=True, getstate_setstate=False) - class C(object): + class C: x = attr.ib() i = C(42) @@ -699,31 +694,30 @@ def test_slots_super_property_get(): """ @attr.s(slots=True) - class A(object): - x = attr.ib() - - @property - def f(self): - return self.x - - @attr.s(slots=True) - class B(A): - @property - def f(self): - return super(B, self).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 + def f(self): + return self.x + + @attr.s(slots=True) + class B(A): + @property + def f(self): + return super().f ** 2 + + assert B(11).f == 121 + assert B(17).f == 289 + + +def test_slots_super_property_get_shortcut(): + """ + On Python 3, the `super()` shortcut is allowed. + """ + + @attr.s(slots=True) + class A: x = attr.ib() @property diff --git a/tests/test_validators.py b/tests/test_validators.py index fce774b1..633f2354 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 @@ import attr 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 @@ class TestDisableValidators(object): assert _config._run_validators is True -class TestInstanceOf(object): +class TestInstanceOf: """ Tests for `instance_of`. """ @@ -161,7 +160,7 @@ class TestInstanceOf(object): ) == repr(v) -class TestMatchesRe(object): +class TestMatchesRe: """ Tests for `matches_re`. """ @@ -178,7 +177,7 @@ class TestMatchesRe(object): """ @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 @@ class TestMatchesRe(object): """ @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 @@ class TestMatchesRe(object): pattern = re.compile("a") @attr.s - class RePatternTester(object): + class RePatternTester: val = attr.ib(validator=matches_re(pattern)) RePatternTester("a") @@ -229,7 +228,7 @@ class TestMatchesRe(object): """ @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 @@ class TestMatchesRe(object): 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 @@ class TestAnd(object): """ @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 ifoo(zope_interface): return IFoo -class TestProvides(object): +class TestProvides: """ Tests for `provides`. """ @@ -354,7 +347,7 @@ class TestProvides(object): """ @zope_interface.implementer(ifoo) - class C(object): + class C: def f(self): pass @@ -395,7 +388,7 @@ class TestProvides(object): @pytest.mark.parametrize( "validator", [instance_of(int), [always_pass, instance_of(int)]] ) -class TestOptional(object): +class TestOptional: """ Tests for `optional`. """ @@ -456,7 +449,7 @@ class TestOptional(object): assert repr_s == repr(v) -class TestIn_(object): +class TestIn_: """ Tests for `in_`. """ @@ -501,7 +494,7 @@ class TestIn_(object): 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 @@ class TestDeepIterable(object): assert expected_repr == repr(v) -class TestDeepMapping(object): +class TestDeepMapping: """ Tests for `deep_mapping`. """ @@ -789,7 +782,7 @@ class TestDeepMapping(object): assert expected_repr == repr(v) -class TestIsCallable(object): +class TestIsCallable: """ Tests for `is_callable`. """ @@ -879,7 +872,7 @@ class TestLtLeGeGt: """ @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 @@ class TestLtLeGeGt: """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 @@ class TestLtLeGeGt: """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 @@ class TestMaxLen: """ @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 @@ class TestMaxLen: """ @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 @@ class TestMaxLen: """ @attr.s - class Tester(object): + class Tester: value = attr.ib(validator=max_len(self.MAX_LENGTH)) with pytest.raises(ValueError): @@ -1026,7 +1019,7 @@ class TestMinLen: """ @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 @@ class TestMinLen: """ @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 @@ class TestMinLen: """ @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 41f75f47..5bd101bc 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 @@ class TestVersionInfo: == 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 a2fefbd6..3d10621d 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 48e969b4..c5dc2ff5 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 =