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:
parent
d0c73d8faa
commit
980c8b04f5
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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/>`_.
|
||||
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 <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!
|
||||
|
|
|
@ -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.
|
|
@ -1,6 +1,5 @@
|
|||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
from hypothesis import HealthCheck, settings
|
||||
|
||||
|
|
|
@ -89,7 +89,6 @@ Full Table of Contents
|
|||
:maxdepth: 1
|
||||
|
||||
license
|
||||
python-2
|
||||
changelog
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
|
@ -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.
|
||||
|
|
5
setup.py
5
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,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import sys
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import functools
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
|
||||
__all__ = ["set_run_validators", "get_run_validators"]
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import copy
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
|
||||
class FrozenError(AttributeError):
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from attr import * # noqa: F401,F403
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -14,7 +14,7 @@ from .strategies import simple_classes
|
|||
cloudpickle = pytest.importorskip("cloudpickle")
|
||||
|
||||
|
||||
class TestCloudpickleCompat(object):
|
||||
class TestCloudpickleCompat:
|
||||
"""
|
||||
Tests for compatibility with ``cloudpickle``.
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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`.
|
||||
"""
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# SPDX-License-Identifier: MIT
|
||||
|
||||
|
||||
class TestImportStar(object):
|
||||
class TestImportStar:
|
||||
def test_from_attr_import_star(self):
|
||||
"""
|
||||
import * from attr
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -19,7 +19,7 @@ class TestPatternMatching:
|
|||
"""
|
||||
|
||||
@dec
|
||||
class C(object):
|
||||
class C:
|
||||
a = attr.ib()
|
||||
|
||||
assert ("a",) == C.__match_args__
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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`.
|
||||
"""
|
||||
|
|
8
tox.ini
8
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 =
|
||||
|
|
Loading…
Reference in New Issue