mirror of
https://github.com/servo/servo.git
synced 2025-09-03 03:28:20 +01:00
Update web-platform-tests to revision 60220357131c65146444da1f54624d5b54d0975d
This commit is contained in:
parent
c45192614c
commit
775b784f79
2144 changed files with 58115 additions and 29658 deletions
|
@ -52,4 +52,4 @@ Project maintainers who do not follow or enforce the Code of Conduct in good fai
|
|||
Attribution
|
||||
-----------
|
||||
|
||||
This Code of Conduct is adapted from the `Contributor Covenant <http://contributor-covenant.org>`_, version 1.4, available at http://contributor-covenant.org/version/1/4.
|
||||
This Code of Conduct is adapted from the `Contributor Covenant <https://www.contributor-covenant.org>`_, version 1.4, available at <https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>.
|
|
@ -2,10 +2,10 @@ How To Contribute
|
|||
=================
|
||||
|
||||
First off, thank you for considering contributing to ``attrs``!
|
||||
It's people like *you* who make it is such a great tool for everyone.
|
||||
It's people like *you* who make it such a great tool for everyone.
|
||||
|
||||
This document is mainly to help you to get started by codifying tribal knowledge and expectations and make it more accessible to everyone.
|
||||
But don't be afraid to open half-finished PRs and ask questions if something is unclear!
|
||||
This document intends to make contribution more accessible by codifying tribal knowledge and expectations.
|
||||
Don't be afraid to open half-finished PRs, and ask questions if something is unclear!
|
||||
|
||||
|
||||
Support
|
||||
|
@ -14,7 +14,7 @@ Support
|
|||
In case you'd like to help out but don't want to deal with GitHub, there's a great opportunity:
|
||||
help your fellow developers on `StackOverflow <https://stackoverflow.com/questions/tagged/python-attrs>`_!
|
||||
|
||||
The offical tag is ``python-attrs`` and helping out in support frees us up for improving ``attrs`` instead!
|
||||
The offical tag is ``python-attrs`` and helping out in support frees us up to improve ``attrs`` instead!
|
||||
|
||||
|
||||
Workflow
|
||||
|
@ -67,7 +67,7 @@ Tests
|
|||
It will ensure the test suite runs with all dependencies against all Python versions just as it will on Travis CI.
|
||||
If you lack some Python versions, you can can always limit the environments like ``tox -e py27,py35`` (in that case you may want to look into pyenv_, which makes it very easy to install many different Python versions in parallel).
|
||||
- Write `good test docstrings`_.
|
||||
- To ensure new features work well with the rest of the system, they should be also added to our `Hypothesis`_ testing strategy which you find in ``tests/util.py``.
|
||||
- To ensure new features work well with the rest of the system, they should be also added to our `Hypothesis`_ testing strategy, which is found in ``tests/strategies.py``.
|
||||
|
||||
|
||||
Documentation
|
||||
|
@ -80,7 +80,7 @@ Documentation
|
|||
This is a sentence.
|
||||
This is another sentence.
|
||||
|
||||
- If you start a new section, add two blank lines before and one blank line after the header except if two headers follow immediately after each other:
|
||||
- If you start a new section, add two blank lines before and one blank line after the header, except if two headers follow immediately after each other:
|
||||
|
||||
.. code-block:: rst
|
||||
|
||||
|
@ -94,27 +94,28 @@ Documentation
|
|||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
First line of new section.
|
||||
- If you add a new feature, demonstrate its awesomeness in the `examples page`_!
|
||||
|
||||
- If you add a new feature, demonstrate its awesomeness on the `examples page`_!
|
||||
|
||||
|
||||
Changelog
|
||||
^^^^^^^^^
|
||||
|
||||
If your change is noteworthy, there needs to be a changelog entry, so our users can learn about it!
|
||||
If your change is noteworthy, there needs to be a changelog entry so our users can learn about it!
|
||||
|
||||
To avoid merge conflicts, we use the towncrier_ package to manage our changelog.
|
||||
``towncrier`` uses independent files for each pull request -- so called *news fragments* -- instead of one monolithic changelog file.
|
||||
On release those news fragments are compiled into our ``CHANGELOG.rst``.
|
||||
On release, those news fragments are compiled into our ``CHANGELOG.rst``.
|
||||
|
||||
You don't need to install ``towncrier`` yourself, you just have to abide to a few simple rules:
|
||||
You don't need to install ``towncrier`` yourself, you just have to abide by a few simple rules:
|
||||
|
||||
- For each pull request, add a new file into ``changelog.d`` with a filename adhering to the ``pr#.(change|deprecation|breaking).rst`` schema:
|
||||
For example ``changelog.d/42.change.rst`` for a non-breaking change, that is proposed in pull request number 42.
|
||||
For example, ``changelog.d/42.change.rst`` for a non-breaking change that is proposed in pull request #42.
|
||||
- As with other docs, please use `semantic newlines`_ within news fragments.
|
||||
- Wrap symbols like modules, functions, or classes into double backticks so they are rendered in a monospaced font.
|
||||
- If you mention functions or other callables, add parantheses at the end of their names: ``attr.func()`` or ``attr.Class.method()``.
|
||||
- Wrap symbols like modules, functions, or classes into double backticks so they are rendered in a monospace font.
|
||||
- If you mention functions or other callables, add parentheses at the end of their names: ``attr.func()`` or ``attr.Class.method()``.
|
||||
This makes the changelog a lot more readable.
|
||||
- Prefer simple past or constructions with "now".
|
||||
- Prefer simple past tense or constructions with "now".
|
||||
For example:
|
||||
|
||||
+ Added ``attr.validators.func()``.
|
||||
|
@ -145,46 +146,45 @@ Local Development Environment
|
|||
-----------------------------
|
||||
|
||||
You can (and should) run our test suite using tox_.
|
||||
However you’ll probably want a more traditional environment too.
|
||||
However, you’ll probably want a more traditional environment as well.
|
||||
We highly recommend to develop using the latest Python 3 release because ``attrs`` tries to take advantage of modern features whenever possible.
|
||||
|
||||
First create a `virtual environment <https://virtualenv.pypa.io/>`_.
|
||||
It’s out of scope for this document to list all the ways to manage virtual environments in Python but if you don’t have already a pet way, take some time to look at tools like `pew <https://github.com/berdario/pew>`_, `virtualfish <http://virtualfish.readthedocs.io/>`_, and `virtualenvwrapper <http://virtualenvwrapper.readthedocs.io/>`_.
|
||||
It’s out of scope for this document to list all the ways to manage virtual environments in Python, but if you don’t already have a pet way, take some time to look at tools like `pew <https://github.com/berdario/pew>`_, `virtualfish <http://virtualfish.readthedocs.io/>`_, and `virtualenvwrapper <http://virtualenvwrapper.readthedocs.io/>`_.
|
||||
|
||||
Next get an up to date checkout of the ``attrs`` repository:
|
||||
Next, get an up to date checkout of the ``attrs`` repository:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git checkout git@github.com:python-attrs/attrs.git
|
||||
$ git checkout git@github.com:python-attrs/attrs.git
|
||||
|
||||
Change into the newly created directory and **after activating your virtual environment** install an editable version of ``attrs``:
|
||||
Change into the newly created directory and **after activating your virtual environment** install an editable version of ``attrs`` along with its tests and docs requirements:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
cd attrs
|
||||
pip install -e .
|
||||
$ cd attrs
|
||||
$ pip install -e .[dev]
|
||||
|
||||
If you run the virtual environment’s Python and try to ``import attr`` it should work!
|
||||
|
||||
To run the test suite, you'll need our development dependencies which can be installed using
|
||||
At this point,
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install -r dev-requirements.txt
|
||||
$ python -m pytest
|
||||
|
||||
At this point
|
||||
should work and pass, as should:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python -m pytest
|
||||
$ cd docs
|
||||
$ make html
|
||||
|
||||
should work and pass!
|
||||
The built documentation can then be found in ``docs/_build/html/``.
|
||||
|
||||
|
||||
Governance
|
||||
----------
|
||||
|
||||
``attrs`` is maintained by `team of volunteers`_ that is always open for new members that share our vision of a fast, lean, and magic-free library that empowers programmers to write better code with less effort.
|
||||
``attrs`` is maintained by `team of volunteers`_ that is always open to new members that share our vision of a fast, lean, and magic-free library that empowers programmers to write better code with less effort.
|
||||
If you'd like to join, just get a pull request merged and ask to be added in the very same pull request!
|
||||
|
||||
**The simple rule is that everyone is welcome to review/merge pull requests of others but nobody is allowed to merge their own code.**
|
||||
|
@ -205,7 +205,7 @@ Thank you for considering contributing to ``attrs``!
|
|||
.. _`PEP 8`: https://www.python.org/dev/peps/pep-0008/
|
||||
.. _`PEP 257`: https://www.python.org/dev/peps/pep-0257/
|
||||
.. _`good test docstrings`: https://jml.io/pages/test-docstrings.html
|
||||
.. _`Code of Conduct`: https://github.com/python-attrs/attrs/blob/master/CODE_OF_CONDUCT.rst
|
||||
.. _`Code of Conduct`: https://github.com/python-attrs/attrs/blob/master/.github/CODE_OF_CONDUCT.rst
|
||||
.. _changelog: https://github.com/python-attrs/attrs/blob/master/CHANGELOG.rst
|
||||
.. _`backward compatibility`: http://www.attrs.org/en/latest/backward-compatibility.html
|
||||
.. _tox: https://tox.readthedocs.io/
|
|
@ -3,7 +3,7 @@
|
|||
This is just a reminder about the most common mistakes. Please make sure that you tick all *appropriate* boxes. But please read our [contribution guide](http://www.attrs.org/en/latest/contributing.html) at least once, it will save you unnecessary review cycles!
|
||||
|
||||
- [ ] Added **tests** for changed code.
|
||||
- [ ] New features have been added to our [Hypothesis testing strategy](https://github.com/python-attrs/attrs/blob/master/tests/utils.py).
|
||||
- [ ] New features have been added to our [Hypothesis testing strategy](https://github.com/python-attrs/attrs/blob/master/tests/strategies.py).
|
||||
- [ ] Updated **documentation** for changed code.
|
||||
- [ ] Documentation in `.rst` files is written using [semantic newlines](http://rhodesmill.org/brandon/2012/one-sentence-per-line/).
|
||||
- [ ] Changed/added classes/methods/functions have appropriate `versionadded`, `versionchanged`, or `deprecated` [directives](http://www.sphinx-doc.org/en/stable/markup/para.html#directive-versionadded).
|
|
@ -1,9 +1,11 @@
|
|||
.tox
|
||||
.coverage*
|
||||
*.pyc
|
||||
*.egg-info
|
||||
.cache
|
||||
.coverage*
|
||||
.hypothesis
|
||||
.pytest_cache
|
||||
.tox
|
||||
build
|
||||
dist
|
||||
docs/_build/
|
||||
htmlcov
|
||||
dist
|
||||
.cache
|
||||
.hypothesis
|
6
tests/wpt/web-platform-tests/tools/third_party/attrs/.readthedocs.yml
vendored
Normal file
6
tests/wpt/web-platform-tests/tools/third_party/attrs/.readthedocs.yml
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
python:
|
||||
version: 3
|
||||
pip_install: true
|
||||
extra_requirements:
|
||||
- docs
|
|
@ -1,4 +1,5 @@
|
|||
dist: trusty
|
||||
group: travis_latest
|
||||
sudo: false
|
||||
cache:
|
||||
directories:
|
||||
|
@ -8,6 +9,8 @@ language: python
|
|||
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
include:
|
||||
- python: "2.7"
|
||||
env: TOXENV=py27
|
||||
|
@ -17,11 +20,17 @@ matrix:
|
|||
env: TOXENV=py35
|
||||
- python: "3.6"
|
||||
env: TOXENV=py36
|
||||
- python: "pypy2.7-5.8.0"
|
||||
- python: "pypy"
|
||||
env: TOXENV=pypy
|
||||
- python: "pypy3.5-5.8.0"
|
||||
- python: "pypy3"
|
||||
env: TOXENV=pypy3
|
||||
|
||||
# Prevent breakage by a new releases
|
||||
- python: "3.6-dev"
|
||||
env: TOXENV=py36
|
||||
- python: "3.7-dev"
|
||||
env: TOXENV=py37
|
||||
|
||||
# Meta
|
||||
- python: "3.6"
|
||||
env: TOXENV=flake8
|
||||
|
@ -34,6 +43,10 @@ matrix:
|
|||
- python: "3.6"
|
||||
env: TOXENV=changelog
|
||||
|
||||
allow_failures:
|
||||
- python: "3.6-dev"
|
||||
- python: "3.7-dev"
|
||||
|
||||
|
||||
install:
|
||||
- pip install tox
|
||||
|
|
|
@ -4,17 +4,162 @@ Changelog
|
|||
Versions follow `CalVer <http://calver.org>`_ with a strict backwards compatibility policy.
|
||||
The third digit is only for regressions.
|
||||
|
||||
Changes for the upcoming release can be found in the `"changelog.d" directory <https://github.com/python-attrs/attrs/tree/master/changelog.d>`_ in our repository.
|
||||
|
||||
..
|
||||
Do *NOT* add changelog entries here!
|
||||
|
||||
This changelog is managed by towncrier and is compiled at release time.
|
||||
|
||||
See http://www.attrs.org/en/latest/contributing.html#changelog for details.""" # noqa
|
||||
|
||||
.. towncrier release notes start
|
||||
|
||||
18.1.0 (2018-05-03)
|
||||
-------------------
|
||||
|
||||
Changes
|
||||
^^^^^^^
|
||||
|
||||
- ``x=X(); x.cycle = x; repr(x)`` will no longer raise a ``RecursionError``, and will instead show as ``X(x=...)``.
|
||||
|
||||
`#95 <https://github.com/python-attrs/attrs/issues/95>`_
|
||||
- ``attr.ib(factory=f)`` is now syntactic sugar for the common case of ``attr.ib(default=attr.Factory(f))``.
|
||||
|
||||
`#178 <https://github.com/python-attrs/attrs/issues/178>`_,
|
||||
`#356 <https://github.com/python-attrs/attrs/issues/356>`_
|
||||
- Added ``attr.field_dict()`` to return an ordered dictionary of ``attrs`` attributes for a class, whose keys are the attribute names.
|
||||
|
||||
`#290 <https://github.com/python-attrs/attrs/issues/290>`_,
|
||||
`#349 <https://github.com/python-attrs/attrs/issues/349>`_
|
||||
- The order of attributes that are passed into ``attr.make_class()`` or the ``these`` argument of ``@attr.s()`` is now retained if the dictionary is ordered (i.e. ``dict`` on Python 3.6 and later, ``collections.OrderedDict`` otherwise).
|
||||
|
||||
Before, the order was always determined by the order in which the attributes have been defined which may not be desirable when creating classes programatically.
|
||||
|
||||
`#300 <https://github.com/python-attrs/attrs/issues/300>`_,
|
||||
`#339 <https://github.com/python-attrs/attrs/issues/339>`_,
|
||||
`#343 <https://github.com/python-attrs/attrs/issues/343>`_
|
||||
- In slotted classes, ``__getstate__`` and ``__setstate__`` now ignore the ``__weakref__`` attribute.
|
||||
|
||||
`#311 <https://github.com/python-attrs/attrs/issues/311>`_,
|
||||
`#326 <https://github.com/python-attrs/attrs/issues/326>`_
|
||||
- Setting the cell type is now completely best effort.
|
||||
This fixes ``attrs`` on Jython.
|
||||
|
||||
We cannot make any guarantees regarding Jython though, because our test suite cannot run due to dependency incompatabilities.
|
||||
|
||||
`#321 <https://github.com/python-attrs/attrs/issues/321>`_,
|
||||
`#334 <https://github.com/python-attrs/attrs/issues/334>`_
|
||||
- If ``attr.s`` is passed a *these* argument, it will not attempt to remove attributes with the same name from the class body anymore.
|
||||
|
||||
`#322 <https://github.com/python-attrs/attrs/issues/322>`_,
|
||||
`#323 <https://github.com/python-attrs/attrs/issues/323>`_
|
||||
- The hash of ``attr.NOTHING`` is now vegan and faster on 32bit Python builds.
|
||||
|
||||
`#331 <https://github.com/python-attrs/attrs/issues/331>`_,
|
||||
`#332 <https://github.com/python-attrs/attrs/issues/332>`_
|
||||
- The overhead of instantiating frozen dict classes is virtually eliminated.
|
||||
`#336 <https://github.com/python-attrs/attrs/issues/336>`_
|
||||
- Generated ``__init__`` methods now have an ``__annotations__`` attribute derived from the types of the fields.
|
||||
|
||||
`#363 <https://github.com/python-attrs/attrs/issues/363>`_
|
||||
- We have restructured the documentation a bit to account for ``attrs``' growth in scope.
|
||||
Instead of putting everything into the `examples <http://www.attrs.org/en/stable/examples.html>`_ page, we have started to extract narrative chapters.
|
||||
|
||||
So far, we've added chapters on `initialization <http://www.attrs.org/en/stable/init.html>`_ and `hashing <http://www.attrs.org/en/stable/hashing.html>`_.
|
||||
|
||||
Expect more to come!
|
||||
|
||||
`#369 <https://github.com/python-attrs/attrs/issues/369>`_,
|
||||
`#370 <https://github.com/python-attrs/attrs/issues/370>`_
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
17.4.0 (2017-12-30)
|
||||
-------------------
|
||||
|
||||
Backward-incompatible Changes
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- The traversal of MROs when using multiple inheritance was backward:
|
||||
If you defined a class ``C`` that subclasses ``A`` and ``B`` like ``C(A, B)``, ``attrs`` would have collected the attributes from ``B`` *before* those of ``A``.
|
||||
|
||||
This is now fixed and means that in classes that employ multiple inheritance, the output of ``__repr__`` and the order of positional arguments in ``__init__`` changes.
|
||||
Due to the nature of this bug, a proper deprecation cycle was unfortunately impossible.
|
||||
|
||||
Generally speaking, it's advisable to prefer ``kwargs``-based initialization anyways – *especially* if you employ multiple inheritance and diamond-shaped hierarchies.
|
||||
|
||||
`#298 <https://github.com/python-attrs/attrs/issues/298>`_,
|
||||
`#299 <https://github.com/python-attrs/attrs/issues/299>`_,
|
||||
`#304 <https://github.com/python-attrs/attrs/issues/304>`_
|
||||
- The ``__repr__`` set by ``attrs``
|
||||
no longer produces an ``AttributeError``
|
||||
when the instance is missing some of the specified attributes
|
||||
(either through deleting
|
||||
or after using ``init=False`` on some attributes).
|
||||
|
||||
This can break code
|
||||
that relied on ``repr(attr_cls_instance)`` raising ``AttributeError``
|
||||
to check if any attr-specified members were unset.
|
||||
|
||||
If you were using this,
|
||||
you can implement a custom method for checking this::
|
||||
|
||||
def has_unset_members(self):
|
||||
for field in attr.fields(type(self)):
|
||||
try:
|
||||
getattr(self, field.name)
|
||||
except AttributeError:
|
||||
return True
|
||||
return False
|
||||
|
||||
`#308 <https://github.com/python-attrs/attrs/issues/308>`_
|
||||
|
||||
|
||||
Deprecations
|
||||
^^^^^^^^^^^^
|
||||
|
||||
- The ``attr.ib(convert=callable)`` option is now deprecated in favor of ``attr.ib(converter=callable)``.
|
||||
|
||||
This is done to achieve consistency with other noun-based arguments like *validator*.
|
||||
|
||||
*convert* will keep working until at least January 2019 while raising a ``DeprecationWarning``.
|
||||
|
||||
`#307 <https://github.com/python-attrs/attrs/issues/307>`_
|
||||
|
||||
|
||||
Changes
|
||||
^^^^^^^
|
||||
|
||||
- Generated ``__hash__`` methods now hash the class type along with the attribute values.
|
||||
Until now the hashes of two classes with the same values were identical which was a bug.
|
||||
|
||||
The generated method is also *much* faster now.
|
||||
|
||||
`#261 <https://github.com/python-attrs/attrs/issues/261>`_,
|
||||
`#295 <https://github.com/python-attrs/attrs/issues/295>`_,
|
||||
`#296 <https://github.com/python-attrs/attrs/issues/296>`_
|
||||
- ``attr.ib``\ ’s ``metadata`` argument now defaults to a unique empty ``dict`` instance instead of sharing a common empty ``dict`` for all.
|
||||
The singleton empty ``dict`` is still enforced.
|
||||
|
||||
`#280 <https://github.com/python-attrs/attrs/issues/280>`_
|
||||
- ``ctypes`` is optional now however if it's missing, a bare ``super()`` will not work in slotted classes.
|
||||
This should only happen in special environments like Google App Engine.
|
||||
|
||||
`#284 <https://github.com/python-attrs/attrs/issues/284>`_,
|
||||
`#286 <https://github.com/python-attrs/attrs/issues/286>`_
|
||||
- The attribute redefinition feature introduced in 17.3.0 now takes into account if an attribute is redefined via multiple inheritance.
|
||||
In that case, the definition that is closer to the base of the class hierarchy wins.
|
||||
|
||||
`#285 <https://github.com/python-attrs/attrs/issues/285>`_,
|
||||
`#287 <https://github.com/python-attrs/attrs/issues/287>`_
|
||||
- Subclasses of ``auto_attribs=True`` can be empty now.
|
||||
|
||||
`#291 <https://github.com/python-attrs/attrs/issues/291>`_,
|
||||
`#292 <https://github.com/python-attrs/attrs/issues/292>`_
|
||||
- Equality tests are *much* faster now.
|
||||
|
||||
`#306 <https://github.com/python-attrs/attrs/issues/306>`_
|
||||
- All generated methods now have correct ``__module__``, ``__name__``, and (on Python 3) ``__qualname__`` attributes.
|
||||
|
||||
`#309 <https://github.com/python-attrs/attrs/issues/309>`_
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
17.3.0 (2017-11-08)
|
||||
-------------------
|
||||
|
@ -33,7 +178,7 @@ Backward-incompatible Changes
|
|||
Changes
|
||||
^^^^^^^
|
||||
|
||||
- ``super()`` and ``__class__`` now work on Python 3 when ``slots=True``.
|
||||
- ``super()`` and ``__class__`` now work with slotted classes on Python 3.
|
||||
(`#102 <https://github.com/python-attrs/attrs/issues/102>`_, `#226 <https://github.com/python-attrs/attrs/issues/226>`_, `#269 <https://github.com/python-attrs/attrs/issues/269>`_, `#270 <https://github.com/python-attrs/attrs/issues/270>`_, `#272 <https://github.com/python-attrs/attrs/issues/272>`_)
|
||||
- Added ``type`` argument to ``attr.ib()`` and corresponding ``type`` attribute to ``attr.Attribute``.
|
||||
|
||||
|
@ -196,13 +341,13 @@ Changes:
|
|||
`#76 <https://github.com/python-attrs/attrs/issues/76>`_
|
||||
- Instantiation of ``attrs`` classes with converters is now significantly faster.
|
||||
`#80 <https://github.com/python-attrs/attrs/pull/80>`_
|
||||
- Pickling now works with ``__slots__`` classes.
|
||||
- Pickling now works with slotted classes.
|
||||
`#81 <https://github.com/python-attrs/attrs/issues/81>`_
|
||||
- ``attr.assoc()`` now works with ``__slots__`` classes.
|
||||
- ``attr.assoc()`` now works with slotted classes.
|
||||
`#84 <https://github.com/python-attrs/attrs/issues/84>`_
|
||||
- The tuple returned by ``attr.fields()`` now also allows to access the ``Attribute`` instances by name.
|
||||
Yes, we've subclassed ``tuple`` so you don't have to!
|
||||
Therefore ``attr.fields(C).x`` is equivalent to the deprecated ``C.x`` and works with ``__slots__`` classes.
|
||||
Therefore ``attr.fields(C).x`` is equivalent to the deprecated ``C.x`` and works with slotted classes.
|
||||
`#88 <https://github.com/python-attrs/attrs/issues/88>`_
|
||||
|
||||
|
||||
|
@ -271,7 +416,7 @@ Changes:
|
|||
^^^^^^^^
|
||||
|
||||
- ``__slots__`` have arrived!
|
||||
Classes now can automatically be `slots <https://docs.python.org/3/reference/datamodel.html#slots>`_-style (and save your precious memory) just by passing ``slots=True``.
|
||||
Classes now can automatically be `slotted <https://docs.python.org/3/reference/datamodel.html#slots>`_-style (and save your precious memory) just by passing ``slots=True``.
|
||||
`#35 <https://github.com/python-attrs/attrs/issues/35>`_
|
||||
- Allow the case of initializing attributes that are set to ``init=False``.
|
||||
This allows for clean initializer parameter lists while being able to initialize attributes to default values.
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
include LICENSE *.rst *.toml
|
||||
include LICENSE *.rst *.toml .readthedocs.yml
|
||||
|
||||
# Don't package GitHub-specific files.
|
||||
exclude *.md .travis.yml
|
||||
exclude .github/*.md .travis.yml
|
||||
|
||||
# Tests
|
||||
include tox.ini .coveragerc conftest.py dev-requirements.txt docs-requirements.txt
|
||||
include tox.ini .coveragerc conftest.py
|
||||
recursive-include tests *.py
|
||||
recursive-include .github *.rst
|
||||
|
||||
# Documentation
|
||||
include docs/Makefile docs/docutils.conf
|
||||
|
@ -15,5 +16,7 @@ recursive-include docs *.py
|
|||
recursive-include docs *.rst
|
||||
prune docs/_build
|
||||
|
||||
# Changelog news fragments -- is empty on releases.
|
||||
prune changelog.d
|
||||
# Just to keep check-manifest happy; on releases those files are gone.
|
||||
# Last rule wins!
|
||||
exclude changelog.d/*.rst
|
||||
include changelog.d/towncrier_template.rst
|
||||
|
|
|
@ -32,7 +32,7 @@ For that, it gives you a class decorator and a way to declaratively define the a
|
|||
.. code-block:: pycon
|
||||
|
||||
>>> import attr
|
||||
|
||||
|
||||
>>> @attr.s
|
||||
... class SomeClass(object):
|
||||
... a_number = attr.ib(default=42)
|
||||
|
@ -40,25 +40,25 @@ For that, it gives you a class decorator and a way to declaratively define the a
|
|||
...
|
||||
... def hard_math(self, another_number):
|
||||
... return self.a_number + sum(self.list_of_numbers) * another_number
|
||||
|
||||
|
||||
|
||||
|
||||
>>> sc = SomeClass(1, [1, 2, 3])
|
||||
>>> sc
|
||||
SomeClass(a_number=1, list_of_numbers=[1, 2, 3])
|
||||
|
||||
|
||||
>>> sc.hard_math(3)
|
||||
19
|
||||
>>> sc == SomeClass(1, [1, 2, 3])
|
||||
True
|
||||
>>> sc != SomeClass(2, [3, 2, 1])
|
||||
True
|
||||
|
||||
|
||||
>>> attr.asdict(sc)
|
||||
{'a_number': 1, 'list_of_numbers': [1, 2, 3]}
|
||||
|
||||
|
||||
>>> SomeClass()
|
||||
SomeClass(a_number=42, list_of_numbers=[])
|
||||
|
||||
|
||||
>>> C = attr.make_class("C", ["a", "b"])
|
||||
>>> C("foo", "bar")
|
||||
C(a='foo', b='bar')
|
||||
|
@ -126,4 +126,7 @@ 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.4+, and PyPy.
|
||||
|
||||
If you'd like to contribute you're most welcome and we've written `a little guide <http://www.attrs.org/en/latest/contributing.html>`_ to get you started!
|
||||
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!
|
||||
|
||||
If you'd like to contribute to ``attrs`` you're most welcome and we've written `a little guide <http://www.attrs.org/en/latest/contributing.html>`_ to get you started!
|
||||
|
|
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/178.change.rst
vendored
Normal file
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/178.change.rst
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
``attr.ib(factory=f)`` is now syntactic sugar for the common case of ``attr.ib(default=attr.Factory(f))``.
|
|
@ -1,4 +0,0 @@
|
|||
Generated ``__hash__`` methods now hash the class type along with the attribute values.
|
||||
Until now the hashes of two classes with the same values were identical which was a bug.
|
||||
|
||||
The generated method is also *much* faster now.
|
|
@ -1,2 +0,0 @@
|
|||
``ctypes`` is optional now however if it's missing, a bare ``super()`` will not work in slots classes.
|
||||
This should only happen in special environments like Google App Engine.
|
|
@ -1,2 +0,0 @@
|
|||
The attribute redefinition feature introduced in 17.3.0 now takes into account if an attribute is redefined via multiple inheritance.
|
||||
In that case, the definition that is closer to the base of the class hierarchy wins.
|
|
@ -1,2 +0,0 @@
|
|||
``ctypes`` is optional now however if it's missing, a bare ``super()`` will not work in slots classes.
|
||||
This should only happen in special environments like Google App Engine.
|
|
@ -1,2 +0,0 @@
|
|||
The attribute redefinition feature introduced in 17.3.0 now takes into account if an attribute is redefined via multiple inheritance.
|
||||
In that case, the definition that is closer to the base of the class hierarchy wins.
|
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/290.change.rst
vendored
Normal file
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/290.change.rst
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Added ``attr.field_dict()`` to return an ordered dictionary of ``attrs`` attributes for a class, whose keys are the attribute names.
|
|
@ -1 +0,0 @@
|
|||
Subclasses of ``auto_attribs=True`` can be empty now.
|
|
@ -1 +0,0 @@
|
|||
Subclasses of ``auto_attribs=True`` can be empty now.
|
|
@ -1,4 +0,0 @@
|
|||
Generated ``__hash__`` methods now hash the class type along with the attribute values.
|
||||
Until now the hashes of two classes with the same values were identical which was a bug.
|
||||
|
||||
The generated method is also *much* faster now.
|
|
@ -1,4 +0,0 @@
|
|||
Generated ``__hash__`` methods now hash the class type along with the attribute values.
|
||||
Until now the hashes of two classes with the same values were identical which was a bug.
|
||||
|
||||
The generated method is also *much* faster now.
|
3
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/300.change.rst
vendored
Normal file
3
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/300.change.rst
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
The order of attributes that are passed into ``attr.make_class()`` or the ``these`` argument of ``@attr.s()`` is now retained if the dictionary is ordered (i.e. ``dict`` on Python 3.6 and later, ``collections.OrderedDict`` otherwise).
|
||||
|
||||
Before, the order was always determined by the order in which the attributes have been defined which may not be desirable when creating classes programatically.
|
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/311.change.rst
vendored
Normal file
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/311.change.rst
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
In slotted classes, ``__getstate__`` and ``__setstate__`` now ignore the ``__weakref__`` attribute.
|
4
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/321.change.rst
vendored
Normal file
4
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/321.change.rst
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
Setting the cell type is now completely best effort.
|
||||
This fixes ``attrs`` on Jython.
|
||||
|
||||
We cannot make any guarantees regarding Jython though, because our test suite cannot run due to dependency incompatabilities.
|
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/322.change.rst
vendored
Normal file
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/322.change.rst
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
If ``attr.s`` is passed a *these* argument, it will not attempt to remove attributes with the same name from the class body anymore.
|
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/323.change.rst
vendored
Normal file
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/323.change.rst
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
If ``attr.s`` is passed a *these* argument, it will not attempt to remove attributes with the same name from the class body anymore.
|
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/326.change.rst
vendored
Normal file
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/326.change.rst
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
In slotted classes, ``__getstate__`` and ``__setstate__`` now ignore the ``__weakref__`` attribute.
|
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/331.change.rst
vendored
Normal file
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/331.change.rst
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
The hash of ``attr.NOTHING`` is now vegan and faster on 32bit Python builds.
|
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/332.change.rst
vendored
Normal file
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/332.change.rst
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
The hash of ``attr.NOTHING`` is now vegan and faster on 32bit Python builds.
|
4
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/334.change.rst
vendored
Normal file
4
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/334.change.rst
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
Setting the cell type is now completely best effort.
|
||||
This fixes ``attrs`` on Jython.
|
||||
|
||||
We cannot make any guarantees regarding Jython though, because our test suite cannot run due to dependency incompatabilities.
|
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/336.change.rst
vendored
Normal file
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/336.change.rst
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
The overhead of instantiating frozen dict classes is virtually eliminated.
|
3
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/339.change.rst
vendored
Normal file
3
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/339.change.rst
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
The order of attributes that are passed into ``attr.make_class()`` or the ``these`` argument of ``@attr.s()`` is now retained if the dictionary is ordered (i.e. ``dict`` on Python 3.6 and later, ``collections.OrderedDict`` otherwise).
|
||||
|
||||
Before, the order was always determined by the order in which the attributes have been defined which may not be desirable when creating classes programatically.
|
3
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/343.change.rst
vendored
Normal file
3
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/343.change.rst
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
The order of attributes that are passed into ``attr.make_class()`` or the ``these`` argument of ``@attr.s()`` is now retained if the dictionary is ordered (i.e. ``dict`` on Python 3.6 and later, ``collections.OrderedDict`` otherwise).
|
||||
|
||||
Before, the order was always determined by the order in which the attributes have been defined which may not be desirable when creating classes programatically.
|
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/349.change.rst
vendored
Normal file
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/349.change.rst
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Added ``attr.field_dict()`` to return an ordered dictionary of ``attrs`` attributes for a class, whose keys are the attribute names.
|
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/356.change.rst
vendored
Normal file
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/356.change.rst
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
``attr.ib(factory=f)`` is now syntactic sugar for the common case of ``attr.ib(default=attr.Factory(f))``.
|
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/363.change.rst
vendored
Normal file
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/363.change.rst
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Generated ``__init__`` methods now have an ``__annotations__`` attribute derived from the types of the fields.
|
6
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/369.change.rst
vendored
Normal file
6
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/369.change.rst
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
We have restructured the documentation a bit to account for ``attrs``' growth in scope.
|
||||
Instead of putting everything into the `examples <http://www.attrs.org/en/stable/examples.html>`_ page, we have started to extract narrative chapters.
|
||||
|
||||
So far, we've added chapters on `initialization <http://www.attrs.org/en/stable/init.html>`_ and `hashing <http://www.attrs.org/en/stable/hashing.html>`_.
|
||||
|
||||
Expect more to come!
|
6
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/370.change.rst
vendored
Normal file
6
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/370.change.rst
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
We have restructured the documentation a bit to account for ``attrs``' growth in scope.
|
||||
Instead of putting everything into the `examples <http://www.attrs.org/en/stable/examples.html>`_ page, we have started to extract narrative chapters.
|
||||
|
||||
So far, we've added chapters on `initialization <http://www.attrs.org/en/stable/init.html>`_ and `hashing <http://www.attrs.org/en/stable/hashing.html>`_.
|
||||
|
||||
Expect more to come!
|
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/95.change.rst
vendored
Normal file
1
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/95.change.rst
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
``x=X(); x.cycle = x; repr(x)`` will no longer raise a ``RecursionError``, and will instead show as ``X(x=...)``.
|
35
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/towncrier_template.rst
vendored
Normal file
35
tests/wpt/web-platform-tests/tools/third_party/attrs/changelog.d/towncrier_template.rst
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
{% for section, _ in sections.items() %}
|
||||
{% set underline = underlines[0] %}{% if section %}{{section}}
|
||||
{{ underline * section|length }}{% set underline = underlines[1] %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% if sections[section] %}
|
||||
{% for category, val in definitions.items() if category in sections[section]%}
|
||||
{{ definitions[category]['name'] }}
|
||||
{{ underline * definitions[category]['name']|length }}
|
||||
|
||||
{% if definitions[category]['showcontent'] %}
|
||||
{% for text, values in sections[section][category].items() %}
|
||||
- {{ text }}
|
||||
{{ values|join(',\n ') }}
|
||||
{% endfor %}
|
||||
|
||||
{% else %}
|
||||
- {{ sections[section][category]['']|join(', ') }}
|
||||
|
||||
{% endif %}
|
||||
{% if sections[section][category]|length == 0 %}
|
||||
No significant changes.
|
||||
|
||||
{% else %}
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
No significant changes.
|
||||
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
----
|
|
@ -1,6 +0,0 @@
|
|||
coverage
|
||||
hypothesis
|
||||
pympler
|
||||
pytest
|
||||
six
|
||||
zope.interface
|
|
@ -1,3 +0,0 @@
|
|||
-e .
|
||||
sphinx
|
||||
zope.interface
|
|
@ -18,7 +18,7 @@ What follows is the API explanation, if you'd like a more hands-on introduction,
|
|||
Core
|
||||
----
|
||||
|
||||
.. autofunction:: attr.s(these=None, repr_ns=None, repr=True, cmp=True, hash=None, init=True, slots=False, frozen=False, str=False)
|
||||
.. autofunction:: attr.s(these=None, repr_ns=None, repr=True, cmp=True, hash=None, init=True, slots=False, frozen=False, str=False, auto_attribs=False)
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -90,7 +90,7 @@ Core
|
|||
... class C(object):
|
||||
... x = attr.ib()
|
||||
>>> attr.fields(C).x
|
||||
Attribute(name='x', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}), type=None)
|
||||
Attribute(name='x', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None)
|
||||
|
||||
|
||||
.. autofunction:: attr.make_class
|
||||
|
@ -140,56 +140,7 @@ Core
|
|||
@attr.s(auto_attribs=True)
|
||||
class C:
|
||||
x: int
|
||||
y = attr.ib()
|
||||
|
||||
|
||||
Influencing Initialization
|
||||
++++++++++++++++++++++++++
|
||||
|
||||
Generally speaking, it's best to keep logic out of your ``__init__``.
|
||||
The moment you need a finer control over how your class is instantiated, it's usually best to use a classmethod factory or to apply the `builder pattern <https://en.wikipedia.org/wiki/Builder_pattern>`_.
|
||||
|
||||
However, sometimes you need to do that one quick thing after your class is initialized.
|
||||
And for that ``attrs`` offers the ``__attrs_post_init__`` hook that is automatically detected and run after ``attrs`` is done initializing your instance:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> @attr.s
|
||||
... class C(object):
|
||||
... x = attr.ib()
|
||||
... y = attr.ib(init=False)
|
||||
... def __attrs_post_init__(self):
|
||||
... self.y = self.x + 1
|
||||
>>> C(1)
|
||||
C(x=1, y=2)
|
||||
|
||||
Please note that you can't directly set attributes on frozen classes:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> @attr.s(frozen=True)
|
||||
... class FrozenBroken(object):
|
||||
... x = attr.ib()
|
||||
... y = attr.ib(init=False)
|
||||
... def __attrs_post_init__(self):
|
||||
... self.y = self.x + 1
|
||||
>>> FrozenBroken(1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
attr.exceptions.FrozenInstanceError: can't set attribute
|
||||
|
||||
If you need to set attributes on a frozen class, you'll have to resort to the :ref:`same trick <how-frozen>` as ``attrs`` and use :meth:`object.__setattr__`:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> @attr.s(frozen=True)
|
||||
... class Frozen(object):
|
||||
... x = attr.ib()
|
||||
... y = attr.ib(init=False)
|
||||
... def __attrs_post_init__(self):
|
||||
... object.__setattr__(self, "y", self.x + 1)
|
||||
>>> Frozen(1)
|
||||
Frozen(x=1, y=2)
|
||||
y = attr.ib() # <- ERROR!
|
||||
|
||||
|
||||
.. _helpers:
|
||||
|
@ -210,12 +161,29 @@ Helpers
|
|||
... x = attr.ib()
|
||||
... y = attr.ib()
|
||||
>>> attr.fields(C)
|
||||
(Attribute(name='x', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}), type=None), Attribute(name='y', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}), type=None))
|
||||
(Attribute(name='x', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None), Attribute(name='y', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None))
|
||||
>>> attr.fields(C)[1]
|
||||
Attribute(name='y', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}), type=None)
|
||||
Attribute(name='y', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None)
|
||||
>>> attr.fields(C).y is attr.fields(C)[1]
|
||||
True
|
||||
|
||||
.. autofunction:: attr.fields_dict
|
||||
|
||||
For example:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> @attr.s
|
||||
... class C(object):
|
||||
... x = attr.ib()
|
||||
... y = attr.ib()
|
||||
>>> attr.fields_dict(C)
|
||||
{'x': Attribute(name='x', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None), 'y': Attribute(name='y', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None)}
|
||||
>>> attr.fields_dict(C)['y']
|
||||
Attribute(name='y', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None)
|
||||
>>> attr.fields_dict(C)['y'] is attr.fields(C).y
|
||||
True
|
||||
|
||||
|
||||
.. autofunction:: attr.has
|
||||
|
||||
|
@ -412,7 +380,7 @@ Converters
|
|||
|
||||
>>> @attr.s
|
||||
... class C(object):
|
||||
... x = attr.ib(convert=attr.converters.optional(int))
|
||||
... x = attr.ib(converter=attr.converters.optional(int))
|
||||
>>> C(None)
|
||||
C(x=None)
|
||||
>>> C(42)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.. _contributing:
|
||||
|
||||
.. include:: ../CONTRIBUTING.rst
|
||||
.. include:: ../.github/CONTRIBUTING.rst
|
||||
|
||||
.. include:: ../CODE_OF_CONDUCT.rst
|
||||
.. include:: ../.github/CODE_OF_CONDUCT.rst
|
||||
|
|
|
@ -115,7 +115,7 @@ This is useful in times when you want to enhance classes that are not yours (nic
|
|||
... class B(object):
|
||||
... b = attr.ib()
|
||||
>>> @attr.s
|
||||
... class C(B, A):
|
||||
... class C(A, B):
|
||||
... c = attr.ib()
|
||||
>>> i = C(1, 2, 3)
|
||||
>>> i
|
||||
|
@ -218,8 +218,6 @@ Other times, all you want is a tuple and ``attrs`` won't let you down:
|
|||
True
|
||||
|
||||
|
||||
|
||||
|
||||
Defaults
|
||||
--------
|
||||
|
||||
|
@ -282,6 +280,16 @@ The method receives the partially initialized instance which enables you to base
|
|||
C(x=1, y=2)
|
||||
|
||||
|
||||
And since the case of ``attr.ib(default=attr.Factory(f))`` is so common, ``attrs`` also comes with syntactic sugar for it:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> @attr.s
|
||||
... class C(object):
|
||||
... x = attr.ib(factory=list)
|
||||
>>> C()
|
||||
C(x=[])
|
||||
|
||||
.. _examples_validators:
|
||||
|
||||
Validators
|
||||
|
@ -291,18 +299,7 @@ Although your initializers should do as little as possible (ideally: just initia
|
|||
|
||||
``attrs`` offers two ways to define validators for each attribute and it's up to you to choose which one suites better your style and project.
|
||||
|
||||
|
||||
Decorator
|
||||
~~~~~~~~~
|
||||
|
||||
The more straightforward way is by using the attribute's ``validator`` method as a decorator.
|
||||
The method has to accept three arguments:
|
||||
|
||||
#. the *instance* that's being validated (aka ``self``),
|
||||
#. the *attribute* that it's validating, and finally
|
||||
#. the *value* that is passed for it.
|
||||
|
||||
If the value does not pass the validator's standards, it just raises an appropriate exception.
|
||||
You can use a decorator:
|
||||
|
||||
.. doctest::
|
||||
|
||||
|
@ -320,15 +317,7 @@ If the value does not pass the validator's standards, it just raises an appropri
|
|||
...
|
||||
ValueError: x must be smaller or equal to 42
|
||||
|
||||
|
||||
Callables
|
||||
~~~~~~~~~
|
||||
|
||||
If you want to re-use your validators, you should have a look at the ``validator`` argument to :func:`attr.ib()`.
|
||||
|
||||
It takes either a callable or a list of callables (usually functions) and treats them as validators that receive the same arguments as with the decorator approach.
|
||||
|
||||
Since the validators runs *after* the instance is initialized, you can refer to other attributes while validating:
|
||||
...or a callable...
|
||||
|
||||
.. doctest::
|
||||
|
||||
|
@ -347,18 +336,28 @@ Since the validators runs *after* the instance is initialized, you can refer to
|
|||
...
|
||||
ValueError: 'x' has to be smaller than 'y'!
|
||||
|
||||
This example also shows of some syntactic sugar for using the :func:`attr.validators.and_` validator: if you pass a list, all validators have to pass.
|
||||
|
||||
``attrs`` won't intercept your changes to those attributes but you can always call :func:`attr.validate` on any instance to verify that it's still valid:
|
||||
...or both at once:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> i = C(4, 5)
|
||||
>>> i.x = 5 # works, no magic here
|
||||
>>> attr.validate(i)
|
||||
>>> @attr.s
|
||||
... class C(object):
|
||||
... x = attr.ib(validator=attr.validators.instance_of(int))
|
||||
... @x.validator
|
||||
... def fits_byte(self, attribute, value):
|
||||
... if not 0 <= value < 256:
|
||||
... raise ValueError("value out of bounds")
|
||||
>>> C(128)
|
||||
C(x=128)
|
||||
>>> C("128")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: 'x' has to be smaller than 'y'!
|
||||
TypeError: ("'x' must be <class 'int'> (got '128' that is a <class 'str'>).", Attribute(name='x', default=NOTHING, validator=[<instance_of validator for type <class 'int'>>, <function fits_byte at 0x10fd7a0d0>], repr=True, cmp=True, hash=True, init=True, metadata=mappingproxy({}), type=None, converter=one), <class 'int'>, '128')
|
||||
>>> C(256)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: value out of bounds
|
||||
|
||||
|
||||
``attrs`` ships with a bunch of validators, make sure to :ref:`check them out <api_validators>` before writing your own:
|
||||
|
||||
|
@ -374,72 +373,25 @@ This example also shows of some syntactic sugar for using the :func:`attr.valida
|
|||
...
|
||||
TypeError: ("'x' must be <type 'int'> (got '42' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, factory=NOTHING, validator=<instance_of validator for type <type 'int'>>, type=None), <type 'int'>, '42')
|
||||
|
||||
Of course you can mix and match the two approaches at your convenience:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> @attr.s
|
||||
... class C(object):
|
||||
... x = attr.ib(validator=attr.validators.instance_of(int))
|
||||
... @x.validator
|
||||
... def fits_byte(self, attribute, value):
|
||||
... if not 0 < value < 256:
|
||||
... raise ValueError("value out of bounds")
|
||||
>>> C(128)
|
||||
C(x=128)
|
||||
>>> C("128")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: ("'x' must be <class 'int'> (got '128' that is a <class 'str'>).", Attribute(name='x', default=NOTHING, validator=[<instance_of validator for type <class 'int'>>, <function fits_byte at 0x10fd7a0d0>], repr=True, cmp=True, hash=True, init=True, convert=None, metadata=mappingproxy({}), type=None), <class 'int'>, '128')
|
||||
>>> C(256)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: value out of bounds
|
||||
|
||||
And finally you can disable validators globally:
|
||||
|
||||
>>> attr.set_run_validators(False)
|
||||
>>> C("128")
|
||||
C(x='128')
|
||||
>>> attr.set_run_validators(True)
|
||||
>>> C("128")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: ("'x' must be <class 'int'> (got '128' that is a <class 'str'>).", Attribute(name='x', default=NOTHING, validator=[<instance_of validator for type <class 'int'>>, <function fits_byte at 0x10fd7a0d0>], repr=True, cmp=True, hash=True, init=True, convert=None, metadata=mappingproxy({}), type=None), <class 'int'>, '128')
|
||||
Check out :ref:`validators` for more details.
|
||||
|
||||
|
||||
Conversion
|
||||
----------
|
||||
|
||||
Attributes can have a ``convert`` function specified, which will be called with the attribute's passed-in value to get a new value to use.
|
||||
Attributes can have a ``converter`` function specified, which will be called with the attribute's passed-in value to get a new value to use.
|
||||
This can be useful for doing type-conversions on values that you don't want to force your callers to do.
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> @attr.s
|
||||
... class C(object):
|
||||
... x = attr.ib(convert=int)
|
||||
... x = attr.ib(converter=int)
|
||||
>>> o = C("1")
|
||||
>>> o.x
|
||||
1
|
||||
|
||||
Converters are run *before* validators, so you can use validators to check the final form of the value.
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> def validate_x(instance, attribute, value):
|
||||
... if value < 0:
|
||||
... raise ValueError("x must be be at least 0.")
|
||||
>>> @attr.s
|
||||
... class C(object):
|
||||
... x = attr.ib(convert=int, validator=validate_x)
|
||||
>>> o = C("0")
|
||||
>>> o.x
|
||||
0
|
||||
>>> C("-1")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: x must be be at least 0.
|
||||
Check out :ref:`converters` for more details.
|
||||
|
||||
|
||||
.. _metadata:
|
||||
|
@ -509,6 +461,7 @@ If you don't mind annotating *all* attributes, you can even drop the :func:`attr
|
|||
>>> AutoC.cls_var
|
||||
5
|
||||
|
||||
The generated ``__init__`` method will have an attribute called ``__annotations__`` that contains this type information.
|
||||
|
||||
.. warning::
|
||||
|
||||
|
@ -521,12 +474,8 @@ If you don't mind annotating *all* attributes, you can even drop the :func:`attr
|
|||
Slots
|
||||
-----
|
||||
|
||||
By default, instances of classes have a dictionary for attribute storage.
|
||||
This wastes space for objects having very few data attributes.
|
||||
The space consumption can become significant when creating large numbers of instances.
|
||||
|
||||
Normal Python classes can avoid using a separate dictionary for each instance of a class by `defining <https://docs.python.org/3/reference/datamodel.html#slots>`_ ``__slots__``.
|
||||
For ``attrs`` classes it's enough to set ``slots=True``:
|
||||
:term:`Slotted classes` have a bunch of advantages on CPython.
|
||||
Defining ``__slots__`` by hand is tedious, in ``attrs`` it's just a matter of passing ``slots=True``:
|
||||
|
||||
.. doctest::
|
||||
|
||||
|
@ -536,59 +485,6 @@ For ``attrs`` classes it's enough to set ``slots=True``:
|
|||
... y = attr.ib()
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
``attrs`` slot classes can inherit from other classes just like non-slot classes, but some of the benefits of slot classes are lost if you do that.
|
||||
If you must inherit from other classes, try to inherit only from other slot classes.
|
||||
|
||||
Slot classes are a little different than ordinary, dictionary-backed classes:
|
||||
|
||||
- Assigning to a non-existent attribute of an instance will result in an ``AttributeError`` being raised.
|
||||
Depending on your needs, this might be a good thing since it will let you catch typos early.
|
||||
This is not the case if your class inherits from any non-slot classes.
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> @attr.s(slots=True)
|
||||
... class Coordinates(object):
|
||||
... x = attr.ib()
|
||||
... y = attr.ib()
|
||||
...
|
||||
>>> c = Coordinates(x=1, y=2)
|
||||
>>> c.z = 3
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Coordinates' object has no attribute 'z'
|
||||
|
||||
- Since non-slot classes cannot be turned into slot classes after they have been created, ``attr.s(slots=True)`` will *replace* the class it is applied to with a copy.
|
||||
In almost all cases this isn't a problem, but we mention it for the sake of completeness.
|
||||
|
||||
* One notable problem is that certain metaclass features like ``__init_subclass__`` do not work with slot classes.
|
||||
|
||||
- Using :mod:`pickle` with slot classes requires pickle protocol 2 or greater.
|
||||
Python 2 uses protocol 0 by default so the protocol needs to be specified.
|
||||
Python 3 uses protocol 3 by default.
|
||||
You can support protocol 0 and 1 by implementing :meth:`__getstate__ <object.__getstate__>` and :meth:`__setstate__ <object.__setstate__>` methods yourself.
|
||||
Those methods are created for frozen slot classes because they won't pickle otherwise.
|
||||
`Think twice <https://www.youtube.com/watch?v=7KnfGDajDQw>`_ before using :mod:`pickle` though.
|
||||
|
||||
- As always with slot classes, you must specify a ``__weakref__`` slot if you wish for the class to be weak-referenceable.
|
||||
Here's how it looks using ``attrs``:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> import weakref
|
||||
>>> @attr.s(slots=True)
|
||||
... class C(object):
|
||||
... __weakref__ = attr.ib(init=False, hash=False, repr=False, cmp=False)
|
||||
... x = attr.ib()
|
||||
>>> c = C(1)
|
||||
>>> weakref.ref(c)
|
||||
<weakref at 0x...; to 'C' at 0x...>
|
||||
|
||||
All in all, setting ``slots=True`` is usually a very good idea.
|
||||
|
||||
|
||||
Immutability
|
||||
------------
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ So it is fairly simple to build your own decorators on top of ``attrs``:
|
|||
... @attr.s
|
||||
... class C(object):
|
||||
... a = attr.ib()
|
||||
(Attribute(name='a', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, convert=None, metadata=mappingproxy({}), type=None),)
|
||||
(Attribute(name='a', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None),)
|
||||
|
||||
|
||||
.. warning::
|
||||
|
|
63
tests/wpt/web-platform-tests/tools/third_party/attrs/docs/glossary.rst
vendored
Normal file
63
tests/wpt/web-platform-tests/tools/third_party/attrs/docs/glossary.rst
vendored
Normal file
|
@ -0,0 +1,63 @@
|
|||
Glossary
|
||||
========
|
||||
|
||||
.. glossary::
|
||||
|
||||
dict classes
|
||||
A regular class whose attributes are stored in the ``__dict__`` attribute of every single instance.
|
||||
This is quite wasteful especially for objects with very few data attributes and the space consumption can become significant when creating large numbers of instances.
|
||||
|
||||
This is the type of class you get by default both with and without ``attrs``.
|
||||
|
||||
slotted classes
|
||||
A class that has no ``__dict__`` attribute and `defines <https://docs.python.org/3/reference/datamodel.html#slots>`_ its attributes in a ``__slots__`` attribute instead.
|
||||
In ``attrs``, they are created by passing ``slots=True`` to ``@attr.s``.
|
||||
|
||||
Their main advantage is that they use less memory on CPython [#pypy]_.
|
||||
|
||||
However they also come with a bunch of possibly surprising gotchas:
|
||||
|
||||
- Slotted classes don't allow for any other attribute to be set except for those defined in one of the class' hierarchies ``__slots__``:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> import attr
|
||||
>>> @attr.s(slots=True)
|
||||
... class Coordinates(object):
|
||||
... x = attr.ib()
|
||||
... y = attr.ib()
|
||||
...
|
||||
>>> c = Coordinates(x=1, y=2)
|
||||
>>> c.z = 3
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'Coordinates' object has no attribute 'z'
|
||||
|
||||
- Slotted classes can inherit from other classes just like non-slotted classes, but some of the benefits of slotted classes are lost if you do that.
|
||||
If you must inherit from other classes, try to inherit only from other slot classes.
|
||||
|
||||
- Using :mod:`pickle` with slotted classes requires pickle protocol 2 or greater.
|
||||
Python 2 uses protocol 0 by default so the protocol needs to be specified.
|
||||
Python 3 uses protocol 3 by default.
|
||||
You can support protocol 0 and 1 by implementing :meth:`__getstate__ <object.__getstate__>` and :meth:`__setstate__ <object.__setstate__>` methods yourself.
|
||||
Those methods are created for frozen slotted classes because they won't pickle otherwise.
|
||||
`Think twice <https://www.youtube.com/watch?v=7KnfGDajDQw>`_ before using :mod:`pickle` though.
|
||||
|
||||
- As always with slotted classes, you must specify a ``__weakref__`` slot if you wish for the class to be weak-referenceable.
|
||||
Here's how it looks using ``attrs``:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> import weakref
|
||||
>>> @attr.s(slots=True)
|
||||
... class C(object):
|
||||
... __weakref__ = attr.ib(init=False, hash=False, repr=False, cmp=False)
|
||||
... x = attr.ib()
|
||||
>>> c = C(1)
|
||||
>>> weakref.ref(c)
|
||||
<weakref at 0x...; to 'C' at 0x...>
|
||||
- Since it's currently impossible to make a class slotted after it's created, ``attrs`` has to replace your class with a new one.
|
||||
While it tries to do that as graciously as possible, certain metaclass features like ``__init_subclass__`` do not work with slotted classes.
|
||||
|
||||
|
||||
.. [#pypy] On PyPy, there is no memory advantage in using slotted classes.
|
56
tests/wpt/web-platform-tests/tools/third_party/attrs/docs/hashing.rst
vendored
Normal file
56
tests/wpt/web-platform-tests/tools/third_party/attrs/docs/hashing.rst
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
Hashing
|
||||
=======
|
||||
|
||||
.. warning::
|
||||
|
||||
The overarching theme is to never set the ``@attr.s(hash=X)`` parameter yourself.
|
||||
Leave it at ``None`` which means that ``attrs`` will do the right thing for you, depending on the other parameters:
|
||||
|
||||
- If you want to make objects hashable by value: use ``@attr.s(frozen=True)``.
|
||||
- If you want hashing and comparison by object identity: use ``@attr.s(cmp=False)``
|
||||
|
||||
Setting ``hash`` yourself can have unexpected consequences so we recommend to tinker with it only if you know exactly what you're doing.
|
||||
|
||||
Under certain circumstances, it's necessary for objects to be *hashable*.
|
||||
For example if you want to put them into a :class:`set` or if you want to use them as keys in a :class:`dict`.
|
||||
|
||||
The *hash* of an object is an integer that represents the contents of an object.
|
||||
It can be obtained by calling :func:`hash` on an object and is implemented by writing a ``__hash__`` method for your class.
|
||||
|
||||
``attrs`` will happily write a ``__hash__`` method you [#fn1]_, however it will *not* do so by default.
|
||||
Because according to the definition_ from the official Python docs, the returned hash has to fullfil certain constraints:
|
||||
|
||||
#. Two objects that are equal, **must** have the same hash.
|
||||
This means that if ``x == y``, it *must* follow that ``hash(x) == hash(y)``.
|
||||
|
||||
By default, Python classes are compared *and* hashed by their :func:`id`.
|
||||
That means that every instance of a class has a different hash, no matter what attributes it carries.
|
||||
|
||||
It follows that the moment you (or ``attrs``) change the way equality is handled by implementing ``__eq__`` which is based on attribute values, this constraint is broken.
|
||||
For that reason Python 3 will make a class that has customized equality unhashable.
|
||||
Python 2 on the other hand will happily let you shoot your foot off.
|
||||
Unfortunately ``attrs`` currently mimics Python 2's behavior for backward compatibility reasons if you set ``hash=False``.
|
||||
|
||||
The *correct way* to achieve hashing by id is to set ``@attr.s(cmp=False)``.
|
||||
Setting ``@attr.s(hash=False)`` (that implies ``cmp=True``) is almost certainly a *bug*.
|
||||
|
||||
#. If two object are not equal, their hash **should** be different.
|
||||
|
||||
While this isn't a requirement from a standpoint of correctness, sets and dicts become less effective if there are a lot of identical hashes.
|
||||
The worst case is when all objects have the same hash which turns a set into a list.
|
||||
|
||||
#. The hash of an object **must not** change.
|
||||
|
||||
If you create a class with ``@attr.s(frozen=True)`` this is fullfilled by definition, therefore ``attrs`` will write a ``__hash__`` function for you automatically.
|
||||
You can also force it to write one with ``hash=True`` but then it's *your* responsibility to make sure that the object is not mutated.
|
||||
|
||||
This point is the reason why mutable structures like lists, dictionaries, or sets aren't hashable while immutable ones like tuples or frozensets are:
|
||||
point 1 and 2 require that the hash changes with the contents but point 3 forbids it.
|
||||
|
||||
For a more thorough explanation of this topic, please refer to this blog post: `Python Hashes and Equality`_.
|
||||
|
||||
|
||||
.. [#fn1] The hash is computed by hashing a tuple that consists of an unique id for the class plus all attribute values.
|
||||
|
||||
.. _definition: https://docs.python.org/3/glossary.html#term-hashable
|
||||
.. _`Python Hashes and Equality`: https://hynek.me/articles/hashes-and-equality/
|
|
@ -17,7 +17,7 @@ In order to ensure that sub-classing works as you'd expect it to work, ``attrs``
|
|||
Please note that ``attrs`` does *not* call ``super()`` *ever*.
|
||||
It will write dunder methods to work on *all* of those attributes which also has performance benefits due to fewer function calls.
|
||||
|
||||
Once ``attrs`` knows what attributes it has to work on, it writes the requested dunder methods and -- depending on whether you wish to have ``__slots__`` -- creates a new class for you (``slots=True``) or attaches them to the original class (``slots=False``).
|
||||
Once ``attrs`` knows what attributes it has to work on, it writes the requested dunder methods and -- depending on whether you wish to have a :term:`dict <dict classes>` or :term:`slotted <slotted classes>` class -- creates a new class for you (``slots=True``) or attaches them to the original class (``slots=False``).
|
||||
While creating new classes is more elegant, we've run into several edge cases surrounding metaclasses that make it impossible to go this route unconditionally.
|
||||
|
||||
To be very clear: if you define a class with a single attribute without a default value, the generated ``__init__`` will look *exactly* how you'd expect:
|
||||
|
@ -53,7 +53,24 @@ Immutability
|
|||
|
||||
In order to give you immutability, ``attrs`` will attach a ``__setattr__`` method to your class that raises a :exc:`attr.exceptions.FrozenInstanceError` whenever anyone tries to set an attribute.
|
||||
|
||||
To circumvent that ourselves in ``__init__``, ``attrs`` uses (an aggressively cached) :meth:`object.__setattr__` to set your attributes.
|
||||
Depending on whether of not a class is a dict class or a slots class, ``attrs`` uses a different technique to circumvent that limitation in the ``__init__`` method.
|
||||
|
||||
Once constructed, frozen instances don't differ in any way from regular ones except that you cannot change its attributes.
|
||||
|
||||
|
||||
Dict Classes
|
||||
++++++++++++
|
||||
|
||||
Dict classes -- i.e. regular classes -- simply assign the value directly into the class' eponymous ``__dict__`` (and there's nothing we can do to stop the user to do the same).
|
||||
|
||||
The performance impact is negligible.
|
||||
|
||||
|
||||
Slots Classes
|
||||
+++++++++++++
|
||||
|
||||
Slots classes are more complicated.
|
||||
Here it uses (an aggressively cached) :meth:`object.__setattr__` to set your attributes.
|
||||
This is (still) slower than a plain assignment:
|
||||
|
||||
.. code-block:: none
|
||||
|
@ -74,6 +91,10 @@ So on a standard notebook the difference is about 300 nanoseconds (1 second is 1
|
|||
It's certainly something you'll feel in a hot loop but shouldn't matter in normal code.
|
||||
Pick what's more important to you.
|
||||
|
||||
****
|
||||
|
||||
Once constructed, frozen instances don't differ in any way from regular ones except that you cannot change its attributes.
|
||||
Summary
|
||||
+++++++
|
||||
|
||||
You should avoid to instantiate lots of frozen slotted classes (i.e. ``@attr.s(slots=True, frozen=True)``) in performance-critical code.
|
||||
|
||||
Frozen dict classes have barely a performance impact, unfrozen slotted classes are even *faster* than unfrozen dict classes (i.e. regular classes).
|
||||
|
|
|
@ -27,6 +27,7 @@ The next three steps should bring you up and running in no time:
|
|||
After reading, you will know about our advanced features and how to use them.
|
||||
- Finally :doc:`why` gives you a rundown of potential alternatives and why we think ``attrs`` is superior.
|
||||
Yes, we've heard about ``namedtuple``\ s!
|
||||
- If at any point you get confused by some terminology, please check out our :doc:`glossary`.
|
||||
|
||||
|
||||
If you need any help while getting started, feel free to use the ``python-attrs`` tag on `StackOverflow <https://stackoverflow.com/questions/tagged/python-attrs>`_ and someone will surely help you out!
|
||||
|
@ -36,6 +37,11 @@ Day-to-Day Usage
|
|||
================
|
||||
|
||||
- Once you're comfortable with the concepts, our :doc:`api` contains all information you need to use ``attrs`` to its fullest.
|
||||
- Instance initialization is one of ``attrs`` key feature areas.
|
||||
Our goal is to relieve you from writing as much code as possible.
|
||||
:doc:`init` gives you an overview what ``attrs`` has to offer and explains some related philosophies we believe in.
|
||||
- If you want to put objects into sets or use them as keys in dictionaries, they have to be hashable.
|
||||
The simplest way to do that is to use frozen classes, but the topic is more complex than it seems and :doc:`hashing` will give you a primer on what to look out for.
|
||||
- ``attrs`` is built for extension from the ground up.
|
||||
:doc:`extending` will show you the affordances it offers and how to make it a building block of your own projects.
|
||||
|
||||
|
@ -68,9 +74,12 @@ Full Table of Contents
|
|||
overview
|
||||
why
|
||||
examples
|
||||
init
|
||||
hashing
|
||||
api
|
||||
extending
|
||||
how-does-it-work
|
||||
glossary
|
||||
|
||||
|
||||
Indices and tables
|
||||
|
|
354
tests/wpt/web-platform-tests/tools/third_party/attrs/docs/init.rst
vendored
Normal file
354
tests/wpt/web-platform-tests/tools/third_party/attrs/docs/init.rst
vendored
Normal file
|
@ -0,0 +1,354 @@
|
|||
Initialization
|
||||
==============
|
||||
|
||||
In Python, instance intialization happens in the ``__init__`` method.
|
||||
Generally speaking, you should keep as little logic as possible in it, and you should think about what the class needs and not how it is going to be instantiated.
|
||||
|
||||
Passing complex objects into ``__init__`` and then using them to derive data for the class unnecessarily couples your new class with the old class which makes it harder to test and also will cause problems later.
|
||||
|
||||
So assuming you use an ORM and want to extract 2D points from a row object, do not write code like this::
|
||||
|
||||
class Point(object):
|
||||
def __init__(self, database_row):
|
||||
self.x = database_row.x
|
||||
self.y = database_row.y
|
||||
|
||||
pt = Point(row)
|
||||
|
||||
Instead, write a :func:`classmethod` that will extract it for you::
|
||||
|
||||
@attr.s
|
||||
class Point(object):
|
||||
x = attr.ib()
|
||||
y = attr.ib()
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row):
|
||||
return cls(row.x, row.y)
|
||||
|
||||
pt = Point.from_row(row)
|
||||
|
||||
Now you can instantiate ``Point``\ s without creating fake row objects in your tests and you can have as many smart creation helpers as you want, in case more data sources appear.
|
||||
|
||||
For similar reasons, we strongly discourage from patterns like::
|
||||
|
||||
pt = Point(**row.attributes)
|
||||
|
||||
which couples your classes to the data model.
|
||||
Try to design your classes in a way that is clean and convenient to use -- not based on your database format.
|
||||
The database format can change anytime and you're stuck with a bad class design that is hard to change.
|
||||
Embrace classmethods as a filter between reality and what's best for you to work with.
|
||||
|
||||
If you look for object serialization, there's a bunch of projects listed on our ``attrs`` extensions `Wiki page`_.
|
||||
Some of them even support nested schemas.
|
||||
|
||||
|
||||
Private Attributes
|
||||
------------------
|
||||
|
||||
One thing people tend to find confusing is the treatment of private attributes that start with an underscore.
|
||||
``attrs`` follows the doctrine that `there is no such thing as a private argument`_ and strips the underscores from the name when writing the ``__init__`` method signature:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> import inspect, attr
|
||||
>>> @attr.s
|
||||
... class C(object):
|
||||
... _x = attr.ib()
|
||||
>>> inspect.signature(C.__init__)
|
||||
<Signature (self, x) -> None>
|
||||
|
||||
There really isn't a right or wrong, it's a matter of taste.
|
||||
But it's important to be aware of it because it can lead to surprising syntax errors:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> @attr.s
|
||||
... class C(object):
|
||||
... _1 = attr.ib()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
SyntaxError: invalid syntax
|
||||
|
||||
In this case a valid attribute name ``_1`` got transformed into an invalid argument name ``1``.
|
||||
|
||||
|
||||
Defaults
|
||||
--------
|
||||
|
||||
Sometimes you don't want to pass all attribute values to a class.
|
||||
And sometimes, certain attributes aren't even intended to be passed but you want to allow for customization anyways for easier testing.
|
||||
|
||||
This is when default values come into play:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> import attr
|
||||
>>> @attr.s
|
||||
... class C(object):
|
||||
... a = attr.ib(default=42)
|
||||
... b = attr.ib(default=attr.Factory(list))
|
||||
... c = attr.ib(factory=list) # syntactic sugar for above
|
||||
... d = attr.ib()
|
||||
... @d.default
|
||||
... def _any_name_except_a_name_of_an_attribute(self):
|
||||
... return {}
|
||||
>>> C()
|
||||
C(a=42, b=[], c=[], d={})
|
||||
|
||||
It's important that the decorated method -- or any other method or property! -- doesn't have the same name as the attribute, otherwise it would overwrite the attribute definition.
|
||||
|
||||
|
||||
Please note that as with function and method signatures, ``default=[]`` will *not* do what you may think it might do:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> @attr.s
|
||||
... class C(object):
|
||||
... x = attr.ib(default=[])
|
||||
>>> i = C()
|
||||
>>> j = C()
|
||||
>>> i.x.append(42)
|
||||
>>> j.x
|
||||
[42]
|
||||
|
||||
|
||||
This is why ``attrs`` comes with factory options.
|
||||
|
||||
.. warning::
|
||||
|
||||
Please note that the decorator based defaults have one gotcha:
|
||||
they are executed when the attribute is set, that means depending on the order of attributes, the ``self`` object may not be fully initialized when they're called.
|
||||
|
||||
Therefore you should use ``self`` as little as possible.
|
||||
|
||||
Even the smartest of us can `get confused`_ by what happens if you pass partially initialized objects around.
|
||||
|
||||
|
||||
.. _validators:
|
||||
|
||||
Validators
|
||||
----------
|
||||
|
||||
Another thing that definitely *does* belong into ``__init__`` is checking the resulting instance for invariants.
|
||||
This is why ``attrs`` has the concept of validators.
|
||||
|
||||
|
||||
Decorator
|
||||
~~~~~~~~~
|
||||
|
||||
The most straightforward way is using the attribute's ``validator`` method as a decorator.
|
||||
|
||||
The method has to accept three arguments:
|
||||
|
||||
#. the *instance* that's being validated (aka ``self``),
|
||||
#. the *attribute* that it's validating, and finally
|
||||
#. the *value* that is passed for it.
|
||||
|
||||
If the value does not pass the validator's standards, it just raises an appropriate exception.
|
||||
|
||||
>>> @attr.s
|
||||
... class C(object):
|
||||
... x = attr.ib()
|
||||
... @x.validator
|
||||
... def _check_x(self, attribute, value):
|
||||
... if value > 42:
|
||||
... raise ValueError("x must be smaller or equal to 42")
|
||||
>>> C(42)
|
||||
C(x=42)
|
||||
>>> C(43)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: x must be smaller or equal to 42
|
||||
|
||||
Again, it's important that the decorated method doesn't have the same name as the attribute.
|
||||
|
||||
|
||||
Callables
|
||||
~~~~~~~~~
|
||||
|
||||
If you want to re-use your validators, you should have a look at the ``validator`` argument to :func:`attr.ib()`.
|
||||
|
||||
It takes either a callable or a list of callables (usually functions) and treats them as validators that receive the same arguments as with the decorator approach.
|
||||
|
||||
Since the validators runs *after* the instance is initialized, you can refer to other attributes while validating:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> def x_smaller_than_y(instance, attribute, value):
|
||||
... if value >= instance.y:
|
||||
... raise ValueError("'x' has to be smaller than 'y'!")
|
||||
>>> @attr.s
|
||||
... class C(object):
|
||||
... x = attr.ib(validator=[attr.validators.instance_of(int),
|
||||
... x_smaller_than_y])
|
||||
... y = attr.ib()
|
||||
>>> C(x=3, y=4)
|
||||
C(x=3, y=4)
|
||||
>>> C(x=4, y=3)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: 'x' has to be smaller than 'y'!
|
||||
|
||||
This example also shows of some syntactic sugar for using the :func:`attr.validators.and_` validator: if you pass a list, all validators have to pass.
|
||||
|
||||
``attrs`` won't intercept your changes to those attributes but you can always call :func:`attr.validate` on any instance to verify that it's still valid:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> i = C(4, 5)
|
||||
>>> i.x = 5 # works, no magic here
|
||||
>>> attr.validate(i)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: 'x' has to be smaller than 'y'!
|
||||
|
||||
``attrs`` ships with a bunch of validators, make sure to :ref:`check them out <api_validators>` before writing your own:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> @attr.s
|
||||
... class C(object):
|
||||
... x = attr.ib(validator=attr.validators.instance_of(int))
|
||||
>>> C(42)
|
||||
C(x=42)
|
||||
>>> C("42")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: ("'x' must be <type 'int'> (got '42' that is a <type 'str'>).", Attribute(name='x', default=NOTHING, factory=NOTHING, validator=<instance_of validator for type <type 'int'>>, type=None), <type 'int'>, '42')
|
||||
|
||||
Of course you can mix and match the two approaches at your convenience.
|
||||
If you define validators both ways for an attribute, they are both ran:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> @attr.s
|
||||
... class C(object):
|
||||
... x = attr.ib(validator=attr.validators.instance_of(int))
|
||||
... @x.validator
|
||||
... def fits_byte(self, attribute, value):
|
||||
... if not 0 <= value < 256:
|
||||
... raise ValueError("value out of bounds")
|
||||
>>> C(128)
|
||||
C(x=128)
|
||||
>>> C("128")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: ("'x' must be <class 'int'> (got '128' that is a <class 'str'>).", Attribute(name='x', default=NOTHING, validator=[<instance_of validator for type <class 'int'>>, <function fits_byte at 0x10fd7a0d0>], repr=True, cmp=True, hash=True, init=True, metadata=mappingproxy({}), type=None, converter=one), <class 'int'>, '128')
|
||||
>>> C(256)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: value out of bounds
|
||||
|
||||
And finally you can disable validators globally:
|
||||
|
||||
>>> attr.set_run_validators(False)
|
||||
>>> C("128")
|
||||
C(x='128')
|
||||
>>> attr.set_run_validators(True)
|
||||
>>> C("128")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: ("'x' must be <class 'int'> (got '128' that is a <class 'str'>).", Attribute(name='x', default=NOTHING, validator=[<instance_of validator for type <class 'int'>>, <function fits_byte at 0x10fd7a0d0>], repr=True, cmp=True, hash=True, init=True, metadata=mappingproxy({}), type=None, converter=None), <class 'int'>, '128')
|
||||
|
||||
|
||||
.. _converters:
|
||||
|
||||
Converters
|
||||
----------
|
||||
|
||||
Finally, sometimes you may want to normalize the values coming in.
|
||||
For that ``attrs`` comes with converters.
|
||||
|
||||
Attributes can have a ``converter`` function specified, which will be called with the attribute's passed-in value to get a new value to use.
|
||||
This can be useful for doing type-conversions on values that you don't want to force your callers to do.
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> @attr.s
|
||||
... class C(object):
|
||||
... x = attr.ib(converter=int)
|
||||
>>> o = C("1")
|
||||
>>> o.x
|
||||
1
|
||||
|
||||
Converters are run *before* validators, so you can use validators to check the final form of the value.
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> def validate_x(instance, attribute, value):
|
||||
... if value < 0:
|
||||
... raise ValueError("x must be at least 0.")
|
||||
>>> @attr.s
|
||||
... class C(object):
|
||||
... x = attr.ib(converter=int, validator=validate_x)
|
||||
>>> o = C("0")
|
||||
>>> o.x
|
||||
0
|
||||
>>> C("-1")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: x must be at least 0.
|
||||
|
||||
|
||||
Arguably, you can abuse converters as one-argument validators:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> C("x")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: invalid literal for int() with base 10: 'x'
|
||||
|
||||
|
||||
Post-Init Hook
|
||||
--------------
|
||||
|
||||
Generally speaking, the moment you think that you need finer control over how your class is instantiated than what ``attrs`` offers, it's usually best to use a classmethod factory or to apply the `builder pattern <https://en.wikipedia.org/wiki/Builder_pattern>`_.
|
||||
|
||||
However, sometimes you need to do that one quick thing after your class is initialized.
|
||||
And for that ``attrs`` offers the ``__attrs_post_init__`` hook that is automatically detected and run after ``attrs`` is done initializing your instance:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> @attr.s
|
||||
... class C(object):
|
||||
... x = attr.ib()
|
||||
... y = attr.ib(init=False)
|
||||
... def __attrs_post_init__(self):
|
||||
... self.y = self.x + 1
|
||||
>>> C(1)
|
||||
C(x=1, y=2)
|
||||
|
||||
Please note that you can't directly set attributes on frozen classes:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> @attr.s(frozen=True)
|
||||
... class FrozenBroken(object):
|
||||
... x = attr.ib()
|
||||
... y = attr.ib(init=False)
|
||||
... def __attrs_post_init__(self):
|
||||
... self.y = self.x + 1
|
||||
>>> FrozenBroken(1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
attr.exceptions.FrozenInstanceError: can't set attribute
|
||||
|
||||
If you need to set attributes on a frozen class, you'll have to resort to the :ref:`same trick <how-frozen>` as ``attrs`` and use :meth:`object.__setattr__`:
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> @attr.s(frozen=True)
|
||||
... class Frozen(object):
|
||||
... x = attr.ib()
|
||||
... y = attr.ib(init=False)
|
||||
... def __attrs_post_init__(self):
|
||||
... object.__setattr__(self, "y", self.x + 1)
|
||||
>>> Frozen(1)
|
||||
Frozen(x=1, y=2)
|
||||
|
||||
|
||||
.. _`Wiki page`: https://github.com/python-attrs/attrs/wiki/Extensions-to-attrs
|
||||
.. _`get confused`: https://github.com/python-attrs/attrs/issues/289
|
||||
.. _`there is no such thing as a private argument`: https://github.com/hynek/characteristic/issues/6
|
|
@ -135,6 +135,28 @@ With ``attrs`` your users won't notice a difference because it creates regular,
|
|||
.. _behaving like a tuple: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences
|
||||
|
||||
|
||||
…Data Classes?
|
||||
--------------
|
||||
|
||||
`PEP 557 <https://www.python.org/dev/peps/pep-0557/>`_ added Data Classes to `Python 3.7 <https://docs.python.org/3.7/whatsnew/3.7.html#pep-557-data-classes>`_ that resemble ``attrs`` in many ways.
|
||||
|
||||
They are the result of the Python community's `wish <https://mail.python.org/pipermail/python-ideas/2017-May/045618.html>`_ to have an easier way to write classes in the standard library that doesn't carry the problems of ``namedtuple``\ s.
|
||||
To that end, ``attrs`` and its developers were involved in the PEP process and while we may disagree with some minor decisions that have been made, it's a fine library and if it stops you from abusing ``namedtuple``\ s, they are a huge win.
|
||||
|
||||
Nevertheless, there are still reasons to prefer ``attrs`` over Data Classes whose relevancy depends on your circumstances:
|
||||
|
||||
- ``attrs`` supports all maintream Python versions, including CPython 2.7 and PyPy.
|
||||
- Data Classes are intentionally less powerful than ``attrs``.
|
||||
There is a long list of features that were sacrificed for the sake of simplicity and while the most obvious ones are validators, converters, and ``__slots__``, 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`` can and will move faster.
|
||||
We are not bound to any release schedules and we have a clear deprecation policy.
|
||||
|
||||
One of the `reasons <https://www.python.org/dev/peps/pep-0557/#why-not-just-use-attrs>`_ to not vendor ``attrs`` in the standard library was to not impede ``attrs``'s future developement.
|
||||
|
||||
|
||||
|
||||
…dicts?
|
||||
-------
|
||||
|
||||
|
@ -143,8 +165,6 @@ Dictionaries are not for fixed fields.
|
|||
If you have a dict, it maps something to something else.
|
||||
You should be able to add and remove values.
|
||||
|
||||
|
||||
|
||||
``attrs`` lets you be specific about those expectations; a dictionary does not.
|
||||
It gives you a named entity (the class) in your code, which lets you explain in other places whether you take a parameter of that class or return a value of that class.
|
||||
|
||||
|
@ -155,7 +175,7 @@ So if you never iterate over the keys of a dict, you should use a proper class.
|
|||
…hand-written classes?
|
||||
----------------------
|
||||
|
||||
While we're fans of all things artisanal, writing the same nine methods all over again doesn't qualify for me.
|
||||
While we're fans of all things artisanal, writing the same nine methods again and again doesn't qualify.
|
||||
I usually manage to get some typos inside and there's simply more code that can break and thus has to be tested.
|
||||
|
||||
To bring it into perspective, the equivalent of
|
||||
|
@ -169,7 +189,7 @@ To bring it into perspective, the equivalent of
|
|||
>>> SmartClass(1, 2)
|
||||
SmartClass(a=1, b=2)
|
||||
|
||||
is
|
||||
is roughly
|
||||
|
||||
.. doctest::
|
||||
|
||||
|
@ -219,7 +239,7 @@ is
|
|||
... return NotImplemented
|
||||
...
|
||||
... def __hash__(self):
|
||||
... return hash((self.a, self.b))
|
||||
... return hash((self.__class__, self.a, self.b))
|
||||
>>> ArtisanalClass(a=1, b=2)
|
||||
ArtisanalClass(a=1, b=2)
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
package = "attr"
|
||||
package_dir = "src"
|
||||
filename = "CHANGELOG.rst"
|
||||
template = "changelog.d/towncrier_template.rst"
|
||||
issue_format = "`#{issue} <https://github.com/python-attrs/attrs/issues/{issue}>`_"
|
||||
directory = "changelog.d"
|
||||
title_format = "{version} ({project_date})"
|
||||
|
|
|
@ -24,11 +24,27 @@ CLASSIFIERS = [
|
|||
"Programming Language :: Python :: 3.4",
|
||||
"Programming Language :: Python :: 3.5",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
]
|
||||
INSTALL_REQUIRES = []
|
||||
EXTRAS_REQUIRE = {
|
||||
"docs": [
|
||||
"sphinx",
|
||||
"zope.interface",
|
||||
],
|
||||
"tests": [
|
||||
"coverage",
|
||||
"hypothesis",
|
||||
"pympler",
|
||||
"pytest",
|
||||
"six",
|
||||
"zope.interface",
|
||||
],
|
||||
}
|
||||
EXTRAS_REQUIRE["dev"] = EXTRAS_REQUIRE["tests"] + EXTRAS_REQUIRE["docs"]
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
@ -92,4 +108,5 @@ if __name__ == "__main__":
|
|||
zip_safe=False,
|
||||
classifiers=CLASSIFIERS,
|
||||
install_requires=INSTALL_REQUIRES,
|
||||
extras_require=EXTRAS_REQUIRE,
|
||||
)
|
||||
|
|
|
@ -6,11 +6,12 @@ from . import converters, exceptions, filters, validators
|
|||
from ._config import get_run_validators, set_run_validators
|
||||
from ._funcs import asdict, assoc, astuple, evolve, has
|
||||
from ._make import (
|
||||
NOTHING, Attribute, Factory, attrib, attrs, fields, make_class, validate
|
||||
NOTHING, Attribute, Factory, attrib, attrs, fields, fields_dict,
|
||||
make_class, validate
|
||||
)
|
||||
|
||||
|
||||
__version__ = "17.4.0.dev0"
|
||||
__version__ = "18.1.0"
|
||||
|
||||
__title__ = "attrs"
|
||||
__description__ = "Classes Without Boilerplate"
|
||||
|
@ -43,6 +44,7 @@ __all__ = [
|
|||
"evolve",
|
||||
"exceptions",
|
||||
"fields",
|
||||
"fields_dict",
|
||||
"filters",
|
||||
"get_run_validators",
|
||||
"has",
|
||||
|
|
|
@ -10,6 +10,13 @@ PY2 = sys.version_info[0] == 2
|
|||
PYPY = platform.python_implementation() == "PyPy"
|
||||
|
||||
|
||||
if PYPY or sys.version_info[:2] >= (3, 6):
|
||||
ordered_dict = dict
|
||||
else:
|
||||
from collections import OrderedDict
|
||||
ordered_dict = OrderedDict
|
||||
|
||||
|
||||
if PY2:
|
||||
from UserDict import IterableUserDict
|
||||
|
||||
|
@ -87,15 +94,12 @@ else:
|
|||
return types.MappingProxyType(dict(d))
|
||||
|
||||
|
||||
def import_ctypes(): # pragma: nocover
|
||||
def import_ctypes():
|
||||
"""
|
||||
Moved into a function for testability.
|
||||
"""
|
||||
try:
|
||||
import ctypes
|
||||
return ctypes
|
||||
except ImportError:
|
||||
return None
|
||||
import ctypes
|
||||
return ctypes
|
||||
|
||||
|
||||
if not PY2:
|
||||
|
@ -126,12 +130,15 @@ def make_set_closure_cell():
|
|||
def set_closure_cell(cell, value):
|
||||
cell.__setstate__((value,))
|
||||
else:
|
||||
ctypes = import_ctypes()
|
||||
if ctypes is not None:
|
||||
try:
|
||||
ctypes = import_ctypes()
|
||||
|
||||
set_closure_cell = ctypes.pythonapi.PyCell_Set
|
||||
set_closure_cell.argtypes = (ctypes.py_object, ctypes.py_object)
|
||||
set_closure_cell.restype = ctypes.c_int
|
||||
else:
|
||||
except Exception:
|
||||
# We try best effort to set the cell, but sometimes it's not
|
||||
# possible. For example on Jython or on GAE.
|
||||
set_closure_cell = just_warn
|
||||
return set_closure_cell
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -23,7 +23,7 @@ def include(*what):
|
|||
Whitelist *what*.
|
||||
|
||||
:param what: What to whitelist.
|
||||
:type what: :class:`list` of :class:`type` or :class:`attr.Attribute`\ s
|
||||
:type what: :class:`list` of :class:`type` or :class:`attr.Attribute`\\ s
|
||||
|
||||
:rtype: :class:`callable`
|
||||
"""
|
||||
|
@ -40,7 +40,7 @@ def exclude(*what):
|
|||
Blacklist *what*.
|
||||
|
||||
:param what: What to blacklist.
|
||||
:type what: :class:`list` of classes or :class:`attr.Attribute`\ s.
|
||||
:type what: :class:`list` of classes or :class:`attr.Attribute`\\ s.
|
||||
|
||||
:rtype: :class:`callable`
|
||||
"""
|
||||
|
|
203
tests/wpt/web-platform-tests/tools/third_party/attrs/tests/strategies.py
vendored
Normal file
203
tests/wpt/web-platform-tests/tools/third_party/attrs/tests/strategies.py
vendored
Normal file
|
@ -0,0 +1,203 @@
|
|||
"""
|
||||
Testing strategies for Hypothesis-based tests.
|
||||
"""
|
||||
|
||||
import keyword
|
||||
import string
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from hypothesis import strategies as st
|
||||
|
||||
import attr
|
||||
|
||||
from .utils import make_class
|
||||
|
||||
|
||||
def gen_attr_names():
|
||||
"""
|
||||
Generate names for attributes, 'a'...'z', then 'aa'...'zz'.
|
||||
|
||||
~702 different attribute names should be enough in practice.
|
||||
|
||||
Some short strings (such as 'as') are keywords, so we skip them.
|
||||
"""
|
||||
lc = string.ascii_lowercase
|
||||
for c in lc:
|
||||
yield c
|
||||
for outer in lc:
|
||||
for inner in lc:
|
||||
res = outer + inner
|
||||
if keyword.iskeyword(res):
|
||||
continue
|
||||
yield outer + inner
|
||||
|
||||
|
||||
def maybe_underscore_prefix(source):
|
||||
"""
|
||||
A generator to sometimes prepend an underscore.
|
||||
"""
|
||||
to_underscore = False
|
||||
for val in source:
|
||||
yield val if not to_underscore else '_' + val
|
||||
to_underscore = not to_underscore
|
||||
|
||||
|
||||
def _create_hyp_class(attrs):
|
||||
"""
|
||||
A helper function for Hypothesis to generate attrs classes.
|
||||
"""
|
||||
return make_class(
|
||||
"HypClass", dict(zip(gen_attr_names(), attrs))
|
||||
)
|
||||
|
||||
|
||||
def _create_hyp_nested_strategy(simple_class_strategy):
|
||||
"""
|
||||
Create a recursive attrs class.
|
||||
|
||||
Given a strategy for building (simpler) classes, create and return
|
||||
a strategy for building classes that have as an attribute: either just
|
||||
the simpler class, a list of simpler classes, a tuple of simpler classes,
|
||||
an ordered dict or a dict mapping the string "cls" to a simpler class.
|
||||
"""
|
||||
# Use a tuple strategy to combine simple attributes and an attr class.
|
||||
def just_class(tup):
|
||||
combined_attrs = list(tup[0])
|
||||
combined_attrs.append(attr.ib(default=attr.Factory(tup[1])))
|
||||
return _create_hyp_class(combined_attrs)
|
||||
|
||||
def list_of_class(tup):
|
||||
default = attr.Factory(lambda: [tup[1]()])
|
||||
combined_attrs = list(tup[0])
|
||||
combined_attrs.append(attr.ib(default=default))
|
||||
return _create_hyp_class(combined_attrs)
|
||||
|
||||
def tuple_of_class(tup):
|
||||
default = attr.Factory(lambda: (tup[1](),))
|
||||
combined_attrs = list(tup[0])
|
||||
combined_attrs.append(attr.ib(default=default))
|
||||
return _create_hyp_class(combined_attrs)
|
||||
|
||||
def dict_of_class(tup):
|
||||
default = attr.Factory(lambda: {"cls": tup[1]()})
|
||||
combined_attrs = list(tup[0])
|
||||
combined_attrs.append(attr.ib(default=default))
|
||||
return _create_hyp_class(combined_attrs)
|
||||
|
||||
def ordereddict_of_class(tup):
|
||||
default = attr.Factory(lambda: OrderedDict([("cls", tup[1]())]))
|
||||
combined_attrs = list(tup[0])
|
||||
combined_attrs.append(attr.ib(default=default))
|
||||
return _create_hyp_class(combined_attrs)
|
||||
|
||||
# A strategy producing tuples of the form ([list of attributes], <given
|
||||
# class strategy>).
|
||||
attrs_and_classes = st.tuples(list_of_attrs, simple_class_strategy)
|
||||
|
||||
return st.one_of(attrs_and_classes.map(just_class),
|
||||
attrs_and_classes.map(list_of_class),
|
||||
attrs_and_classes.map(tuple_of_class),
|
||||
attrs_and_classes.map(dict_of_class),
|
||||
attrs_and_classes.map(ordereddict_of_class))
|
||||
|
||||
|
||||
bare_attrs = st.builds(attr.ib, default=st.none())
|
||||
int_attrs = st.integers().map(lambda i: attr.ib(default=i))
|
||||
str_attrs = st.text().map(lambda s: attr.ib(default=s))
|
||||
float_attrs = st.floats().map(lambda f: attr.ib(default=f))
|
||||
dict_attrs = (st.dictionaries(keys=st.text(), values=st.integers())
|
||||
.map(lambda d: attr.ib(default=d)))
|
||||
|
||||
simple_attrs_without_metadata = (bare_attrs | int_attrs | str_attrs |
|
||||
float_attrs | dict_attrs)
|
||||
|
||||
|
||||
@st.composite
|
||||
def simple_attrs_with_metadata(draw):
|
||||
"""
|
||||
Create a simple attribute with arbitrary metadata.
|
||||
"""
|
||||
c_attr = draw(simple_attrs)
|
||||
keys = st.booleans() | st.binary() | st.integers() | st.text()
|
||||
vals = st.booleans() | st.binary() | st.integers() | st.text()
|
||||
metadata = draw(st.dictionaries(
|
||||
keys=keys, values=vals, min_size=1, max_size=5))
|
||||
|
||||
return attr.ib(
|
||||
default=c_attr._default,
|
||||
validator=c_attr._validator,
|
||||
repr=c_attr.repr,
|
||||
cmp=c_attr.cmp,
|
||||
hash=c_attr.hash,
|
||||
init=c_attr.init,
|
||||
metadata=metadata,
|
||||
type=None,
|
||||
converter=c_attr.converter,
|
||||
)
|
||||
|
||||
|
||||
simple_attrs = simple_attrs_without_metadata | simple_attrs_with_metadata()
|
||||
|
||||
# Python functions support up to 255 arguments.
|
||||
list_of_attrs = st.lists(simple_attrs, max_size=9)
|
||||
|
||||
|
||||
@st.composite
|
||||
def simple_classes(draw, slots=None, frozen=None, private_attrs=None):
|
||||
"""
|
||||
A strategy that generates classes with default non-attr attributes.
|
||||
|
||||
For example, this strategy might generate a class such as:
|
||||
|
||||
@attr.s(slots=True, frozen=True)
|
||||
class HypClass:
|
||||
a = attr.ib(default=1)
|
||||
_b = attr.ib(default=None)
|
||||
c = attr.ib(default='text')
|
||||
_d = attr.ib(default=1.0)
|
||||
c = attr.ib(default={'t': 1})
|
||||
|
||||
By default, all combinations of slots and frozen classes will be generated.
|
||||
If `slots=True` is passed in, only slots classes will be generated, and
|
||||
if `slots=False` is passed in, no slot classes will be generated. The same
|
||||
applies to `frozen`.
|
||||
|
||||
By default, some attributes will be private (i.e. prefixed with an
|
||||
underscore). If `private_attrs=True` is passed in, all attributes will be
|
||||
private, and if `private_attrs=False`, no attributes will be private.
|
||||
"""
|
||||
attrs = draw(list_of_attrs)
|
||||
frozen_flag = draw(st.booleans()) if frozen is None else frozen
|
||||
slots_flag = draw(st.booleans()) if slots is None else slots
|
||||
|
||||
if private_attrs is None:
|
||||
attr_names = maybe_underscore_prefix(gen_attr_names())
|
||||
elif private_attrs is True:
|
||||
attr_names = ('_' + n for n in gen_attr_names())
|
||||
elif private_attrs is False:
|
||||
attr_names = gen_attr_names()
|
||||
|
||||
cls_dict = dict(zip(attr_names, attrs))
|
||||
post_init_flag = draw(st.booleans())
|
||||
if post_init_flag:
|
||||
def post_init(self):
|
||||
pass
|
||||
cls_dict["__attrs_post_init__"] = post_init
|
||||
|
||||
return make_class(
|
||||
"HypClass",
|
||||
cls_dict,
|
||||
slots=slots_flag,
|
||||
frozen=frozen_flag,
|
||||
)
|
||||
|
||||
|
||||
# st.recursive works by taking a base strategy (in this case, simple_classes)
|
||||
# and a special function. This function receives a strategy, and returns
|
||||
# another strategy (building on top of the base strategy).
|
||||
nested_classes = st.recursive(
|
||||
simple_classes(),
|
||||
_create_hyp_nested_strategy,
|
||||
max_leaves=10
|
||||
)
|
|
@ -11,6 +11,7 @@ import pytest
|
|||
|
||||
import attr
|
||||
|
||||
from attr._make import _classvar_prefixes
|
||||
from attr.exceptions import UnannotatedAttributeError
|
||||
|
||||
|
||||
|
@ -32,6 +33,11 @@ class TestAnnotations:
|
|||
assert int is attr.fields(C).x.type
|
||||
assert str is attr.fields(C).y.type
|
||||
assert None is attr.fields(C).z.type
|
||||
assert C.__init__.__annotations__ == {
|
||||
'x': int,
|
||||
'y': str,
|
||||
'return': None,
|
||||
}
|
||||
|
||||
def test_catches_basic_type_conflict(self):
|
||||
"""
|
||||
|
@ -57,6 +63,11 @@ class TestAnnotations:
|
|||
|
||||
assert typing.List[int] is attr.fields(C).x.type
|
||||
assert typing.Optional[str] is attr.fields(C).y.type
|
||||
assert C.__init__.__annotations__ == {
|
||||
'x': typing.List[int],
|
||||
'y': typing.Optional[str],
|
||||
'return': None,
|
||||
}
|
||||
|
||||
def test_only_attrs_annotations_collected(self):
|
||||
"""
|
||||
|
@ -68,6 +79,10 @@ class TestAnnotations:
|
|||
y: int
|
||||
|
||||
assert 1 == len(attr.fields(C))
|
||||
assert C.__init__.__annotations__ == {
|
||||
'x': typing.List[int],
|
||||
'return': None,
|
||||
}
|
||||
|
||||
@pytest.mark.parametrize("slots", [True, False])
|
||||
def test_auto_attribs(self, slots):
|
||||
|
@ -115,6 +130,15 @@ class TestAnnotations:
|
|||
i.y = 23
|
||||
assert 23 == i.y
|
||||
|
||||
assert C.__init__.__annotations__ == {
|
||||
'a': int,
|
||||
'x': typing.List[int],
|
||||
'y': int,
|
||||
'z': int,
|
||||
'foo': typing.Any,
|
||||
'return': None,
|
||||
}
|
||||
|
||||
@pytest.mark.parametrize("slots", [True, False])
|
||||
def test_auto_attribs_unannotated(self, slots):
|
||||
"""
|
||||
|
@ -154,3 +178,52 @@ class TestAnnotations:
|
|||
|
||||
assert "B(a=1, b=2)" == repr(B())
|
||||
assert "C(a=1)" == repr(C())
|
||||
|
||||
assert A.__init__.__annotations__ == {
|
||||
'a': int,
|
||||
'return': None,
|
||||
}
|
||||
assert B.__init__.__annotations__ == {
|
||||
'a': int,
|
||||
'b': int,
|
||||
'return': None,
|
||||
}
|
||||
assert C.__init__.__annotations__ == {
|
||||
'a': int,
|
||||
'return': None,
|
||||
}
|
||||
|
||||
def test_converter_annotations(self):
|
||||
"""
|
||||
Attributes with converters don't have annotations.
|
||||
"""
|
||||
|
||||
@attr.s(auto_attribs=True)
|
||||
class A:
|
||||
a: int = attr.ib(converter=int)
|
||||
|
||||
assert A.__init__.__annotations__ == {'return': None}
|
||||
|
||||
@pytest.mark.parametrize("slots", [True, False])
|
||||
@pytest.mark.parametrize("classvar", _classvar_prefixes)
|
||||
def test_annotations_strings(self, slots, classvar):
|
||||
"""
|
||||
String annotations are passed into __init__ as is.
|
||||
"""
|
||||
@attr.s(auto_attribs=True, slots=slots)
|
||||
class C:
|
||||
cls_var: classvar + '[int]' = 23
|
||||
a: 'int'
|
||||
x: 'typing.List[int]' = attr.Factory(list)
|
||||
y: 'int' = 2
|
||||
z: 'int' = attr.ib(default=3)
|
||||
foo: 'typing.Any' = None
|
||||
|
||||
assert C.__init__.__annotations__ == {
|
||||
'a': 'int',
|
||||
'x': 'typing.List[int]',
|
||||
'y': 'int',
|
||||
'z': 'int',
|
||||
'foo': 'typing.Any',
|
||||
'return': None,
|
||||
}
|
||||
|
|
|
@ -380,3 +380,36 @@ class TestDarkMagic(object):
|
|||
z = attr.ib(default=4)
|
||||
|
||||
assert "E(c=100, b=23, a=42, x=2, d=3.14, y=3, z=4)" == repr(E())
|
||||
|
||||
@pytest.mark.parametrize("base_slots", [True, False])
|
||||
@pytest.mark.parametrize("sub_slots", [True, False])
|
||||
@pytest.mark.parametrize("base_frozen", [True, False])
|
||||
@pytest.mark.parametrize("sub_frozen", [True, False])
|
||||
@pytest.mark.parametrize("base_converter", [True, False])
|
||||
@pytest.mark.parametrize("sub_converter", [True, False])
|
||||
def test_frozen_slots_combo(self, base_slots, sub_slots, base_frozen,
|
||||
sub_frozen, base_converter, sub_converter):
|
||||
"""
|
||||
A class with a single attribute, inheriting from another class
|
||||
with a single attribute.
|
||||
"""
|
||||
|
||||
@attr.s(frozen=base_frozen, slots=base_slots)
|
||||
class Base(object):
|
||||
a = attr.ib(converter=int if base_converter else None)
|
||||
|
||||
@attr.s(frozen=sub_frozen, slots=sub_slots)
|
||||
class Sub(Base):
|
||||
b = attr.ib(converter=int if sub_converter else None)
|
||||
|
||||
i = Sub("1", "2")
|
||||
|
||||
assert i.a == (1 if base_converter else "1")
|
||||
assert i.b == (2 if sub_converter else "2")
|
||||
|
||||
if base_frozen or sub_frozen:
|
||||
with pytest.raises(FrozenInstanceError):
|
||||
i.a = "2"
|
||||
|
||||
with pytest.raises(FrozenInstanceError):
|
||||
i.b = "3"
|
||||
|
|
|
@ -187,6 +187,20 @@ class TestAddRepr(object):
|
|||
"""
|
||||
assert "C(a=1, b=2)" == repr(cls(1, 2))
|
||||
|
||||
def test_infinite_recursion(self):
|
||||
"""
|
||||
In the presence of a cyclic graph, repr will emit an ellipsis and not
|
||||
raise an exception.
|
||||
"""
|
||||
@attr.s
|
||||
class Cycle(object):
|
||||
value = attr.ib(default=7)
|
||||
cycle = attr.ib(default=None)
|
||||
|
||||
cycle = Cycle()
|
||||
cycle.cycle = cycle
|
||||
assert "Cycle(value=7, cycle=...)" == repr(cycle)
|
||||
|
||||
def test_underscores(self):
|
||||
"""
|
||||
repr does not strip underscores.
|
||||
|
@ -200,6 +214,16 @@ class TestAddRepr(object):
|
|||
|
||||
assert "C(_x=42)" == repr(i)
|
||||
|
||||
def test_repr_uninitialized_member(self):
|
||||
"""
|
||||
repr signals unset attributes
|
||||
"""
|
||||
C = make_class("C", {
|
||||
"a": attr.ib(init=False),
|
||||
})
|
||||
|
||||
assert "C(a=NOTHING)" == repr(C())
|
||||
|
||||
@given(add_str=booleans(), slots=booleans())
|
||||
def test_str(self, add_str, slots):
|
||||
"""
|
||||
|
|
|
@ -8,8 +8,8 @@ from collections import Mapping, OrderedDict, Sequence
|
|||
|
||||
import pytest
|
||||
|
||||
from hypothesis import strategies as st
|
||||
from hypothesis import HealthCheck, assume, given, settings
|
||||
from hypothesis import strategies as st
|
||||
|
||||
import attr
|
||||
|
||||
|
@ -18,7 +18,7 @@ from attr._compat import TYPE
|
|||
from attr.exceptions import AttrsAttributeNotFoundError
|
||||
from attr.validators import instance_of
|
||||
|
||||
from .utils import nested_classes, simple_classes
|
||||
from .strategies import nested_classes, simple_classes
|
||||
|
||||
|
||||
MAPPING_TYPES = (dict, OrderedDict)
|
||||
|
|
|
@ -4,7 +4,9 @@ Tests for `attr._make`.
|
|||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import copy
|
||||
import inspect
|
||||
import itertools
|
||||
import sys
|
||||
|
||||
from operator import attrgetter
|
||||
|
@ -17,17 +19,19 @@ from hypothesis.strategies import booleans, integers, lists, sampled_from, text
|
|||
import attr
|
||||
|
||||
from attr import _config
|
||||
from attr._compat import PY2
|
||||
from attr._compat import PY2, ordered_dict
|
||||
from attr._make import (
|
||||
Attribute, Factory, _AndValidator, _Attributes, _ClassBuilder,
|
||||
_CountingAttr, _transform_attrs, and_, fields, make_class, validate
|
||||
_CountingAttr, _transform_attrs, and_, fields, fields_dict, make_class,
|
||||
validate
|
||||
)
|
||||
from attr.exceptions import DefaultAlreadySetError, NotAnAttrsClassError
|
||||
|
||||
from .utils import (
|
||||
gen_attr_names, list_of_attrs, simple_attr, simple_attrs,
|
||||
from .strategies import (
|
||||
gen_attr_names, list_of_attrs, simple_attrs, simple_attrs_with_metadata,
|
||||
simple_attrs_without_metadata, simple_classes
|
||||
)
|
||||
from .utils import simple_attr
|
||||
|
||||
|
||||
attrs_st = simple_attrs.map(lambda c: Attribute.from_counting_attr("name", c))
|
||||
|
@ -120,6 +124,66 @@ class TestCountingAttr(object):
|
|||
assert Factory(f, True) == a._default
|
||||
|
||||
|
||||
class TestAttribute(object):
|
||||
"""
|
||||
Tests for `attr.Attribute`.
|
||||
"""
|
||||
def test_deprecated_convert_argument(self):
|
||||
"""
|
||||
Using *convert* raises a DeprecationWarning and sets the converter
|
||||
field.
|
||||
"""
|
||||
def conv(v):
|
||||
return v
|
||||
|
||||
with pytest.warns(DeprecationWarning) as wi:
|
||||
a = Attribute(
|
||||
"a", True, True, True, True, True, True, convert=conv
|
||||
)
|
||||
w = wi.pop()
|
||||
|
||||
assert conv == a.converter
|
||||
assert (
|
||||
"The `convert` argument is deprecated in favor of `converter`. "
|
||||
"It will be removed after 2019/01.",
|
||||
) == w.message.args
|
||||
assert __file__ == w.filename
|
||||
|
||||
def test_deprecated_convert_attribute(self):
|
||||
"""
|
||||
If Attribute.convert is accessed, a DeprecationWarning is raised.
|
||||
"""
|
||||
def conv(v):
|
||||
return v
|
||||
|
||||
a = simple_attr("a", converter=conv)
|
||||
with pytest.warns(DeprecationWarning) as wi:
|
||||
convert = a.convert
|
||||
w = wi.pop()
|
||||
|
||||
assert conv is convert is a.converter
|
||||
assert (
|
||||
"The `convert` attribute is deprecated in favor of `converter`. "
|
||||
"It will be removed after 2019/01.",
|
||||
) == w.message.args
|
||||
assert __file__ == w.filename
|
||||
|
||||
def test_convert_converter(self):
|
||||
"""
|
||||
A TypeError is raised if both *convert* and *converter* are passed.
|
||||
"""
|
||||
with pytest.raises(RuntimeError) as ei:
|
||||
Attribute(
|
||||
"a", True, True, True, True, True, True,
|
||||
convert=lambda v: v, converter=lambda v: v,
|
||||
)
|
||||
|
||||
assert (
|
||||
"Can't pass both `convert` and `converter`. "
|
||||
"Please use `converter` only.",
|
||||
) == ei.value.args
|
||||
|
||||
|
||||
def make_tc():
|
||||
class TransformC(object):
|
||||
z = attr.ib()
|
||||
|
@ -147,7 +211,7 @@ class TestTransformAttrs(object):
|
|||
Transforms every `_CountingAttr` and leaves others (a) be.
|
||||
"""
|
||||
C = make_tc()
|
||||
attrs, _, = _transform_attrs(C, None, False)
|
||||
attrs, _, _ = _transform_attrs(C, None, False)
|
||||
|
||||
assert ["z", "y", "x"] == [a.name for a in attrs]
|
||||
|
||||
|
@ -159,14 +223,14 @@ class TestTransformAttrs(object):
|
|||
class C(object):
|
||||
pass
|
||||
|
||||
assert _Attributes(((), [])) == _transform_attrs(C, None, False)
|
||||
assert _Attributes(((), [], {})) == _transform_attrs(C, None, False)
|
||||
|
||||
def test_transforms_to_attribute(self):
|
||||
"""
|
||||
All `_CountingAttr`s are transformed into `Attribute`s.
|
||||
"""
|
||||
C = make_tc()
|
||||
attrs, super_attrs = _transform_attrs(C, None, False)
|
||||
attrs, super_attrs, _ = _transform_attrs(C, None, False)
|
||||
|
||||
assert [] == super_attrs
|
||||
assert 3 == len(attrs)
|
||||
|
@ -187,8 +251,8 @@ class TestTransformAttrs(object):
|
|||
"No mandatory attributes allowed after an attribute with a "
|
||||
"default value or factory. Attribute in question: Attribute"
|
||||
"(name='y', default=NOTHING, validator=None, repr=True, "
|
||||
"cmp=True, hash=None, init=True, convert=None, "
|
||||
"metadata=mappingproxy({}), type=None)",
|
||||
"cmp=True, hash=None, init=True, metadata=mappingproxy({}), "
|
||||
"type=None, converter=None)",
|
||||
) == e.value.args
|
||||
|
||||
def test_these(self):
|
||||
|
@ -201,13 +265,38 @@ class TestTransformAttrs(object):
|
|||
class C(Base):
|
||||
y = attr.ib()
|
||||
|
||||
attrs, super_attrs = _transform_attrs(C, {"x": attr.ib()}, False)
|
||||
attrs, super_attrs, _ = _transform_attrs(C, {"x": attr.ib()}, False)
|
||||
|
||||
assert [] == super_attrs
|
||||
assert (
|
||||
simple_attr("x"),
|
||||
) == attrs
|
||||
|
||||
def test_these_leave_body(self):
|
||||
"""
|
||||
If these is passed, no attributes are removed from the body.
|
||||
"""
|
||||
@attr.s(init=False, these={"x": attr.ib()})
|
||||
class C(object):
|
||||
x = 5
|
||||
|
||||
assert 5 == C().x
|
||||
assert "C(x=5)" == repr(C())
|
||||
|
||||
def test_these_ordered(self):
|
||||
"""
|
||||
If these is passed ordered attrs, their order respect instead of the
|
||||
counter.
|
||||
"""
|
||||
b = attr.ib(default=2)
|
||||
a = attr.ib(default=1)
|
||||
|
||||
@attr.s(these=ordered_dict([("a", a), ("b", b)]))
|
||||
class C(object):
|
||||
pass
|
||||
|
||||
assert "C(a=1, b=2)" == repr(C())
|
||||
|
||||
def test_multiple_inheritance(self):
|
||||
"""
|
||||
Order of attributes doesn't get mixed up by multiple inheritance.
|
||||
|
@ -235,7 +324,7 @@ class TestTransformAttrs(object):
|
|||
d2 = attr.ib(default="d2")
|
||||
|
||||
@attr.s
|
||||
class E(D, C):
|
||||
class E(C, D):
|
||||
e1 = attr.ib(default="e1")
|
||||
e2 = attr.ib(default="e2")
|
||||
|
||||
|
@ -437,6 +526,35 @@ class TestAttributes(object):
|
|||
|
||||
assert not isinstance(x, _CountingAttr)
|
||||
|
||||
def test_factory_sugar(self):
|
||||
"""
|
||||
Passing factory=f is syntactic sugar for passing default=Factory(f).
|
||||
"""
|
||||
@attr.s
|
||||
class C(object):
|
||||
x = attr.ib(factory=list)
|
||||
|
||||
assert Factory(list) == attr.fields(C).x.default
|
||||
|
||||
def test_sugar_factory_mutex(self):
|
||||
"""
|
||||
Passing both default and factory raises ValueError.
|
||||
"""
|
||||
with pytest.raises(ValueError, match="mutually exclusive"):
|
||||
@attr.s
|
||||
class C(object):
|
||||
x = attr.ib(factory=list, default=Factory(list))
|
||||
|
||||
def test_sugar_callable(self):
|
||||
"""
|
||||
Factory has to be a callable to prevent people from passing Factory
|
||||
into it.
|
||||
"""
|
||||
with pytest.raises(ValueError, match="must be a callable"):
|
||||
@attr.s
|
||||
class C(object):
|
||||
x = attr.ib(factory=Factory(list))
|
||||
|
||||
|
||||
@attr.s
|
||||
class GC(object):
|
||||
|
@ -537,6 +655,18 @@ class TestMakeClass(object):
|
|||
|
||||
assert 1 == len(C.__attrs_attrs__)
|
||||
|
||||
def test_make_class_ordered(self):
|
||||
"""
|
||||
If `make_class()` is passed ordered attrs, their order is respected
|
||||
instead of the counter.
|
||||
"""
|
||||
b = attr.ib(default=2)
|
||||
a = attr.ib(default=1)
|
||||
|
||||
C = attr.make_class("C", ordered_dict([("a", a), ("b", b)]))
|
||||
|
||||
assert "C(a=1, b=2)" == repr(C())
|
||||
|
||||
|
||||
class TestFields(object):
|
||||
"""
|
||||
|
@ -557,6 +687,7 @@ class TestFields(object):
|
|||
"""
|
||||
with pytest.raises(NotAnAttrsClassError) as e:
|
||||
fields(object)
|
||||
|
||||
assert (
|
||||
"{o!r} is not an attrs-decorated class.".format(o=object)
|
||||
) == e.value.args[0]
|
||||
|
@ -577,16 +708,52 @@ class TestFields(object):
|
|||
assert getattr(fields(C), attribute.name) is attribute
|
||||
|
||||
|
||||
class TestConvert(object):
|
||||
class TestFieldsDict(object):
|
||||
"""
|
||||
Tests for `fields_dict`.
|
||||
"""
|
||||
def test_instance(self, C):
|
||||
"""
|
||||
Raises `TypeError` on non-classes.
|
||||
"""
|
||||
with pytest.raises(TypeError) as e:
|
||||
fields_dict(C(1, 2))
|
||||
|
||||
assert "Passed object must be a class." == e.value.args[0]
|
||||
|
||||
def test_handler_non_attrs_class(self, C):
|
||||
"""
|
||||
Raises `ValueError` if passed a non-``attrs`` instance.
|
||||
"""
|
||||
with pytest.raises(NotAnAttrsClassError) as e:
|
||||
fields_dict(object)
|
||||
|
||||
assert (
|
||||
"{o!r} is not an attrs-decorated class.".format(o=object)
|
||||
) == e.value.args[0]
|
||||
|
||||
@given(simple_classes())
|
||||
def test_fields_dict(self, C):
|
||||
"""
|
||||
Returns an ordered dict of ``{attribute_name: Attribute}``.
|
||||
"""
|
||||
d = fields_dict(C)
|
||||
|
||||
assert isinstance(d, ordered_dict)
|
||||
assert list(fields(C)) == list(d.values())
|
||||
assert [a.name for a in fields(C)] == [field_name for field_name in d]
|
||||
|
||||
|
||||
class TestConverter(object):
|
||||
"""
|
||||
Tests for attribute conversion.
|
||||
"""
|
||||
def test_convert(self):
|
||||
"""
|
||||
Return value of convert is used as the attribute's value.
|
||||
Return value of converter is used as the attribute's value.
|
||||
"""
|
||||
C = make_class("C", {
|
||||
"x": attr.ib(convert=lambda v: v + 1),
|
||||
"x": attr.ib(converter=lambda v: v + 1),
|
||||
"y": attr.ib(),
|
||||
})
|
||||
c = C(1, 2)
|
||||
|
@ -601,7 +768,7 @@ class TestConvert(object):
|
|||
"""
|
||||
C = make_class("C", {
|
||||
"y": attr.ib(),
|
||||
"x": attr.ib(init=init, default=val, convert=lambda v: v + 1),
|
||||
"x": attr.ib(init=init, default=val, converter=lambda v: v + 1),
|
||||
})
|
||||
c = C(2)
|
||||
|
||||
|
@ -613,13 +780,14 @@ class TestConvert(object):
|
|||
"""
|
||||
Property tests for attributes with convert, and a factory default.
|
||||
"""
|
||||
C = make_class("C", {
|
||||
"y": attr.ib(),
|
||||
"x": attr.ib(
|
||||
C = make_class("C", ordered_dict([
|
||||
("y", attr.ib()),
|
||||
("x", attr.ib(
|
||||
init=init,
|
||||
default=Factory(lambda: val),
|
||||
convert=lambda v: v + 1),
|
||||
})
|
||||
converter=lambda v: v + 1
|
||||
)),
|
||||
]))
|
||||
c = C(2)
|
||||
|
||||
assert c.x == val + 1
|
||||
|
@ -653,7 +821,7 @@ class TestConvert(object):
|
|||
raise RuntimeError("foo")
|
||||
C = make_class(
|
||||
"C", {
|
||||
"x": attr.ib(validator=validator, convert=lambda v: 1 / 0),
|
||||
"x": attr.ib(validator=validator, converter=lambda v: 1 / 0),
|
||||
"y": attr.ib(),
|
||||
})
|
||||
with pytest.raises(ZeroDivisionError):
|
||||
|
@ -664,10 +832,49 @@ class TestConvert(object):
|
|||
Converters circumvent immutability.
|
||||
"""
|
||||
C = make_class("C", {
|
||||
"x": attr.ib(convert=lambda v: int(v)),
|
||||
"x": attr.ib(converter=lambda v: int(v)),
|
||||
}, frozen=True)
|
||||
C("1")
|
||||
|
||||
def test_deprecated_convert(self):
|
||||
"""
|
||||
Using *convert* raises a DeprecationWarning and sets the converter
|
||||
field.
|
||||
"""
|
||||
def conv(v):
|
||||
return v
|
||||
|
||||
with pytest.warns(DeprecationWarning) as wi:
|
||||
@attr.s
|
||||
class C(object):
|
||||
x = attr.ib(convert=conv)
|
||||
|
||||
convert = fields(C).x.convert
|
||||
|
||||
assert 2 == len(wi.list)
|
||||
w = wi.pop()
|
||||
|
||||
assert conv == fields(C).x.converter == convert
|
||||
assert (
|
||||
"The `convert` argument is deprecated in favor of `converter`. "
|
||||
"It will be removed after 2019/01.",
|
||||
) == w.message.args
|
||||
assert __file__ == w.filename
|
||||
|
||||
def test_convert_converter(self):
|
||||
"""
|
||||
A TypeError is raised if both *convert* and *converter* are passed.
|
||||
"""
|
||||
with pytest.raises(RuntimeError) as ei:
|
||||
@attr.s
|
||||
class C(object):
|
||||
x = attr.ib(convert=lambda v: v, converter=lambda v: v)
|
||||
|
||||
assert (
|
||||
"Can't pass both `convert` and `converter`. "
|
||||
"Please use `converter` only.",
|
||||
) == ei.value.args
|
||||
|
||||
|
||||
class TestValidate(object):
|
||||
"""
|
||||
|
@ -823,6 +1030,39 @@ class TestMetadata(object):
|
|||
for a in fields(C)[1:]:
|
||||
assert a.metadata is fields(C)[0].metadata
|
||||
|
||||
@given(lists(simple_attrs_without_metadata, min_size=2, max_size=5))
|
||||
def test_empty_countingattr_metadata_independent(self, list_of_attrs):
|
||||
"""
|
||||
All empty metadata attributes are independent before ``@attr.s``.
|
||||
"""
|
||||
for x, y in itertools.combinations(list_of_attrs, 2):
|
||||
assert x.metadata is not y.metadata
|
||||
|
||||
@given(lists(simple_attrs_with_metadata(), min_size=2, max_size=5))
|
||||
def test_not_none_metadata(self, list_of_attrs):
|
||||
"""
|
||||
Non-empty metadata attributes exist as fields after ``@attr.s``.
|
||||
"""
|
||||
C = make_class("C", dict(zip(gen_attr_names(), list_of_attrs)))
|
||||
|
||||
assert len(fields(C)) > 0
|
||||
|
||||
for cls_a, raw_a in zip(fields(C), list_of_attrs):
|
||||
assert cls_a.metadata != {}
|
||||
assert cls_a.metadata == raw_a.metadata
|
||||
|
||||
def test_metadata(self):
|
||||
"""
|
||||
If metadata that is not None is passed, it is used.
|
||||
|
||||
This is necessary for coverage because the previous test is
|
||||
hypothesis-based.
|
||||
"""
|
||||
md = {}
|
||||
a = attr.ib(metadata=md)
|
||||
|
||||
assert md is a.metadata
|
||||
|
||||
|
||||
class TestClassBuilder(object):
|
||||
"""
|
||||
|
@ -864,3 +1104,62 @@ class TestClassBuilder(object):
|
|||
.build_class()
|
||||
|
||||
assert "ns.C(x=1)" == repr(cls(1))
|
||||
|
||||
@pytest.mark.parametrize("meth_name", [
|
||||
"__init__", "__hash__", "__repr__", "__str__",
|
||||
"__eq__", "__ne__", "__lt__", "__le__", "__gt__", "__ge__",
|
||||
])
|
||||
def test_attaches_meta_dunders(self, meth_name):
|
||||
"""
|
||||
Generated methods have correct __module__, __name__, and __qualname__
|
||||
attributes.
|
||||
"""
|
||||
@attr.s(hash=True, str=True)
|
||||
class C(object):
|
||||
def organic(self):
|
||||
pass
|
||||
|
||||
meth = getattr(C, meth_name)
|
||||
|
||||
assert meth_name == meth.__name__
|
||||
assert C.organic.__module__ == meth.__module__
|
||||
if not PY2:
|
||||
organic_prefix = C.organic.__qualname__.rsplit(".", 1)[0]
|
||||
assert organic_prefix + "." + meth_name == meth.__qualname__
|
||||
|
||||
def test_handles_missing_meta_on_class(self):
|
||||
"""
|
||||
If the class hasn't a __module__ or __qualname__, the method hasn't
|
||||
either.
|
||||
"""
|
||||
class C(object):
|
||||
pass
|
||||
|
||||
b = _ClassBuilder(
|
||||
C, these=None, slots=False, frozen=False, auto_attribs=False,
|
||||
)
|
||||
b._cls = {} # no __module__; no __qualname__
|
||||
|
||||
def fake_meth(self):
|
||||
pass
|
||||
|
||||
fake_meth.__module__ = "42"
|
||||
fake_meth.__qualname__ = "23"
|
||||
|
||||
rv = b._add_method_dunders(fake_meth)
|
||||
|
||||
assert "42" == rv.__module__ == fake_meth.__module__
|
||||
assert "23" == rv.__qualname__ == fake_meth.__qualname__
|
||||
|
||||
def test_weakref_setstate(self):
|
||||
"""
|
||||
__weakref__ is not set on in setstate because it's not writable in
|
||||
slots classes.
|
||||
"""
|
||||
@attr.s(slots=True)
|
||||
class C(object):
|
||||
__weakref__ = attr.ib(
|
||||
init=False, hash=False, repr=False, cmp=False
|
||||
)
|
||||
|
||||
assert C() == copy.deepcopy(C())
|
||||
|
|
|
@ -9,8 +9,8 @@ import zope.interface
|
|||
|
||||
import attr
|
||||
|
||||
from attr import validators as validator_module
|
||||
from attr import has
|
||||
from attr import validators as validator_module
|
||||
from attr._compat import TYPE
|
||||
from attr.validators import and_, in_, instance_of, optional, provides
|
||||
|
||||
|
|
|
@ -4,15 +4,6 @@ Common helper functions for tests.
|
|||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import keyword
|
||||
import string
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from hypothesis import strategies as st
|
||||
|
||||
import attr
|
||||
|
||||
from attr import Attribute
|
||||
from attr._make import NOTHING, make_class
|
||||
|
||||
|
@ -30,13 +21,13 @@ def simple_class(cmp=False, repr=False, hash=False, str=False, slots=False,
|
|||
|
||||
|
||||
def simple_attr(name, default=NOTHING, validator=None, repr=True,
|
||||
cmp=True, hash=None, init=True):
|
||||
cmp=True, hash=None, init=True, converter=None):
|
||||
"""
|
||||
Return an attribute with a name and no other bells and whistles.
|
||||
"""
|
||||
return Attribute(
|
||||
name=name, default=default, validator=validator, repr=repr,
|
||||
cmp=cmp, hash=hash, init=init
|
||||
cmp=cmp, hash=hash, init=init, converter=converter,
|
||||
)
|
||||
|
||||
|
||||
|
@ -55,183 +46,3 @@ class TestSimpleClass(object):
|
|||
Each call returns a completely new class.
|
||||
"""
|
||||
assert simple_class() is not simple_class()
|
||||
|
||||
|
||||
def gen_attr_names():
|
||||
"""
|
||||
Generate names for attributes, 'a'...'z', then 'aa'...'zz'.
|
||||
|
||||
~702 different attribute names should be enough in practice.
|
||||
|
||||
Some short strings (such as 'as') are keywords, so we skip them.
|
||||
"""
|
||||
lc = string.ascii_lowercase
|
||||
for c in lc:
|
||||
yield c
|
||||
for outer in lc:
|
||||
for inner in lc:
|
||||
res = outer + inner
|
||||
if keyword.iskeyword(res):
|
||||
continue
|
||||
yield outer + inner
|
||||
|
||||
|
||||
def maybe_underscore_prefix(source):
|
||||
"""
|
||||
A generator to sometimes prepend an underscore.
|
||||
"""
|
||||
to_underscore = False
|
||||
for val in source:
|
||||
yield val if not to_underscore else '_' + val
|
||||
to_underscore = not to_underscore
|
||||
|
||||
|
||||
def _create_hyp_class(attrs):
|
||||
"""
|
||||
A helper function for Hypothesis to generate attrs classes.
|
||||
"""
|
||||
return make_class(
|
||||
"HypClass", dict(zip(gen_attr_names(), attrs))
|
||||
)
|
||||
|
||||
|
||||
def _create_hyp_nested_strategy(simple_class_strategy):
|
||||
"""
|
||||
Create a recursive attrs class.
|
||||
|
||||
Given a strategy for building (simpler) classes, create and return
|
||||
a strategy for building classes that have as an attribute: either just
|
||||
the simpler class, a list of simpler classes, a tuple of simpler classes,
|
||||
an ordered dict or a dict mapping the string "cls" to a simpler class.
|
||||
"""
|
||||
# Use a tuple strategy to combine simple attributes and an attr class.
|
||||
def just_class(tup):
|
||||
combined_attrs = list(tup[0])
|
||||
combined_attrs.append(attr.ib(default=attr.Factory(tup[1])))
|
||||
return _create_hyp_class(combined_attrs)
|
||||
|
||||
def list_of_class(tup):
|
||||
default = attr.Factory(lambda: [tup[1]()])
|
||||
combined_attrs = list(tup[0])
|
||||
combined_attrs.append(attr.ib(default=default))
|
||||
return _create_hyp_class(combined_attrs)
|
||||
|
||||
def tuple_of_class(tup):
|
||||
default = attr.Factory(lambda: (tup[1](),))
|
||||
combined_attrs = list(tup[0])
|
||||
combined_attrs.append(attr.ib(default=default))
|
||||
return _create_hyp_class(combined_attrs)
|
||||
|
||||
def dict_of_class(tup):
|
||||
default = attr.Factory(lambda: {"cls": tup[1]()})
|
||||
combined_attrs = list(tup[0])
|
||||
combined_attrs.append(attr.ib(default=default))
|
||||
return _create_hyp_class(combined_attrs)
|
||||
|
||||
def ordereddict_of_class(tup):
|
||||
default = attr.Factory(lambda: OrderedDict([("cls", tup[1]())]))
|
||||
combined_attrs = list(tup[0])
|
||||
combined_attrs.append(attr.ib(default=default))
|
||||
return _create_hyp_class(combined_attrs)
|
||||
|
||||
# A strategy producing tuples of the form ([list of attributes], <given
|
||||
# class strategy>).
|
||||
attrs_and_classes = st.tuples(list_of_attrs, simple_class_strategy)
|
||||
|
||||
return st.one_of(attrs_and_classes.map(just_class),
|
||||
attrs_and_classes.map(list_of_class),
|
||||
attrs_and_classes.map(tuple_of_class),
|
||||
attrs_and_classes.map(dict_of_class),
|
||||
attrs_and_classes.map(ordereddict_of_class))
|
||||
|
||||
|
||||
bare_attrs = st.just(attr.ib(default=None))
|
||||
int_attrs = st.integers().map(lambda i: attr.ib(default=i))
|
||||
str_attrs = st.text().map(lambda s: attr.ib(default=s))
|
||||
float_attrs = st.floats().map(lambda f: attr.ib(default=f))
|
||||
dict_attrs = (st.dictionaries(keys=st.text(), values=st.integers())
|
||||
.map(lambda d: attr.ib(default=d)))
|
||||
|
||||
simple_attrs_without_metadata = (bare_attrs | int_attrs | str_attrs |
|
||||
float_attrs | dict_attrs)
|
||||
|
||||
|
||||
@st.composite
|
||||
def simple_attrs_with_metadata(draw):
|
||||
"""
|
||||
Create a simple attribute with arbitrary metadata.
|
||||
"""
|
||||
c_attr = draw(simple_attrs)
|
||||
keys = st.booleans() | st.binary() | st.integers() | st.text()
|
||||
vals = st.booleans() | st.binary() | st.integers() | st.text()
|
||||
metadata = draw(st.dictionaries(keys=keys, values=vals))
|
||||
|
||||
return attr.ib(c_attr._default, c_attr._validator, c_attr.repr,
|
||||
c_attr.cmp, c_attr.hash, c_attr.init, c_attr.convert,
|
||||
metadata)
|
||||
|
||||
|
||||
simple_attrs = simple_attrs_without_metadata | simple_attrs_with_metadata()
|
||||
|
||||
# Python functions support up to 255 arguments.
|
||||
list_of_attrs = st.lists(simple_attrs, average_size=3, max_size=9)
|
||||
|
||||
|
||||
@st.composite
|
||||
def simple_classes(draw, slots=None, frozen=None, private_attrs=None):
|
||||
"""
|
||||
A strategy that generates classes with default non-attr attributes.
|
||||
|
||||
For example, this strategy might generate a class such as:
|
||||
|
||||
@attr.s(slots=True, frozen=True)
|
||||
class HypClass:
|
||||
a = attr.ib(default=1)
|
||||
_b = attr.ib(default=None)
|
||||
c = attr.ib(default='text')
|
||||
_d = attr.ib(default=1.0)
|
||||
c = attr.ib(default={'t': 1})
|
||||
|
||||
By default, all combinations of slots and frozen classes will be generated.
|
||||
If `slots=True` is passed in, only slots classes will be generated, and
|
||||
if `slots=False` is passed in, no slot classes will be generated. The same
|
||||
applies to `frozen`.
|
||||
|
||||
By default, some attributes will be private (i.e. prefixed with an
|
||||
underscore). If `private_attrs=True` is passed in, all attributes will be
|
||||
private, and if `private_attrs=False`, no attributes will be private.
|
||||
"""
|
||||
attrs = draw(list_of_attrs)
|
||||
frozen_flag = draw(st.booleans()) if frozen is None else frozen
|
||||
slots_flag = draw(st.booleans()) if slots is None else slots
|
||||
|
||||
if private_attrs is None:
|
||||
attr_names = maybe_underscore_prefix(gen_attr_names())
|
||||
elif private_attrs is True:
|
||||
attr_names = ('_' + n for n in gen_attr_names())
|
||||
elif private_attrs is False:
|
||||
attr_names = gen_attr_names()
|
||||
|
||||
cls_dict = dict(zip(attr_names, attrs))
|
||||
post_init_flag = draw(st.booleans())
|
||||
if post_init_flag:
|
||||
def post_init(self):
|
||||
pass
|
||||
cls_dict["__attrs_post_init__"] = post_init
|
||||
|
||||
return make_class(
|
||||
"HypClass",
|
||||
cls_dict,
|
||||
slots=slots_flag,
|
||||
frozen=frozen_flag,
|
||||
)
|
||||
|
||||
|
||||
# st.recursive works by taking a base strategy (in this case, simple_classes)
|
||||
# and a special function. This function receives a strategy, and returns
|
||||
# another strategy (building on top of the base strategy).
|
||||
nested_classes = st.recursive(
|
||||
simple_classes(),
|
||||
_create_hyp_nested_strategy,
|
||||
max_leaves=10
|
||||
)
|
||||
|
|
|
@ -7,17 +7,22 @@ envlist = isort,py27,py34,py35,py36,pypy,pypy3,flake8,manifest,docs,readme,chang
|
|||
# https://github.com/pypa/setuptools/issues/1042 from breaking our builds.
|
||||
setenv =
|
||||
VIRTUALENV_NO_DOWNLOAD=1
|
||||
deps = -rdev-requirements.txt
|
||||
extras = tests
|
||||
commands = python -m pytest {posargs}
|
||||
|
||||
|
||||
[testenv:py27]
|
||||
deps = -rdev-requirements.txt
|
||||
extras = tests
|
||||
commands = coverage run --parallel -m pytest {posargs}
|
||||
|
||||
|
||||
[testenv:py36]
|
||||
deps = -rdev-requirements.txt
|
||||
# Python 3.6+ has a number of compile-time warnings on invalid string escapes.
|
||||
# PYTHONWARNINGS=d and --no-compile below make them visible during the Tox run.
|
||||
install_command = pip install --no-compile {opts} {packages}
|
||||
setenv =
|
||||
PYTHONWARNINGS=d
|
||||
extras = tests
|
||||
commands = coverage run --parallel -m pytest {posargs}
|
||||
|
||||
|
||||
|
@ -33,9 +38,9 @@ commands =
|
|||
|
||||
[testenv:flake8]
|
||||
basepython = python3.6
|
||||
extras = tests
|
||||
# Needs a full install so isort can determine own/foreign imports.
|
||||
deps =
|
||||
-rdev-requirements.txt
|
||||
flake8
|
||||
flake8-isort
|
||||
commands = flake8 src tests setup.py conftest.py docs/conf.py
|
||||
|
@ -43,9 +48,9 @@ commands = flake8 src tests setup.py conftest.py docs/conf.py
|
|||
|
||||
[testenv:isort]
|
||||
basepython = python3.6
|
||||
extras = tests
|
||||
# Needs a full install so isort can determine own/foreign imports.
|
||||
deps =
|
||||
-rdev-requirements.txt
|
||||
isort
|
||||
commands =
|
||||
isort --recursive setup.py conftest.py src tests
|
||||
|
@ -55,7 +60,7 @@ commands =
|
|||
basepython = python3.6
|
||||
setenv =
|
||||
PYTHONHASHSEED = 0
|
||||
deps = -rdocs-requirements.txt
|
||||
extras = docs
|
||||
commands =
|
||||
sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/_build/html
|
||||
sphinx-build -W -b doctest -d {envtmpdir}/doctrees docs docs/_build/html
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue