Drop Python 2.7 (#936)

* 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 <hs@ox.cx>

* 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 <hs@ox.cx>

* 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ć <tinchester@gmail.com>

* 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ć <tinchester@gmail.com>
This commit is contained in:
Hynek Schlawack 2022-03-21 08:47:47 +01:00 committed by GitHub
parent d0c73d8faa
commit 980c8b04f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 486 additions and 913 deletions

View File

@ -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).

View File

@ -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

View File

@ -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

View File

@ -117,7 +117,8 @@ Project Information
its documentation lives at `Read the Docs <https://www.attrs.org/>`_,
the code on `GitHub <https://github.com/python-attrs/attrs>`_,
and the latest release on `PyPI <https://pypi.org/project/attrs/>`_.
Its rigorously tested on Python 2.7, 3.5+, and PyPy.
Its rigorously tested on Python 3.5+ and PyPy.
The last version with Python 2.7 support is `21.4.0 <https://pypi.org/project/attrs/21.4.0/>`_.
We collect information on **third-party extensions** in our `wiki <https://github.com/python-attrs/attrs/wiki/Extensions-to-attrs>`_.
Feel free to browse and add your own!

View File

@ -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.

View File

@ -1,6 +1,5 @@
# SPDX-License-Identifier: MIT
from __future__ import absolute_import, division, print_function
from hypothesis import HealthCheck, settings

View File

@ -89,7 +89,6 @@ Full Table of Contents
:maxdepth: 1
license
python-2
changelog

View File

@ -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.

View File

@ -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 <custom-comparison>`, or :doc:`extensibility <extending>` 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.

View File

@ -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,

View File

@ -1,6 +1,5 @@
# SPDX-License-Identifier: MIT
from __future__ import absolute_import, division, print_function
import sys

View File

@ -1,6 +1,5 @@
# SPDX-License-Identifier: MIT
from __future__ import absolute_import, division, print_function
import functools

View File

@ -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

View File

@ -1,7 +1,5 @@
# SPDX-License-Identifier: MIT
from __future__ import absolute_import, division, print_function
__all__ = ["set_run_validators", "get_run_validators"]

View File

@ -1,6 +1,5 @@
# SPDX-License-Identifier: MIT
from __future__ import absolute_import, division, print_function
import copy

View File

@ -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 = "<attrs generated {0} {1}.{2}>".format(
unique_filename = "<attrs generated {} {}.{}>".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()

View File

@ -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:

View File

@ -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",

View File

@ -1,7 +1,5 @@
# SPDX-License-Identifier: MIT
from __future__ import absolute_import, division, print_function
class FrozenError(AttributeError):
"""

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -1,6 +1,5 @@
# SPDX-License-Identifier: MIT
from __future__ import absolute_import
from attr import * # noqa: F401,F403

View File

@ -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

View File

@ -14,7 +14,7 @@ from .strategies import simple_classes
cloudpickle = pytest.importorskip("cloudpickle")
class TestCloudpickleCompat(object):
class TestCloudpickleCompat:
"""
Tests for compatibility with ``cloudpickle``.
"""

View File

@ -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

View File

@ -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):
"""

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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`.
"""

View File

@ -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")

View File

@ -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)

View File

@ -1,7 +1,7 @@
# SPDX-License-Identifier: MIT
class TestImportStar(object):
class TestImportStar:
def test_from_attr_import_star(self):
"""
import * from attr

View File

@ -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.
"""

View File

@ -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

View File

@ -19,7 +19,7 @@ class TestPatternMatching:
"""
@dec
class C(object):
class C:
a = attr.ib()
assert ("a",) == C.__match_args__

View File

@ -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 = {

View File

@ -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)

View File

@ -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

View File

@ -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 (("<in_ validator with options [3, 4, 5]>")) == repr(v)
assert ("<in_ validator with options [3, 4, 5]>") == 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):

View File

@ -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):
"""

View File

@ -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`.
"""

View File

@ -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 =